/*++ Copyright (c) 2001 Microsoft Corporation Module Name: ntsetup\winnt32\dll\sxs.h Abstract: SideBySide support in the winnt32 phase of ntsetup. Author: Jay Krell (JayKrell) March 2001 Revision History: Environment: winnt32 -- Win9x ANSI (down to Win95gold) or NT Unicode libcmt statically linked in, _tcs* ok --*/ #include "precomp.h" #pragma hdrstop #include #include #include #include "tchar.h" #include "sxs.h" #include "msg.h" #define NUMBER_OF(x) (sizeof(x)/sizeof((x)[0])) #define CHECK_FOR_MINIMUM_ASSEMBLIES 0 #define CHECK_FOR_OBSOLETE_ASSEMBLIES 1 #define EMPTY_LEAF_DIRECTORIES_ARE_OK 1 const static TCHAR rgchPathSeperators[] = TEXT("\\/"); #define PRESERVE_LAST_ERROR(code) \ do { DWORD PreserveLastError = Success ? NO_ERROR : GetLastError(); \ do { code ; } while(0); \ if (!Success) SetLastError(PreserveLastError); \ } while(0) #define StringLength _tcslen #define StringCopy _tcscpy #define StringAppend _tcscat #define FindLastChar _tcsrchr BOOL SxspIsPathSeperator( TCHAR ch ) { return (_tcschr(rgchPathSeperators, ch) != NULL); } VOID __cdecl SxspDebugOut( LPCTSTR Format, ... ) { // // DebugLog directly doesn't quite work out, because // it wants %1 formatting, where we have GetLastError(). // Unless we duplicate all of our messages.. // TCHAR Buffer[2000]; va_list args; const BOOL Success = TRUE; // PRESERVE_LAST_ERROR Buffer[0] = 0; va_start(args, Format); #pragma prefast(suppress:53, Buffer is always nul-terminated) _vsntprintf(Buffer, NUMBER_OF(Buffer), Format, args); va_end(args); if (Buffer[0] != 0) { LPTSTR End; SIZE_T Length; Buffer[NUMBER_OF(Buffer) - 1] = 0; PRESERVE_LAST_ERROR(OutputDebugString(Buffer)); Length = StringLength(Buffer); End = Buffer + Length - 1; while (*End == ' ' || *End == '\t' || *End == '\n' || *End == '\r') *End-- = 0; DebugLog(Winnt32LogError, TEXT("%1"), 0, Buffer); } } VOID SxspRemoveTrailingPathSeperators( LPTSTR s ) { if (s != NULL && s[0] != 0) { LPTSTR t; // // This is inefficient, in order to be mbcs correct, // but there aren't likely to be more than one or two. // while ((t = _tcsrchr(s, rgchPathSeperators[0])) != NULL && *(t + 1) == 0) { *t = 0; } } } VOID SxspGetPathBaseName( LPCTSTR Path, LPTSTR Base ) { LPCTSTR Dot = FindLastChar(Path, '.'); LPCTSTR Slash = FindLastChar(Path, rgchPathSeperators[0]); // // beware \foo.txt\bar // beware \bar // beware bar // beware .bar // beware \.bar // *Base = 0; if (Slash == NULL) Slash = Path; else Slash += 1; if (Dot == NULL || Dot < Slash) Dot = Path + StringLength(Path); CopyMemory(Base, Slash, (Dot - Slash) * sizeof(*Base)); Base[Dot - Slash] = 0; } BOOL SxspIsDotOrDotDot( PCTSTR s ) { return (s[0] == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))); } const static LPCTSTR DotManifestExtensions[] = { TEXT(".Man"), TEXT(".Dll"), TEXT(".Manifest"), TEXT(".Policy") }; const static LPCTSTR DotCatalogExtensions[] = { TEXT(".Cat") }; BOOL SxspGetSameNamedFileWithExtensionFromList( PSXS_CHECK_LOCAL_SOURCE Context, LPCTSTR Directory, CONST LPCTSTR Extensions[], SIZE_T NumberOfExtensions, LPTSTR File ) { const static TCHAR T_FUNCTION[] = TEXT("SxspGetSameNamedFileWithExtensionFromList"); LPTSTR FileEnd = NULL; PTSTR Base = NULL; DWORD FileAttributes = 0; SIZE_T i = 0; BOOL Success = FALSE; File[0] = 0; StringCopy(File, Directory); SxspRemoveTrailingPathSeperators(File); Base = File + StringLength(File) + 1; SxspGetPathBaseName(Directory, Base); Base[-1] = rgchPathSeperators[0]; FileEnd = Base + StringLength(Base); for (i = 0 ; i != NumberOfExtensions ; ++i) { StringCopy(FileEnd, Extensions[i]); FileAttributes = GetFileAttributes(File); if (FileAttributes != INVALID_FILE_ATTRIBUTES) { if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { return TRUE; } } else { const DWORD LastError = GetLastError(); if (LastError != ERROR_FILE_NOT_FOUND && LastError != ERROR_PATH_NOT_FOUND ) { SxspDebugOut( TEXT("SXS: %s(%s):GetFileAttributes(%s):%lu\n"), T_FUNCTION, Directory, File, LastError ); MessageBoxFromMessage( Context->ParentWindow, LastError, TRUE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL ); File[0] = 0; Success = FALSE; goto Exit; } } } File[0] = 0; Success = TRUE; Exit: return Success; } BOOL SxspCheckFile( PSXS_CHECK_LOCAL_SOURCE Context, LPCTSTR File ) { BYTE Buffer[512]; static BYTE Zeroes[sizeof(Buffer)]; HANDLE FileHandle = INVALID_HANDLE_VALUE; DWORD BytesRead = 0; BOOL Success = FALSE; FileHandle = CreateFile( File, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (FileHandle == INVALID_HANDLE_VALUE) { CONST DWORD LastError = GetLastError(); SxspDebugOut(TEXT("SXS: unable to open file %s, error %lu\n"), File, LastError); MessageBoxFromMessageAndSystemError( Context->ParentWindow, MSG_SXS_ERROR_FILE_OPEN_FAILED, GetLastError(), AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, File ); Success = FALSE; goto Exit; } if (!ReadFile(FileHandle, Buffer, sizeof(Buffer), &BytesRead, NULL)) { CONST DWORD LastError = GetLastError(); SxspDebugOut(TEXT("SXS: ReadFile(%s) failed %lu\n"), File, LastError); MessageBoxFromMessageAndSystemError( Context->ParentWindow, MSG_SXS_ERROR_FILE_READ_FAILED, LastError, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, File ); Success = FALSE; goto Exit; } if (memcmp(Buffer, Zeroes, BytesRead) == 0) { SxspDebugOut(TEXT("SXS: File %s is all zeroes\n"), File); MessageBoxFromMessage( Context->ParentWindow, MSG_SXS_ERROR_FILE_IS_ALL_ZEROES, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, File ); Success = FALSE; goto Exit; } Success = TRUE; Exit: if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle); return Success; } BOOL SxspCheckLeafDirectory( PSXS_CHECK_LOCAL_SOURCE Context, LPCTSTR Directory ) { TCHAR File[MAX_PATH]; BOOL Success = TRUE; // NOTE backwardness const static struct { const LPCTSTR* Extensions; SIZE_T NumberOfExtensions; ULONG Error; } x[] = { { DotManifestExtensions, NUMBER_OF(DotManifestExtensions), MSG_SXS_ERROR_DIRECTORY_IS_MISSING_MANIFEST }, { DotCatalogExtensions, NUMBER_OF(DotCatalogExtensions), MSG_SXS_ERROR_DIRECTORY_IS_MISSING_CATALOG } }; SIZE_T i; for (i = 0 ; i != NUMBER_OF(x) ; ++i) { if (SxspGetSameNamedFileWithExtensionFromList(Context, Directory, x[i].Extensions, x[i].NumberOfExtensions, File)) { if (File[0] == 0) { TCHAR Base[MAX_PATH]; SxspGetPathBaseName(Directory, Base); SxspDebugOut(TEXT("SXS: Missing manifest or catalog in %s\n"), Directory); MessageBoxFromMessage( Context->ParentWindow, x[i].Error, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, Directory, Base ); Success = FALSE; //goto Exit; // keep looping, to possibly report more errors (manifest and catalog) } else { if (!SxspCheckFile(Context, File)) Success = FALSE; // keep looping, to possibly report more errors } } } // NOTE don't set Success = TRUE here. //Exit: return Success; } BOOL SxspFindAndCheckLeaves( PSXS_CHECK_LOCAL_SOURCE Context, LPTSTR Directory, SIZE_T DirectoryLength, LPWIN32_FIND_DATA FindData ) { const static TCHAR T_FUNCTION[] = TEXT("SxspFindAndCheckLeaves"); HANDLE FindHandle = INVALID_HANDLE_VALUE; BOOL ChildrenDirectories = FALSE; BOOL ChildrenFiles = FALSE; BOOL Success = TRUE; // // first enumerate looking for any directories // recurse on each one // if none found, check it as a leaf // ConcatenatePaths(Directory, TEXT("*"), MAX_PATH); FindHandle = FindFirstFile(Directory, FindData); if (FindHandle == INVALID_HANDLE_VALUE) { CONST DWORD LastError = GetLastError(); // // we already did a successful GetFileAttributes on this and // found it was a directory, so no error is expected here // SxspDebugOut( TEXT("SXS: %s(%s),FindFirstFile:%d\n"), T_FUNCTION, Directory, LastError ); MessageBoxFromMessage( Context->ParentWindow, LastError, TRUE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL ); Success = FALSE; goto Exit; } else { do { if (SxspIsDotOrDotDot(FindData->cFileName)) continue; if (FindData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { ChildrenDirectories = TRUE; Directory[DirectoryLength] = 0; ConcatenatePaths(Directory, FindData->cFileName, MAX_PATH); if (!SxspFindAndCheckLeaves( Context, Directory, StringLength(Directory), FindData )) { Success = FALSE; // keep looping, in order to possibly report more errors } } else { ChildrenFiles = TRUE; } } while (FindNextFile(FindHandle, FindData)); FindClose(FindHandle); } if (!ChildrenDirectories #if EMPTY_LEAF_DIRECTORIES_ARE_OK /* currently yes */ && ChildrenFiles #endif ) { Directory[DirectoryLength] = 0; if (!SxspCheckLeafDirectory(Context, Directory)) Success = FALSE; } #if !EMPTY_LEAF_DIRECTORIES_ARE_OK /* currently no */ if (!ChildrenDirectories && !ChildrenFiles) { // report an error } #endif // NOTE do not set Success = TRUE here Exit: return Success; } #if CHECK_FOR_MINIMUM_ASSEMBLIES /* 0 */ // // This data is very specific to Windows 5.1. // // All of these should be under all roots, assuming // corporate deployers do not add roots to dosnet.inf. // const static LPCTSTR MinimumAssemblies[] = { TEXT("6000\\Msft\\Windows\\Common\\Controls"), TEXT("1000\\Msft\\Windows\\GdiPlus"), TEXT("5100\\Msft\\Windows\\System\\Default") }; #endif #if CHECK_FOR_OBSOLETE_ASSEMBLIES // // This data is specific to Windows 5.1. // // None of these should be under any root, assuming // corporate deployers don't use these names. // // People internally end up with obsolete assemblies because they // copy newer drops on top of older drops, without deleting what is // no longer in the drop. // const static LPCTSTR ObsoleteAssemblies[] = { // This assembly was reversioned very early in its life, from 1.0.0.0 to 5.1.0.0. TEXT("1000\\Msft\\Windows\\System\\Default") }; #endif BOOL SxspCheckRoot( PSXS_CHECK_LOCAL_SOURCE Context, LPCTSTR Root ) { const static TCHAR T_FUNCTION[] = TEXT("SxspCheckRoot"); DWORD FileAttributes = 0; DWORD LastError = 0; HANDLE FindHandle = INVALID_HANDLE_VALUE; WIN32_FIND_DATA FindData; TCHAR RootStar[MAX_PATH]; SIZE_T RootLength = 0; BOOL Empty = TRUE; BOOL Success = TRUE; // NOTE the backwardness SIZE_T i = 0; StringCopy(RootStar, Root); RootLength = StringLength(Root); // // check that the root exists // FileAttributes = GetFileAttributes(Root); if (FileAttributes == INVALID_FILE_ATTRIBUTES) { Success = FALSE; LastError = GetLastError(); // // If the root is not found, then this isn't an error - there's just no 'asms' // to install (ie: they all got mushed into a cab) // if ((LastError == ERROR_FILE_NOT_FOUND) || (LastError == ERROR_PATH_NOT_FOUND)) { LastError = ERROR_SUCCESS; SxspDebugOut(TEXT("SXS: (%s) - Directory %s does not exist, assuming no asms present.\n"), T_FUNCTION, Root); Success = TRUE; goto Exit; } SxspDebugOut( TEXT("SXS: %s(%s),GetFileAttributes:%d\n"), T_FUNCTION, Root, LastError ); //if (LastError == ERROR_FILE_NOT_FOUND || LastError == ERROR_PATH_NOT_FOUND) { MessageBoxFromMessageAndSystemError( Context->ParentWindow, MSG_SXS_ERROR_REQUIRED_DIRECTORY_MISSING, LastError, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, Root ); goto Exit; // abort, otherwise we get many cascades, guaranteed } //else { /* MessageBoxFromMessage( Context->ParentWindow, LastError, TRUE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL ); goto Exit; */ } } // // check that the root is a directory // if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { SxspDebugOut(TEXT("SXS: %s is file instead of directory\n"), Root); MessageBoxFromMessage( Context->ParentWindow, MSG_SXS_ERROR_FILE_INSTEAD_OF_DIRECTORY, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, Root ); Success = FALSE; goto Exit; } #if CHECK_FOR_MINIMUM_ASSEMBLIES /* We do NOT this, it is buggy wrt asms/wasms. */ // // ensure all the mandatory assemblies exist // NOTE this check is only partial, but a more complete // check will be done when we enumerate and recurse // for (i = 0 ; i != NUMBER_OF(MinimumAssemblies) ; ++i) { RootStar[RootLength] = 0; ConcatenatePaths(RootStar, MinimumAssemblies[i], MAX_PATH); FileAttributes = GetFileAttributes(RootStar); if (FileAttributes == INVALID_FILE_ATTRIBUTES) { const DWORD LastError = GetLastError(); SxspDebugOut(TEXT("SXS: required directory %s missing, or error %lu.\n"), RootStar, LastError); MessageBoxFromMessageAndSystemError( Context->ParentWindow, MSG_SXS_ERROR_REQUIRED_DIRECTORY_MISSING, LastError, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, RootStar ); Success = FALSE; // keep running, look for more errors } if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { SxspDebugOut(TEXT("SXS: %s is file instead of directory\n"), RootStar); MessageBoxFromMessage( Context->ParentWindow, MSG_SXS_ERROR_FILE_INSTEAD_OF_DIRECTORY, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, RootStar ); Success = FALSE; } } #endif #if CHECK_FOR_OBSOLETE_ASSEMBLIES /* We do this; it somewhat against longstanding principle. */ // // ensure none of the obsolete assemblies exist // for (i = 0 ; i != NUMBER_OF(ObsoleteAssemblies) ; ++i) { RootStar[RootLength] = 0; ConcatenatePaths(RootStar, ObsoleteAssemblies[i], MAX_PATH); FileAttributes = GetFileAttributes(RootStar); if (FileAttributes != INVALID_FILE_ATTRIBUTES) { // // We don't care if it's a file or directory or what // the directory contains. It's a fatal error no matter what. // SxspDebugOut(TEXT("SXS: obsolete %s present\n"), RootStar); MessageBoxFromMessage( Context->ParentWindow, MSG_SXS_ERROR_OBSOLETE_DIRECTORY_PRESENT, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, RootStar ); Success = FALSE; // keep running, look for more errors } } #endif // // enumerate and recurse // RootStar[RootLength] = 0; StringCopy(RootStar, Root); ConcatenatePaths(RootStar, TEXT("*"), MAX_PATH); FindHandle = FindFirstFile(RootStar, &FindData); if (FindHandle == INVALID_HANDLE_VALUE) { // // An error here is unexplainable. // CONST DWORD LastError = GetLastError(); SxspDebugOut( TEXT("SXS: %s(%s), FindFirstFile(%s):%d\n"), T_FUNCTION, Root, RootStar, LastError ); MessageBoxFromMessage( Context->ParentWindow, LastError, TRUE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL ); Success = FALSE; goto Exit; } do { if (SxspIsDotOrDotDot(FindData.cFileName)) continue; // // REVIEW // I think this is too strict. // Corporate deployers might drop a readme.txt here. // if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { //RootStar[RootLength] = 0; //Context->ReportErrorMessage(Context, MSG_SXS_ERROR_NON_LEAF_DIRECTORY_CONTAINS_FILE, RootStar, FindData.cFileName); } else { // // now enumerate recursively, checking each leaf // munge the recursion slightly to save a function and stack space // (usually we'd start at the root, instead of its first generation children) // Empty = FALSE; RootStar[RootLength] = 0; ConcatenatePaths(RootStar, FindData.cFileName, MAX_PATH); if (!SxspFindAndCheckLeaves(Context, RootStar, StringLength(RootStar), &FindData)) Success = FALSE; // keep looping, to possibly report more errors } } while(FindNextFile(FindHandle, &FindData)); FindClose(FindHandle); if (Empty) { SxspDebugOut(TEXT("SXS: directory %s empty\n"), Root); MessageBoxFromMessage( Context->ParentWindow, MSG_SXS_ERROR_DIRECTORY_EMPTY, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, Root ); Success = FALSE; goto Exit; } Exit: return Success; } BOOL SxsCheckLocalSource( PSXS_CHECK_LOCAL_SOURCE Parameters ) /* Late in winnt32 enumerate ~ls\...\asms ensure asms is a directory ensure that everything one level down in asms is a directory (I didn't do this, seems too strict). enumerate asms recursively ensure every leaf directory has a .cat with the same base name as the directory ensure every leaf directory has a .man or .manifest with the same base name as the directory Read the first 512 bytes of every .cat/.man/.manifest. Ensure that they are not all zero. REVIEW also that required exist and obsolete assemblies do not */ { ULONG i; TCHAR FullPath[MAX_PATH]; BOOL Success = TRUE; TCHAR LocalSourceDrive; // // ensure LocalSource is present/valid // if (!MakeLocalSource) return TRUE; LocalSourceDrive = (TCHAR)towupper(LocalSourceDirectory[0]); if (LocalSourceDrive != towupper(LocalSourceWithPlatform[0])) return TRUE; if (LocalSourceDrive < 'C' || LocalSourceDrive > 'Z') return TRUE; // // flush LocalSource where the Win32 api is simple (NT, not Win9x) // if (ISNT()) { CONST TCHAR LocalSourceDrivePath[] = { '\\', '\\', '.', '\\', LocalSourceDrive, ':', 0 }; CONST HANDLE LocalSourceDriveHandle = CreateFile( LocalSourceDrivePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING, NULL ); if (LocalSourceDriveHandle != INVALID_HANDLE_VALUE) { FlushFileBuffers(LocalSourceDriveHandle); CloseHandle(LocalSourceDriveHandle); } } for(i = 0; i != OptionalDirectoryCount; ++i) { if ((OptionalDirectoryFlags[i] & OPTDIR_SIDE_BY_SIDE) != 0) { MYASSERT( (OptionalDirectoryFlags[i] & OPTDIR_PLATFORM_INDEP) ^ (OptionalDirectoryFlags[i] & OPTDIR_ADDSRCARCH) ); switch (OptionalDirectoryFlags[i] & (OPTDIR_PLATFORM_INDEP | OPTDIR_ADDSRCARCH)) { case OPTDIR_ADDSRCARCH: StringCopy(FullPath, LocalSourceWithPlatform); break; case OPTDIR_PLATFORM_INDEP: StringCopy(FullPath, LocalSourceDirectory); break; } ConcatenatePaths(FullPath, OptionalDirectories[i], MAX_PATH); if (!SxspCheckRoot(Parameters, FullPath)) Success = FALSE; // keep looping, to possibly report more errors } } return Success; }