windows-nt/Source/XPSP1/NT/base/ntsetup/opktools/setupmgr/supplib/settngs.c

1030 lines
26 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
//----------------------------------------------------------------------------
//
// Copyright (c) 1997-1999 Microsoft Corporation
// All rights reserved.
//
// File Name:
// settngs.c
//
// Description:
//
// This file contains the support routines to deal with settings
// in answer files.
//
// This wizard does not do in-place editting of answer files.
// These apis must be used to write to the answer file.
//
// For most settings, the job is easy. Call SettingQueue_AddSetting,
// and specify the [SectionName] KeyName=Value and which queue (the
// answer file or the .udf for the case of multiple computer names).
// Check common\savefile.c for tons of examples.
//
// Be aware that on an edit, these queues are initialized with the
// settings loaded from the original file.
//
// When the user edits a script and pushes NEXT on the NewOrEdit
// page, the settings in the existing answer file and .udf are loaded
// onto the OrignalAnswerFileQueue and the OriginalUdfQueue.
//
// When the user pushes NEXT on the SaveScript page, the following
// ocurrs. (the below code is in common\save.c)
//
// Empty(AnswerFileQueue)
// Empty(UdfQueue)
//
// Copy original answer file settings to the AnswerFileQueue
// Copy original .udf settings to the UdfQueue
//
// Call common\savefile.c to enqueue all new settings
//
// Flush(AnswerFileQueue)
// Flush(UdfQueue)
//
// To support "Do not specify this setting", you must call
// SettingQueue_AddSetting with an lpValue of "". SettingQueue_Flush
// writes nothing if lpValue == "". Failure to do this results in
// the original setting being preserved in the outputed answer file.
//
// You must also ensure that mutually excluded settings are cleared
// by setting the lpValue to "". For example, if JoinWorkGroup=workgroup,
// then, make sure to set JoinDomain="", CreateComputerAccount="" etc.
//
// A section can be marked as volatile. This is needed for the network
// pages because many sections should be removed if (for example) the
// user changes from CustomNet to TypicalNet. When the queue is flushed,
// any sections still marked as volatile will not be written to the file.
// Use SettingQueue_MarkVolatile to mark a section as such. Check
// common\loadfile.c for examples.
//
// In contrast to in-place editing, being able to mark a section
// as volatile at load time means that the answer file does not have
// to be re-read to determine what should be removed at save time.
//
//----------------------------------------------------------------------------
#include "pch.h"
#include "settypes.h"
//
// Declare the queues
//
// AnswerFileQueue holds the settings to write
// UdfQueue holds the settings in case of mulitple computers
//
// OrigAnswerFileQueue holds the settings loaded on an edit
// OrigUdfFileQueue holds the settings loaded on an edit in the .udf
//
// The first 2 are "output queues"
// The next 2 are "input queues"
// The last is a queue for the HAL and SCSI OEM settings.
//
// First, we read the answer file and .udf at the NewOrEdit page and
// place each setting on the Orig*Queue's.
//
// When user is way at the end of the wizard (SaveScript page), we
// empty the AnswerFileQueue and UdfQueue and initialize it with a
// copy of the original settings. This is all necessary because we
// don't want to merge with garbage we previously put on the queue
// if the user went back and forth in the wizard alot.
//
static LINKED_LIST AnswerFileQueue = { 0 };
static LINKED_LIST UdfQueue = { 0 };
static LINKED_LIST OrigAnswerFileQueue = { 0 };
static LINKED_LIST OrigUdfQueue = { 0 };
static LINKED_LIST TxtSetupOemQueue = { 0 };
//
// Local prototypes
//
SECTION_NODE *
SettingQueue_AddSection(LPTSTR lpSection, QUEUENUM dwWhichQueue);
static SECTION_NODE *FindQueuedSection(LPTSTR lpSection,
QUEUENUM dwWhichQueue);
VOID InsertNode(LINKED_LIST *pList, PVOID pNode);
KEY_NODE* FindKey(LINKED_LIST *ListHead,
LPTSTR lpKeyName);
LINKED_LIST *SelectSettingQueue(QUEUENUM dwWhichQueue);
static BOOL IsNecessaryToQuoteString(LPTSTR p);
BOOL DoesSectionHaveKeys( SECTION_NODE *pSection );
//----------------------------------------------------------------------------
//
// This section has the entry points for the wizard
//
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//
// Function: SettingQueue_AddSetting
//
// Purpose: Queues [section] key=value in internal structs.
//
// Arguments:
// LPTSTR lpSection - name of section in .ini
// LPTSTR lpKey - name of key in section
// LPTSTR lpValue - value of setting
// QUEUENUM dwWhichQueue - which settings queue
//
// Returns:
// BOOL - fails only because of no memory
//
// Notes:
//
// - A lpValue = "" is interpreted to mean 'Do not write this setting'.
//
// - A lpKey = "" creates a [SectionName] header with no settings.
//
// - If the setting existed in the original answer file, it's value
// is updated. An assert fires if the wizard tries to set the same
// key twice.
//
//----------------------------------------------------------------------------
BOOL SettingQueue_AddSetting(LPTSTR lpSection,
LPTSTR lpKey,
LPTSTR lpValue,
QUEUENUM dwWhichQueue)
{
SECTION_NODE *pSectionNode;
KEY_NODE *pKeyNode;
//
// You have to pass a section key and value. Section name cannot
// be empty.
//
Assert(lpSection != NULL);
Assert(lpKey != NULL);
Assert(lpValue != NULL);
Assert(lpSection[0]);
//
// See if a node for this section already exists. If not, create one.
//
pSectionNode = SettingQueue_AddSection(lpSection, dwWhichQueue);
if ( pSectionNode == NULL )
return FALSE;
//
// See if this key has already been set. If not, alloc a node and
// set all of its fields except for the lpValue.
//
// If the node already exist, free the lpValue to make room for
// the new value.
//
if ( lpKey[0] == _T('\0') ||
(pKeyNode = FindKey( &pSectionNode->key_list, lpKey) ) == NULL ) {
if ( (pKeyNode=malloc(sizeof(KEY_NODE))) == NULL )
return FALSE;
if ( (pKeyNode->lpKey = lstrdup(lpKey)) == NULL )
{
free(pKeyNode);
return FALSE;
}
InsertNode(&pSectionNode->key_list, pKeyNode);
} else {
#if DBG
//
// If the wizard has already set this key once, assert.
//
if ( pKeyNode->bSetOnce ) {
AssertMsg2(FALSE,
"Section \"%S\" Key \"%S\" has already been set",
lpSection, lpKey);
}
#endif
free(pKeyNode->lpValue);
}
#if DBG
//
// If this is going to an output queue, mark this setting as
// having already been set by the wizard.
//
// Note that when the input queue is copied to the output queue,
// the copy function preserves this setting.
//
pKeyNode->bSetOnce = ( (dwWhichQueue == SETTING_QUEUE_ANSWERS) |
(dwWhichQueue == SETTING_QUEUE_UDF) );
#endif
//
// Put the (possibly new) value in
//
if ( (pKeyNode->lpValue = lstrdup(lpValue)) == NULL )
return FALSE;
return TRUE;
}
//----------------------------------------------------------------------------
//
// Function: SettingQueue_AddSection
//
// Purpose: Adds a section (by name) to either the answer file setting
// queue, or the .udf queue.
//
// Returns: FALSE if out of memory
//
// Notes:
//
// - If the section is already on the given list, a pointer to it
// is returned. Else a new one is created and put at the end of
// the list.
//
//----------------------------------------------------------------------------
SECTION_NODE *
SettingQueue_AddSection(LPTSTR lpSection, QUEUENUM dwWhichQueue)
{
SECTION_NODE *pSectionNode;
LINKED_LIST *pList;
//
// If it already exists, return a pointer to it.
//
// If we're modifying a section (or any of it's settings) on one
// of the output queues, we must make sure that this section is not
// marked volatile anymore.
//
pSectionNode = FindQueuedSection(lpSection, dwWhichQueue);
if ( pSectionNode != NULL ) {
if ( dwWhichQueue == SETTING_QUEUE_ANSWERS ||
dwWhichQueue == SETTING_QUEUE_UDF ) {
pSectionNode->bVolatile = FALSE;
}
return pSectionNode;
}
//
// Create the new section node. They always begin not volatile.
// Callers use MarkVolatile to mark a volatile section on in input
// queue at answer file load time.
//
if ( (pSectionNode=malloc(sizeof(SECTION_NODE))) == NULL )
return FALSE;
if ( (pSectionNode->lpSection = lstrdup(lpSection)) == NULL )
{
free(pSectionNode);
return FALSE;
}
pSectionNode->bVolatile = FALSE;
memset(&pSectionNode->key_list, 0, sizeof(pSectionNode->key_list));
//
// Put this node at the tail of the correct list.
//
pList = SelectSettingQueue(dwWhichQueue);
if ( pList != NULL )
InsertNode(pList, pSectionNode);
return pSectionNode;
}
//----------------------------------------------------------------------------
//
// Function: SettingQueue_RemoveSection
//
// Purpose:
//
// Returns:
//
// Notes:
//
//----------------------------------------------------------------------------
VOID
SettingQueue_RemoveSection( LPTSTR lpSection, QUEUENUM dwWhichQueue )
{
LINKED_LIST *pList;
KEY_NODE *pKeyNode;
SECTION_NODE *pSectionNode;
SECTION_NODE *pPreviousSectionNode = NULL;
pList = SelectSettingQueue( dwWhichQueue );
if (pList == NULL)
return;
pSectionNode = (SECTION_NODE *) pList->Head;
//
// Iterate through all the sections.
//
while( pSectionNode ) {
KEY_NODE *pTempKeyNode;
//
// If this section matches the one we are looking for, delete it.
// Else advance to the next section.
//
if( lstrcmpi( pSectionNode->lpSection, lpSection ) == 0 ) {
for( pKeyNode = (KEY_NODE *) pSectionNode->key_list.Head; pKeyNode; ) {
free( pKeyNode->lpKey );
free( pKeyNode->lpValue );
pTempKeyNode = (KEY_NODE *) pKeyNode->Header.next;
free( pKeyNode );
pKeyNode = pTempKeyNode;
}
//
// Special case if we are at the head of the list
//
if( pPreviousSectionNode == NULL ) {
pList->Head = pSectionNode->Header.next;
free( pSectionNode->lpSection );
pSectionNode = (SECTION_NODE *) pList->Head;
}
else {
pPreviousSectionNode->Header.next = pSectionNode->Header.next;
free( pSectionNode->lpSection );
pSectionNode = (SECTION_NODE *) pPreviousSectionNode->Header.next;
}
}
else {
pPreviousSectionNode = pSectionNode;
pSectionNode = (SECTION_NODE *) pSectionNode->Header.next;
}
}
}
//----------------------------------------------------------------------------
//
// Function: SettingQueue_MarkVolatile
//
// Purpose: Marks or clears the Volatile flag for a section. Typically
// one marks volatile sections at load time on the "Orig" queues.
//
// Later, at save time, the volatile flag gets cleared if you
// ever call *_AddSetting() *_AddSection().
//
//----------------------------------------------------------------------------
VOID
SettingQueue_MarkVolatile(LPTSTR lpSection,
QUEUENUM dwWhichQueue)
{
SECTION_NODE *p = FindQueuedSection(lpSection, dwWhichQueue);
if ( p == NULL )
return;
p->bVolatile = TRUE;
}
//----------------------------------------------------------------------------
//
// Function: SettingQueue_Empty
//
// Purpose: This function emptys the queue of settings. Since the user
// can go BACK and re-save a file, it must be emptied before
// trying to save it again.
//
// Arguments: VOID
//
// Returns: VOID
//
//----------------------------------------------------------------------------
VOID SettingQueue_Empty(QUEUENUM dwWhichQueue)
{
LINKED_LIST *pList;
SECTION_NODE *p, *pn;
KEY_NODE *q, *qn;
//
// Point to the proper queue to empty and start at the head of it
//
pList = SelectSettingQueue(dwWhichQueue);
if (pList == NULL)
return;
p = (SECTION_NODE *) pList->Head;
//
// For each SECTION_NODE, walk down each KEY_NODE. Unlink and free all.
//
while ( p ) {
for ( q = (KEY_NODE *) p->key_list.Head; q; ) {
free(q->lpKey);
free(q->lpValue);
qn=(KEY_NODE *) q->Header.next;
free(q);
q=qn;
}
free(p->lpSection);
pn=(SECTION_NODE *) p->Header.next;
free(p);
p=pn;
}
//
// Zero out the head & tail pointers
//
pList->Head = NULL;
pList->Tail = NULL;
}
//----------------------------------------------------------------------------
//
// Function: SettingQueue_Flush
//
// Purpose: This function is called (by the wizard) once all the settings
// have been queued.
//
// Arguments:
// LPTSTR lpFileName - name of file to create/edit
// DWORD dwWhichQueue - which queue, answers file, .udf, ...
//
// Returns:
// BOOL - success
//
//----------------------------------------------------------------------------
BOOL
SettingQueue_Flush(LPTSTR lpFileName,
QUEUENUM dwWhichQueue)
{
LINKED_LIST *pList;
SECTION_NODE *pSection;
KEY_NODE *pKey;
TCHAR Buffer[MAX_INILINE_LEN];
FILE *fp;
INT BufferSize = sizeof(Buffer) / sizeof(TCHAR);
HRESULT hrPrintf;
//
// Point to the proper queue to flush
//
pList = SelectSettingQueue(dwWhichQueue);
if (pList == NULL)
return FALSE;
pSection = (SECTION_NODE *) pList->Head;
//
// Start writing the file
//
if( ( fp = My_fopen( lpFileName, _T("w") ) ) == NULL ) {
return( FALSE );
}
if( My_fputs( _T(";SetupMgrTag\n"), fp ) == _TEOF ) {
My_fclose( fp );
return( FALSE );
}
//
// For each section ...
//
for ( pSection = (SECTION_NODE *) pList->Head;
pSection;
pSection = (SECTION_NODE *) pSection->Header.next ) {
//
// We don't write out sections that are still marked volatile.
//
if ( pSection->bVolatile )
continue;
//
// Write the section name only if we will write keys below it
//
// ISSUE-2002/02/28-stelo- this causes problems because we want to write out
// some sections without keys, like:
//
//[NetServices]
// MS_SERVER=params.MS_SERVER
//
//[params.MS_SERVER]
//
// How can we get around this?
//
if( DoesSectionHaveKeys( pSection ) ) {
hrPrintf=StringCchPrintf(Buffer,
AS(Buffer),
_T("[%s]\n"),
pSection->lpSection);
}
else {
continue;
}
if( My_fputs( Buffer, fp ) == _TEOF ) {
My_fclose( fp );
return( FALSE );
}
//
// Write out each key=value
//
for ( pKey = (KEY_NODE *) pSection->key_list.Head;
pKey;
pKey = (KEY_NODE *) pKey->Header.next ) {
BOOL bQuoteKey = FALSE;
BOOL bQuoteValue = FALSE;
TCHAR *p;
//
// An empty value means to not write it
//
if ( pKey->lpValue[0] == _T('\0') )
continue;
//
// Double-quote the value if it has white-space and is not
// already quoted
//
bQuoteKey = IsNecessaryToQuoteString( pKey->lpKey );
bQuoteValue = IsNecessaryToQuoteString( pKey->lpValue );
//
// Put the key we want into Buffer
// ISSUE-2002/02/28-stelo- text might get truncated here, should we show a warning?
Buffer[0] = _T('\0');
if( pKey->lpKey[0] != _T('\0') ) {
if ( bQuoteKey ) {
hrPrintf=StringCchPrintf(Buffer,
AS(Buffer),
_T(" \"%s\"="),
pKey->lpKey);
}
else {
hrPrintf=StringCchPrintf(Buffer,
AS(Buffer),
_T(" %s="),
pKey->lpKey);
}
}
//
// Put the value we want into Buffer paying attention to
// whether we want quotes around the it or not.
//
if ( bQuoteValue ) {
lstrcatn( Buffer, _T("\""), BufferSize );
lstrcatn( Buffer, pKey->lpValue, BufferSize );
lstrcatn( Buffer, _T("\""), BufferSize );
lstrcatn( Buffer, _T("\n"), BufferSize );
}
else {
lstrcatn( Buffer, pKey->lpValue, BufferSize );
lstrcatn( Buffer, _T("\n"), BufferSize );
}
if( My_fputs( Buffer, fp ) == _TEOF ) {
My_fclose( fp );
return( FALSE );
}
}
//
// Write a blank line at the end of the section
//
hrPrintf=StringCchPrintf(Buffer, AS(Buffer), _T("\n"));
if( My_fputs( Buffer, fp ) == _TEOF ) {
My_fclose( fp );
return( FALSE );
}
}
My_fclose( fp );
return( TRUE );
}
//----------------------------------------------------------------------------
//
// Function: SettingQueue_Copy
//
// Purpose: Copies one settings queue to another. Used to copy the
// input queues to the output queues.
//
// Look at common\save.c
//
//----------------------------------------------------------------------------
VOID
SettingQueue_Copy(QUEUENUM dwFrom, QUEUENUM dwTo)
{
LINKED_LIST *pListFrom = SelectSettingQueue(dwFrom);
SECTION_NODE *p, *pSectionNode;
KEY_NODE *q;
#if DBG
KEY_NODE *pKeyNode;
#endif
if (pListFrom == NULL)
return;
for ( p = (SECTION_NODE *) pListFrom->Head;
p;
p = (SECTION_NODE *) p->Header.next ) {
//
// Add the section to the output queue
//
SettingQueue_AddSetting(p->lpSection,
_T(""),
_T(""),
dwTo);
pSectionNode = FindQueuedSection(p->lpSection, dwTo);
for ( q = (KEY_NODE *) p->key_list.Head;
q;
q = (KEY_NODE *) q->Header.next ) {
//
// Add the key=value
//
SettingQueue_AddSetting(p->lpSection,
q->lpKey,
q->lpValue,
dwTo);
#if DBG
//
// Retain the bSetOnce flag
//
pKeyNode = FindKey(&pSectionNode->key_list, q->lpKey);
if ( pKeyNode != NULL ) {
pKeyNode->bSetOnce = q->bSetOnce;
}
#endif
}
//
// Retain the bVolatile flag on the section node.
//
if ( pSectionNode != NULL ) {
pSectionNode->bVolatile = p->bVolatile;
}
}
}
//----------------------------------------------------------------------------
//
// Internal support routines
//
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//
// Function: DoesSectionHaveKeys
//
// Purpose: Determines if a section has keys to be written out or not
//
// Arguments:
// SECTION_NODE *pSection - the section to determine if it has keys or not
//
// Returns:
// TRUE - if this section contains keys
// FALSE - if this section does not contain keys
//
//----------------------------------------------------------------------------
BOOL
DoesSectionHaveKeys( SECTION_NODE *pSection ) {
KEY_NODE *pKey;
for ( pKey = (KEY_NODE *) pSection->key_list.Head;
pKey;
pKey = (KEY_NODE *) pKey->Header.next ) {
if ( pKey->lpValue[0] != _T('\0') ) {
return( TRUE );
}
}
return( FALSE );
}
//----------------------------------------------------------------------------
//
// Function: IsNecessaryToQuoteString
//
// Purpose: Determines if a string is already quoted and if it is not quoted,
// if a string has white space or not
//
// Arguments:
// LPTSTR p - the string to be scanned
//
// Returns:
// TRUE - if the string needs to be quoted
// FALSE - if the string does not need to be quoted
//
//----------------------------------------------------------------------------
static BOOL
IsNecessaryToQuoteString( LPTSTR p )
{
LPTSTR pCommaSearch;
//
// See if it is already quoted
// We only check if the first char is a quote because the last char may
// not be a quote. Example: ComputerType = "HAL Friendly Name", OEM
//
if( *p == _T('"') )
{
return( FALSE );
}
//
// If it contains a comma, then don't quote it except for the printer
// command that contains rundll32. This is kind of a hack.
// This prevents keys like:
//
// ComputerType = "HAL Friendly Name", OEM
//
// from being quoted.
//
if( ! _tcsstr( p, _T("rundll32") ) )
{
for( pCommaSearch = p; *pCommaSearch; pCommaSearch++ )
{
if( *pCommaSearch == _T(',') )
{
return( FALSE );
}
}
}
//
// Look for white space
//
for ( ; *p; p++ )
{
if( iswspace(*p) )
{
return( TRUE );
}
}
return( FALSE );
}
//----------------------------------------------------------------------------
//
// Function: FindQueuedSection (static)
//
// Purpose: Finds the SECTION_NODE on the global settings queue with
// the given name.
//
// Arguments:
// LPTSTR lpSection - name of section in .ini
//
// Returns:
// SECTION_NODE * or NULL if it does not exist
//
// Notes:
// - Searches are case insensitive
//
//----------------------------------------------------------------------------
static SECTION_NODE *FindQueuedSection(LPTSTR lpSection,
QUEUENUM dwWhichQueue)
{
SECTION_NODE *p;
LINKED_LIST *pList;
pList = SelectSettingQueue(dwWhichQueue);
if (pList == NULL)
return NULL;
p = (SECTION_NODE *) pList->Head;
if ( p == NULL )
return NULL;
do {
if ( _tcsicmp(p->lpSection, lpSection) == 0 )
break;
p = (SECTION_NODE *) p->Header.next;
} while ( p );
return p;
}
//----------------------------------------------------------------------------
//
// Function: InsertNode
//
// Purpose: Puts the given node at the tail of the given list. All nodes
// must begin with a NODE_HEADER.
//
// Returns: VOID
//
// Notes:
// - Allocates no memory, only links the node in.
//
//----------------------------------------------------------------------------
VOID InsertNode(LINKED_LIST *pList, PVOID pNode)
{
NODE_HEADER *pNode2 = (NODE_HEADER *) pNode;
//
// Put it at the tail
//
pNode2->next = NULL;
if ( pList->Tail )
pList->Tail->next = pNode2;
pList->Tail = pNode2;
//
// In case its the first one onto the list, fixup the head
//
if ( ! pList->Head )
pList->Head = pNode2;
}
//----------------------------------------------------------------------------
//
// Function: FindKey
//
// Purpose: Searches the given list of keynodes and finds one with the
// given name.
//
// Arguments:
// LPTSTR lpSection - name of section in .ini
//
// Returns:
// SECTION_NODE * or NULL if it does not exist
//
// Notes:
// - Searches are case insensitive
//
//----------------------------------------------------------------------------
KEY_NODE* FindKey(LINKED_LIST *ListHead,
LPTSTR lpKeyName)
{
KEY_NODE *p = (KEY_NODE *) ListHead->Head;
if ( p == NULL )
return NULL;
do {
if ( _tcsicmp(p->lpKey, lpKeyName) == 0 )
break;
p = (KEY_NODE *) p->Header.next;
} while ( p );
return p;
}
//----------------------------------------------------------------------------
//
// Function: SelectSettingQueue
//
// Purpose: Translates dwWhichQueue into a LINKED_LIST pointer.
//
// Returns: A pointer to one of the 5 settings queues we have.
//
//----------------------------------------------------------------------------
LINKED_LIST *SelectSettingQueue(QUEUENUM dwWhichQueue)
{
switch ( dwWhichQueue ) {
case SETTING_QUEUE_ANSWERS:
return &AnswerFileQueue;
case SETTING_QUEUE_UDF:
return &UdfQueue;
case SETTING_QUEUE_ORIG_ANSWERS:
return &OrigAnswerFileQueue;
case SETTING_QUEUE_ORIG_UDF:
return &OrigUdfQueue;
case SETTING_QUEUE_TXTSETUP_OEM:
return &TxtSetupOemQueue;
default:
AssertMsg(FALSE, "Invalid dwWhichQueue");
}
return NULL;
}