/*++ Copyright (c) Microsoft Corporation. All rights reserved. Module Name: filelog.c Abstract: Routines for logging files in copy logs. Author: Ted Miller (tedm) 14-Jun-1995 Revision History: --*/ #include "precomp.h" #pragma hdrstop // // Define name of system log file and various strings used // within it. // PCTSTR SystemLogFileName = TEXT("repair\\setup.log"); PCTSTR NtFileSectionName = TEXT("Files.WinNT"); // // Define structure used internally to represent a file log file. // typedef struct _SETUP_FILE_LOG { PCTSTR FileName; BOOL QueryOnly; BOOL SystemLog; } SETUP_FILE_LOG, *PSETUP_FILE_LOG; #ifdef UNICODE // // ANSI version // HSPFILELOG SetupInitializeFileLogA( IN PCSTR LogFileName, OPTIONAL IN DWORD Flags ) { PWSTR p; DWORD d; HSPFILELOG h; if(LogFileName) { d = pSetupCaptureAndConvertAnsiArg(LogFileName,&p); if(d != NO_ERROR) { SetLastError(d); return(INVALID_HANDLE_VALUE); } } else { p = NULL; } h = SetupInitializeFileLogW(p,Flags); d = GetLastError(); if(p) { MyFree(p); } SetLastError(d); return(h); } #else // // Unicode stub // HSPFILELOG SetupInitializeFileLogW( IN PCWSTR LogFileName, OPTIONAL IN DWORD Flags ) { UNREFERENCED_PARAMETER(LogFileName); UNREFERENCED_PARAMETER(Flags); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return(INVALID_HANDLE_VALUE); } #endif HSPFILELOG SetupInitializeFileLog( IN PCTSTR LogFileName, OPTIONAL IN DWORD Flags ) /*++ Routine Description: Initialize a file for logging or query. The caller may specify that he wishes to use the system log, which is where the system tracks which files are installed as part of Windows NT; or the caller may specify any other random file to be used as a log. If the user specifies the system log not for query only, the function fails unless the user is administrator. However this only guarantees security on the log when the system is installed on a drive with a filesystem that supports ACLs; the log is simply a file and anyone can access it unless setup can secure it via ACLs. Arguments: LogFileName - if specified, supplies the filename of the file to be used as the log file. Must be specified if Flags does not include SPFILELOG_SYSTEMLOG. Must not be specified if Flags includes SPFILELOG_SYSTEMLOG. Flags - supplies a combination of the following values: SPFILELOG_SYSTEMLOG - use the Windows NT system file log, which is used to track what files are installed as part of Windows NT. The user must be administrator to specify this option unless SPFILELOG_QUERYONLY is specified, and LogFileName must not be specified. May not be specified in combination with SPFILELOG_FORCENEW. SPFILELOG_FORCENEW - if the log file exists, it will be overwritten. If the log file exists and this flag is not specified then additional files are added to the existing log. May not be specified in combination with SPFILELOG_SYSTEMLOG. SPFILELOG_QUERYONLY - open the log file for querying only. The user Return Value: Handle to file log or INVALID_HANDLE_VALUE if the function fails; extended error info is available via GetLastError() in this case. --*/ { TCHAR SysLogFileName[MAX_PATH]; PCTSTR FileName; PSETUP_FILE_LOG FileLog; DWORD Err; HANDLE hFile; // // Validate args. // Err = ERROR_INVALID_PARAMETER; if(Flags & SPFILELOG_SYSTEMLOG) { if((Flags & SPFILELOG_FORCENEW) || LogFileName) { goto clean0; } // // User must be administrator to gain write access to system log. // if(!(Flags & SPFILELOG_QUERYONLY) && !pSetupIsUserAdmin()) { Err = ERROR_ACCESS_DENIED; goto clean0; } // // uses actual windows directory instead of hydra remapped // lstrcpyn(SysLogFileName,WindowsDirectory,MAX_PATH); pSetupConcatenatePaths(SysLogFileName,SystemLogFileName,MAX_PATH,NULL); FileName = SysLogFileName; } else { if(LogFileName) { if(!lstrcpyn(SysLogFileName,LogFileName,MAX_PATH)) { // // lstrcpyn faulted, LogFileName must be bad // Err = ERROR_INVALID_PARAMETER; goto clean0; } FileName = SysLogFileName; } else { goto clean0; } } // // Allocate a log file structure. // Err = ERROR_NOT_ENOUGH_MEMORY; if(FileLog = MyMalloc(sizeof(SETUP_FILE_LOG))) { FileLog->FileName = DuplicateString(FileName); if(!FileLog->FileName) { goto clean1; } } else { goto clean0; } FileLog->QueryOnly = ((Flags & SPFILELOG_QUERYONLY) != 0); FileLog->SystemLog = ((Flags & SPFILELOG_SYSTEMLOG) != 0); // // See if the file exists. // if(FileExists(FileName,NULL)) { // // If it's the system log, take ownership of the file. // if(FileLog->SystemLog) { Err = TakeOwnershipOfFile(FileName); if(Err != NO_ERROR) { goto clean2; } } // // Set attribute to normal. This ensures we can delete/open/create the file // as appropriate below. // if(!SetFileAttributes(FileName,FILE_ATTRIBUTE_NORMAL)) { Err = GetLastError(); goto clean2; } // // Delete the file now if the caller specified the force_new flag. // if((Flags & SPFILELOG_FORCENEW) && !DeleteFile(FileName)) { Err = GetLastError(); goto clean2; } } // // Make sure we can open/create the file by attempting to do that now. // hFile = CreateFile( FileName, GENERIC_READ | (FileLog->QueryOnly ? 0 : GENERIC_WRITE), FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, // Open if exists, create if not FILE_ATTRIBUTE_NORMAL, NULL ); if(hFile == INVALID_HANDLE_VALUE) { Err = GetLastError(); goto clean2; } CloseHandle(hFile); return((HSPFILELOG)FileLog); clean2: MyFree(FileLog->FileName); clean1: MyFree(FileLog); clean0: SetLastError(Err); return(INVALID_HANDLE_VALUE); } BOOL SetupTerminateFileLog( IN HSPFILELOG FileLogHandle ) /*++ Routine Description: Releases resources associated with a file log. Arguments: FileLogHandle - supplies the handle to the file log, as returned by SetupInitializeLogFile. Return Value: Boolean value indicating outcome. If FALSE, the caller can use GetLastError() to get extended error info. --*/ { PSETUP_FILE_LOG FileLog; DWORD Err; FileLog = (PSETUP_FILE_LOG)FileLogHandle; Err = NO_ERROR; try { MyFree(FileLog->FileName); MyFree(FileLog); } except(EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_PARAMETER; } SetLastError(Err); return(Err == NO_ERROR); } #ifdef UNICODE // // ANSI version // BOOL SetupLogFileA( IN HSPFILELOG FileLogHandle, IN PCSTR LogSectionName, OPTIONAL IN PCSTR SourceFilename, IN PCSTR TargetFilename, IN DWORD Checksum, OPTIONAL IN PCSTR DiskTagfile, OPTIONAL IN PCSTR DiskDescription, OPTIONAL IN PCSTR OtherInfo, OPTIONAL IN DWORD Flags ) { PWSTR logsectionname = NULL; PWSTR sourcefilename = NULL; PWSTR targetfilename = NULL; PWSTR disktagfile = NULL; PWSTR diskdescription = NULL; PWSTR otherinfo = NULL; DWORD d; BOOL b; if(LogSectionName) { d = pSetupCaptureAndConvertAnsiArg(LogSectionName,&logsectionname); } else { d = NO_ERROR; } if(d == NO_ERROR) { d = pSetupCaptureAndConvertAnsiArg(SourceFilename,&sourcefilename); } if(d == NO_ERROR) { d = pSetupCaptureAndConvertAnsiArg(TargetFilename,&targetfilename); } if((d == NO_ERROR) && DiskTagfile) { d = pSetupCaptureAndConvertAnsiArg(DiskTagfile,&disktagfile); } if((d == NO_ERROR) && DiskDescription) { d = pSetupCaptureAndConvertAnsiArg(DiskDescription,&diskdescription); } if((d == NO_ERROR) && OtherInfo) { d = pSetupCaptureAndConvertAnsiArg(OtherInfo,&otherinfo); } if(d == NO_ERROR) { b = SetupLogFileW( FileLogHandle, logsectionname, sourcefilename, targetfilename, Checksum, disktagfile, diskdescription, otherinfo, Flags ); d = GetLastError(); } else { b = FALSE; } if(logsectionname) { MyFree(logsectionname); } if(sourcefilename) { MyFree(sourcefilename); } if(targetfilename) { MyFree(targetfilename); } if(disktagfile) { MyFree(disktagfile); } if(diskdescription) { MyFree(diskdescription); } if(otherinfo) { MyFree(otherinfo); } SetLastError(d); return(b); } #else // // Unicode stub // BOOL SetupLogFileW( IN HSPFILELOG FileLogHandle, IN PCWSTR LogSectionName, OPTIONAL IN PCWSTR SourceFilename, IN PCWSTR TargetFilename, IN DWORD Checksum, OPTIONAL IN PCWSTR DiskTagfile, OPTIONAL IN PCWSTR DiskDescription, OPTIONAL IN PCWSTR OtherInfo, OPTIONAL IN DWORD Flags ) { UNREFERENCED_PARAMETER(FileLogHandle); UNREFERENCED_PARAMETER(LogSectionName); UNREFERENCED_PARAMETER(SourceFilename); UNREFERENCED_PARAMETER(TargetFilename); UNREFERENCED_PARAMETER(Checksum); UNREFERENCED_PARAMETER(DiskTagfile); UNREFERENCED_PARAMETER(DiskDescription); UNREFERENCED_PARAMETER(OtherInfo); UNREFERENCED_PARAMETER(Flags); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return(FALSE); } #endif BOOL SetupLogFile( IN HSPFILELOG FileLogHandle, IN PCTSTR LogSectionName, OPTIONAL IN PCTSTR SourceFilename, IN PCTSTR TargetFilename, IN DWORD Checksum, OPTIONAL IN PCTSTR DiskTagfile, OPTIONAL IN PCTSTR DiskDescription, OPTIONAL IN PCTSTR OtherInfo, OPTIONAL IN DWORD Flags ) /*++ Routine Description: Logs a file into a file log. Arguments: FileLogHandle - supplies the handle to the file log, as returned by SetupInitializeLogFile(). The caller must not have passed SPFILELOG_QUERYONLY when the log file was opened/initialized. LogSectionName - required if SPFILELOG_SYSTEMLOG was not passed when the file log was opened/initialized; optional otherwise. Supplies the name for a logical grouping of files within the log. SourceFilename - supplies the name of the file as it exists on the source media from which it was installed. This name should be in whatever format is meaningful to the caller. TargetFilename - supplies the name of the file as it exists on the Target. This name should be in whatever format is meaningful to the caller. Checksum - supplies a 32-bit checksum value. Required for the system log. DiskTagfile - Gives the tagfile for the media from which the file was installed. Required for the system log if SPFILELOG_OEMFILE is specified. Ignored for the system log if SPFILELOG_OEMFILE is not specified. DiskDescription - Gives the human-readable description for the media from which the file was installed. Required for the system log if SPFILELOG_OEMFILE is specified. Ignored for the system log if SPFILELOG_OEMFILE is not specified. OtherInfo - supplies additional information to be associated with the file. Flags - may supply SPFILELOG_OEMFILE, which is meaningful only for the system log and indicates that the file is not an MS-supplied file. Can be used to convert an existing file's entry such as when an oem overwrites an MS-supplied system file. Return Value: Boolean value indicating outcome. If FALSE, the caller can use GetLastError() to get extended error info. --*/ { PSETUP_FILE_LOG FileLog; DWORD Err; BOOL b; TCHAR LineToWrite[512]; TCHAR sourceFilename[MAX_PATH]; PTSTR p,Directory; FileLog = (PSETUP_FILE_LOG)FileLogHandle; try { // // Validate params. Handle must be for non-queryonly. // If for the system log and oem file is specified, // caller must have passed disk tagfile and description. // There's really no way to validate the checksum because // 0 is a perfectly valid one. // If not the system log, caller must have passed a section name. // if(FileLog->QueryOnly || ( FileLog->SystemLog && (Flags & SPFILELOG_OEMFILE) && (!DiskTagfile || !DiskDescription)) || (!FileLog->SystemLog && !LogSectionName)) { Err = ERROR_INVALID_PARAMETER; } else { // // Use default section if not specified. // if(!LogSectionName) { MYASSERT(FileLog->SystemLog); LogSectionName = NtFileSectionName; } // // IF THIS LOGIC IS CHANGED BE SURE TO CHANGE // SetupQueryFileLog() AS WELL! // // Split up the source filename into filename and // directory if appropriate. // lstrcpyn(sourceFilename,SourceFilename,MAX_PATH); if(FileLog->SystemLog && (Flags & SPFILELOG_OEMFILE)) { if(p = _tcsrchr(sourceFilename,TEXT('\\'))) { *p++ = 0; Directory = p; } else { Directory = TEXT("\\"); } } else { Directory = TEXT(""); } _sntprintf( LineToWrite, sizeof(LineToWrite)/sizeof(LineToWrite[0]), TEXT("%s,%x,%s,%s,\"%s\""), sourceFilename, Checksum, Directory, DiskTagfile ? DiskTagfile : TEXT(""), DiskDescription ? DiskDescription : TEXT("") ); b = WritePrivateProfileString( LogSectionName, TargetFilename, LineToWrite, FileLog->FileName ); Err = b ? NO_ERROR : GetLastError(); } } except(EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_PARAMETER; } SetLastError(Err); return(Err == NO_ERROR); } #ifdef UNICODE // // ANSI version // BOOL SetupRemoveFileLogEntryA( IN HSPFILELOG FileLogHandle, IN PCSTR LogSectionName, OPTIONAL IN PCSTR TargetFilename OPTIONAL ) { PWSTR logsectionname,targetfilename; DWORD d; BOOL b; if(LogSectionName) { d = pSetupCaptureAndConvertAnsiArg(LogSectionName,&logsectionname); if(d != NO_ERROR) { SetLastError(d); return(FALSE); } } else { logsectionname = NULL; } if(TargetFilename) { d = pSetupCaptureAndConvertAnsiArg(TargetFilename,&targetfilename); if(d != NO_ERROR) { if(logsectionname) { MyFree(logsectionname); } SetLastError(d); return(FALSE); } } else { targetfilename = NULL; } b = SetupRemoveFileLogEntryW(FileLogHandle,logsectionname,targetfilename); d = GetLastError(); if(logsectionname) { MyFree(logsectionname); } if(targetfilename) { MyFree(targetfilename); } SetLastError(d); return(b); } #else // // Unicode stub // BOOL SetupRemoveFileLogEntryW( IN HSPFILELOG FileLogHandle, IN PCWSTR LogSectionName, OPTIONAL IN PCWSTR TargetFilename OPTIONAL ) { UNREFERENCED_PARAMETER(FileLogHandle); UNREFERENCED_PARAMETER(LogSectionName); UNREFERENCED_PARAMETER(TargetFilename); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return(FALSE); } #endif BOOL SetupRemoveFileLogEntry( IN HSPFILELOG FileLogHandle, IN PCTSTR LogSectionName, OPTIONAL IN PCTSTR TargetFilename OPTIONAL ) /*++ Routine Description: Removes an entry or section from a file log. Arguments: FileLogHandle - supplies the handle to the file log, as returned by SetupInitializeLogFile(). The caller must not have passed SPFILELOG_QUERYONLY when the log file was opened/initialized. LogSectionName - Supplies the name for a logical grouping of files within the log. Required for non-system logs; optional for the system log. TargetFilename - supplies the name of the file as it exists on the Target. This name should be in whatever format is meaningful to the caller. If not specified, the entire section specified by LogSectionName is removed. Removing the main section for NT files is not allowed. Return Value: Boolean value indicating outcome. If FALSE, the caller can use GetLastError() to get extended error info. --*/ { DWORD Err; PSETUP_FILE_LOG FileLog; BOOL b; FileLog = (PSETUP_FILE_LOG)FileLogHandle; try { Err = NO_ERROR; if(FileLog->QueryOnly) { Err = ERROR_INVALID_PARAMETER; } else { if(!LogSectionName) { if(FileLog->SystemLog) { LogSectionName = NtFileSectionName; } else { Err = ERROR_INVALID_PARAMETER; } } // // Diallow removing the main nt files section. // if((Err == NO_ERROR) && FileLog->SystemLog && !TargetFilename && !lstrcmpi(LogSectionName,NtFileSectionName)) { Err = ERROR_INVALID_PARAMETER; } } if(Err == NO_ERROR) { b = WritePrivateProfileString(LogSectionName,TargetFilename,NULL,FileLog->FileName); Err = b ? NO_ERROR : GetLastError(); } } except(EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_PARAMETER; } return(Err == NO_ERROR); } #ifdef UNICODE // // ANSI version // BOOL SetupQueryFileLogA( IN HSPFILELOG FileLogHandle, IN PCSTR LogSectionName, OPTIONAL IN PCSTR TargetFilename, IN SetupFileLogInfo DesiredInfo, OUT PSTR DataOut, OPTIONAL IN DWORD ReturnBufferSize, OUT PDWORD RequiredSize OPTIONAL ) { PWSTR logsectionname; PWSTR targetfilename; PWSTR unicodeBuffer = NULL; DWORD unicodeSize = 2048; PSTR ansidata; DWORD requiredsize; DWORD d; BOOL b; d = pSetupCaptureAndConvertAnsiArg(TargetFilename,&targetfilename); if(d != NO_ERROR) { SetLastError(d); return(FALSE); } if(LogSectionName) { d = pSetupCaptureAndConvertAnsiArg(LogSectionName,&logsectionname); if(d != NO_ERROR) { MyFree(targetfilename); SetLastError(d); return(FALSE); } } else { logsectionname = NULL; } unicodeBuffer = MyMalloc(unicodeSize*sizeof(WCHAR)); if(!unicodeBuffer) { if(targetfilename) { MyFree(targetfilename); } if(logsectionname) { MyFree(logsectionname); } SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } b = SetupQueryFileLogW( FileLogHandle, logsectionname, targetfilename, DesiredInfo, unicodeBuffer, unicodeSize, &requiredsize ); d = GetLastError(); if(b) { d = NO_ERROR; if(ansidata = pSetupUnicodeToAnsi(unicodeBuffer)) { requiredsize = lstrlenA(ansidata)+1; if(RequiredSize) { try { *RequiredSize = requiredsize; } except(EXCEPTION_EXECUTE_HANDLER) { b = FALSE; d = ERROR_INVALID_PARAMETER; } } if(b && DataOut) { if(ReturnBufferSize >= requiredsize) { if(!lstrcpyA(DataOut,ansidata)) { // // lstrcpy faulted, ReturnBuffer must be invalid // d = ERROR_INVALID_PARAMETER; b = FALSE; } } else { d = ERROR_INSUFFICIENT_BUFFER; b = FALSE; } } MyFree(ansidata); } else { b = FALSE; d = ERROR_NOT_ENOUGH_MEMORY; } } MyFree(targetfilename); if(logsectionname) { MyFree(logsectionname); } if(unicodeBuffer) { MyFree(unicodeBuffer); } SetLastError(d); return(b); } #else // // Unicode stub // BOOL SetupQueryFileLogW( IN HSPFILELOG FileLogHandle, IN PCWSTR LogSectionName, OPTIONAL IN PCWSTR TargetFilename, IN SetupFileLogInfo DesiredInfo, OUT PWSTR DataOut, OPTIONAL IN DWORD ReturnBufferSize, OUT PDWORD RequiredSize OPTIONAL ) { UNREFERENCED_PARAMETER(FileLogHandle); UNREFERENCED_PARAMETER(LogSectionName); UNREFERENCED_PARAMETER(TargetFilename); UNREFERENCED_PARAMETER(DesiredInfo); UNREFERENCED_PARAMETER(DataOut); UNREFERENCED_PARAMETER(ReturnBufferSize); UNREFERENCED_PARAMETER(RequiredSize); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return(FALSE); } #endif BOOL SetupQueryFileLog( IN HSPFILELOG FileLogHandle, IN PCTSTR LogSectionName, OPTIONAL IN PCTSTR TargetFilename, IN SetupFileLogInfo DesiredInfo, OUT PTSTR DataOut, OPTIONAL IN DWORD ReturnBufferSize, OUT PDWORD RequiredSize OPTIONAL ) /*++ Routine Description: Returns information from a setup file log. Arguments: FileLogHandle - supplies handle to open file log, as returned by SetupInitializeFileLog(). LogSectionName - required for non-system logs; if not specified for the system log a default is supplied. Supplies the name for a logical grouping within the log that is meaningful to the caller. TargetFilename - supplies name of file for which log information is desired. DesiredInfo - supplies an ordinal indicating what information is desired about the file. DataOut - If specified, points to a buffer that receives the requested information for the file. Note that not all info is provided for every file; an error is not returned if an entry for the file exists in the log but is empty. ReturnBufferSize - supplies size of the buffer (in chars) pointed to by DataOut. If the buffer is too small and DataOut is specified, no data is stored and the function returns FALSE. If DataOut is not specified this value is ignored. RequiredSize - receives the number of characters (including the terminating nul) required to hold the result. Return Value: Boolean value indicating result. If FALSE, extended error info is available from GetLastError(). --*/ { DWORD Err; PSETUP_FILE_LOG FileLog; BOOL b; TCHAR ProfileValue[2*MAX_PATH]; INT n; DWORD d; PTCHAR Field,End,Info; UINT InfoLength; BOOL Quoted; FileLog = (PSETUP_FILE_LOG)FileLogHandle; try { // // Validate arguments. // Section name must be supplied for non-system log. // if((!FileLog->SystemLog && !LogSectionName) || (DesiredInfo >= SetupFileLogMax) || !TargetFilename) { Err = ERROR_INVALID_PARAMETER; } else { if(!LogSectionName) { MYASSERT(FileLog->SystemLog); LogSectionName = NtFileSectionName; } // // Query the log file via profile API. // d = GetPrivateProfileString( LogSectionName, TargetFilename, TEXT(""), ProfileValue, sizeof(ProfileValue)/sizeof(ProfileValue[0]), FileLog->FileName ); if(d) { // // We want to retreive the Nth item in the value we just // retreived, where N is based on what the caller wants. // This routine assumes that the SetupFileLogInfo enum is // in the same order as items appear in a line in the log! // Field = ProfileValue; n = 0; nextfield: // // Find the end of the current field, which is // the first comma, or the end of the value. // Skip leading spaces. // while(*Field == TEXT(' ')) { Field++; } End = Field; Quoted = FALSE; while(*End) { if(*End == TEXT('\"')) { Quoted = !Quoted; } else { if(!Quoted && *End == TEXT(',')) { // // Got the end of the field. // break; } } End++; } // // At this point, Field points to the start of the field // and End points at the character that terminated it. // if(n == DesiredInfo) { Info = Field; InfoLength = (UINT)(End-Field); // // Compensate for trailing space. // while (*--End == TEXT(' ')) { InfoLength--; } } else { // // Skip trailing spaces and the comma, if any. // while(*End == ' ') { End++; } if(*End == ',') { // // More fields exist. // Field = End+1; n++; goto nextfield; } else { // // Item doesn't exist. // Info = TEXT(""); InfoLength = 0; } } if(RequiredSize) { *RequiredSize = InfoLength+1; } Err = NO_ERROR; if(DataOut) { if(ReturnBufferSize > InfoLength) { lstrcpyn(DataOut,Info,InfoLength+1); } else { Err = ERROR_INSUFFICIENT_BUFFER; } } } else { Err = ERROR_FILE_NOT_FOUND; } } } except(EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_PARAMETER; } SetLastError(Err); return(Err == NO_ERROR); }