windows-nt/Source/XPSP1/NT/base/ntsetup/winnt32/dll/sxs.c
2020-09-26 16:20:57 +08:00

792 lines
22 KiB
C

/*++
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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#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);
_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"), TEXT(".Ca_") };
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();
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;
}