/*++ Copyright (c) 1992 Microsoft Corporation Module Name: dncopy.c Abstract: File copy routines for DOS-hosted NT Setup program. Author: Ted Miller (tedm) 1-April-1992 Revision History: --*/ #include "precomp.h" #pragma hdrstop #include "msg.h" typedef struct _DIRECTORY_NODE { struct _DIRECTORY_NODE *Next; PTSTR Directory; // never starts or ends with \. PTSTR Symbol; } DIRECTORY_NODE, *PDIRECTORY_NODE; #define RETRY_COUNT 2 // retry copy twice if copy fails. // // NOTE: // // This code must be multi-thread safe, as more than one thread // may be running through it at any one time. // PDIRECTORY_NODE DirectoryList; BOOL DnpCreateDirectories( IN PTSTR TargetRootDir, IN HWND hdlg ); DWORD DnpLocateSourceFile( IN PTSTR Filename, OUT PWIN32_FIND_DATA FindData, OUT PTSTR *ActualFilename ); PTSTR DnpLookUpDirectory( IN PTSTR Symbol ); BOOL DnCreateLocalSourceDirectories( IN HWND hdlg ) { BOOL b; b = DnpCreateOneDirectory(LocalSourcePath,hdlg) && DnpCreateDirectories(LocalSourceSubPath,hdlg); return(b); } DWORD ThreadCopyLocalSourceFiles( IN PVOID ThreadParameter ) /*++ Routine Description: Top-level file copy entry point. Creates all directories listed in the [Directories] section of the inf. Copies all files listed in the [Files] section of the inf file from the source to the target (which becomes the local source). Arguments: ThreadParameter - supplies the handle of a dialog box that will receive window messages relating to copy progress. Return Value: FALSE if an error occurred copying a file and the user elected to exit setup, or it an error occurred creating directories, or a syntax error appeared in the inf file. TRUE otherwise. --*/ { DWORD ClusterSize,SectorSize; DWORD SizeOccupied; TCHAR Buffer[MAX_PATH]; DWORD FileCount; HWND hdlg; BOOL rc; // // Do not change this without changing text setup as well // (SpPtDetermineRegionSpace()). // CHAR SizeLine[64]; PCHAR Lines[3] = { "[Data]",SizeLine,NULL }; hdlg = (HWND)ThreadParameter; try { FileCount = DnSearchINFSection(InfHandle,INF_FILES); // // Determine the sector and cluster size on the drive. // GetDriveSectorInfo(LocalSourceDrive,&SectorSize,&ClusterSize); // // Pass over the copy list and actually perform the copies. // SizeOccupied = CopySectionOfFilesToCopy( hdlg, INF_FILES, LocalSourceSubPath, ClusterSize, TRUE ); // // Assume failure. // rc = FALSE; if(SizeOccupied != (DWORD)(-1)) { // // Make an approximate calculation of the amount of disk space taken up // by the local source directory itself, assuming 32 bytes per dirent. // Also account for the small ini file that we'll put in the local source // directory, to tell text setup how much space the local source takes up. // We don't account for clusters in the directory -- we base this on sector // counts only. // SizeOccupied += SectorSize * (((FileCount + 1) / (SectorSize / 32)) + 1); // // Create a small ini file listing the size occupied by the local source. // Account for the ini file in the size. // lstrcpy(Buffer,LocalSourcePath); // // Do not change this without changing text setup as well // (SpPtDetermineRegionSpace()). // DnConcatenatePaths(Buffer,TEXT("\\size.sif"),MAX_PATH); wsprintfA(Lines[1],"Size = %lu\n",SizeOccupied + ClusterSize); DnWriteSmallIniFile(Buffer,Lines,NULL); // // BUGBUG should really check return code from DnWriteSmallIniFile. // The ini file being written tells text setup how large the local sources // are on disk. Not critical if absent. // rc = TRUE; } PostMessage(hdlg, WMX_ALL_FILES_COPIED, rc, 0); } except(EXCEPTION_EXECUTE_HANDLER) { MessageBoxFromMessage( hdlg, MSG_GENERIC_EXCEPTION, AppTitleStringId, MB_ICONSTOP | MB_OK | MB_APPLMODAL | MB_SETFOREGROUND, GetExceptionCode() ); rc = FALSE; PostMessage(hdlg, WMX_ALL_FILES_COPIED, rc, 0); } ExitThread(rc); return(rc); // avoid compiler warning } VOID DnCreateDirectoryList( IN PTSTR SectionName ) /*++ Routine Description: Examine a section in the INF file, whose lines are to be in the form key = directory and create a linked list describing the key/directory pairs found therein. If the directory field is empty, it is assumed to be the root. Arguments: SectionName - supplies name of section Return Value: None. Does not return if syntax error in the inf file section. --*/ { unsigned LineIndex,len; PDIRECTORY_NODE DirNode,PreviousNode; PTSTR Key; PTSTR Dir; LineIndex = 0; PreviousNode = NULL; while(Key = DnGetKeyName(InfHandle,SectionName,LineIndex)) { Dir = DnGetSectionKeyIndex(InfHandle,SectionName,Key,0); if(Dir == NULL) { Dir = TEXT(""); // use the root if not specified } // // Skip leading backslashes // while(*Dir == TEXT('\\')) { Dir++; } // // Clip off trailing backslashes if present // while((len = lstrlen(Dir)) && (Dir[len-1] == TEXT('\\'))) { Dir[len-1] = 0; } DirNode = MALLOC(sizeof(DIRECTORY_NODE)); DirNode->Next = NULL; DirNode->Directory = Dir; DirNode->Symbol = Key; if(PreviousNode) { PreviousNode->Next = DirNode; } else { DirectoryList = DirNode; } PreviousNode = DirNode; LineIndex++; } } BOOL DnpCreateDirectories( IN PTSTR TargetRootDir, IN HWND hdlg ) /*++ Routine Description: Create the local source directory, and run down the DirectoryList and create directories listed therein relative to the given root dir. Arguments: TargetRootDir - supplies the name of root directory of the target Return Value: Boolean value indicating whether the directories were created successfully. --*/ { PDIRECTORY_NODE DirNode; TCHAR TargetDirTemp[MAX_PATH]; if(!DnpCreateOneDirectory(TargetRootDir,hdlg)) { return(FALSE); } for(DirNode = DirectoryList; DirNode; DirNode = DirNode->Next) { // // No need to create the root // if(*DirNode->Directory) { lstrcpy(TargetDirTemp,TargetRootDir); DnConcatenatePaths(TargetDirTemp,DirNode->Directory,MAX_PATH); if(!DnpCreateOneDirectory(TargetDirTemp,hdlg)) { return(FALSE); } } } return(TRUE); } BOOL DnpCreateOneDirectory( IN PTSTR Directory, IN HWND hdlg ) /*++ Routine Description: Create a single directory if it does not already exist. Arguments: Directory - directory to create Return Value: Boolean value indicating whether the directory was created. --*/ { WIN32_FIND_DATA FindData; HANDLE FindHandle; BOOL b; // // First, see if there's a file out there that matches the name. // FindHandle = FindFirstFile(Directory,&FindData); if(FindHandle == INVALID_HANDLE_VALUE) { // // Directory doesn't seem to be there, so we should be able // to create the directory. // b = CreateDirectory(Directory,NULL); } else { // // File matched. If it's a dir, we're OK. Otherwise we can't // create the dir, a fatal error. // b = ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); FindClose(FindHandle); } if(!b) { MessageBoxFromMessage( hdlg, MSG_COULDNT_CREATE_DIRECTORY, IDS_ERROR, MB_OK | MB_ICONSTOP, Directory ); } return(b); } BOOL VerifySectionOfFilesToCopy( IN PTSTR SectionName, OUT PDWORD ErrorLine, OUT PDWORD ErrorValue ) { DWORD LineIndex; DWORD LineCount; PTSTR DirSym,FileName; LineCount = DnSearchINFSection(InfHandle,SectionName); if((LONG)LineCount <= 0) { // section missing or empty -- indicate bad value on line 0, value 0 *ErrorLine = 0; *ErrorValue = 0; return(FALSE); } for(LineIndex=0; LineIndexNext) { if(!lstrcmpi(DirList->Symbol,Symbol)) { return(DirList->Directory); } } return(NULL); } DWORD DnCopyOneFile( IN HWND hdlg, IN PTSTR SourceName, IN PTSTR DestName, OUT PDWORD ErrorCode OPTIONAL ) /*++ Routine Description: Copies a single file. Arguments: SourceName - supplies fully qualified name of source file DestName - supplies fully qualified name of destination file Return Value: Number of bytes copied (0 if file is skipped). If an error occurs, and the user elects to exit setup, the return value is -1. --*/ { BOOL Retry; DWORD FileSize; DWORD ec; WIN32_FIND_DATA FindData; BOOL b; UINT RetryCount; PTSTR ActualName = NULL; // // Try to ensure that the target file is overwritable if it exists. // SetFileAttributes(DestName,FILE_ATTRIBUTE_NORMAL); while(1) { RetryCount = 0; do { ec = DnpLocateSourceFile(SourceName,&FindData,&ActualName); } while((ec != NO_ERROR) && (RetryCount++ < RETRY_COUNT)); if(ec == NO_ERROR) { // // Copy the file. // RetryCount = 0; do { b = CopyFile(ActualName ? ActualName : SourceName,DestName,FALSE); } while(!b && (RetryCount++ < RETRY_COUNT)); if(b) { if(ActualName) { FREE(ActualName); } return(FindData.nFileSizeLow); } else { ec = GetLastError(); } } if(ActualName) { FREE(ActualName); } if(ErrorCode) { *ErrorCode = ec; return((DWORD)(-1)); } else { // // Send out to copy error routine for processing. // That routine can tell us to exit, retry, or skip the file. // ec = DnFileCopyError(hdlg,SourceName,DestName,ec); switch(ec) { case COPYERR_EXIT: PostMessage(hdlg, WM_COMMAND, IDCANCEL, 0); return((DWORD)(-1)); case COPYERR_SKIP: return(0); case COPYERR_RETRY: break; } } } } VOID DnFreeDirectoryList( VOID ) /*++ Routine Description: Free a linked list of directory nodes and place NULL in the head pointer. Arguments: None. Return Value: None. --*/ { PDIRECTORY_NODE n,p = DirectoryList; while(p) { n = p->Next; FREE(p); p = n; } DirectoryList = NULL; } PTSTR DnpGenerateCompressedName( IN PTSTR Filename ) /*++ Routine Description: Given a filename, generate the compressed form of the name. The compressed form is generated as follows: Look backwards for a dot. If there is no dot, append "._" to the name. If there is a dot followed by 0, 1, or 2 charcaters, append "_". Otherwise assume there is a 3-character extension and replace the third character after the dot with "_". Arguments: Filename - supplies filename whose compressed form is desired. Return Value: Pointer to buffer containing nul-terminated compressed-form filename. The caller must free this buffer via FREE(). --*/ { PTSTR CompressedName,p,q; // // The maximum length of the compressed filename is the length of the // original name plus 2 (for ._). // CompressedName = MALLOC((lstrlen(Filename)+3)*sizeof(TCHAR)); lstrcpy(CompressedName,Filename); p = StringRevChar(CompressedName,TEXT('.')); q = StringRevChar(CompressedName,TEXT('\\')); if(q < p) { // // If there are 0, 1, or 2 characters after the dot, just append // the underscore. p points to the dot so include that in the length. // if(lstrlen(p) < 4) { lstrcat(CompressedName,TEXT("_")); } else { // // Assume there are 3 characters in the extension. So replace // the final one with an underscore. // p[3] = TEXT('_'); } } else { // // No dot, just add ._. // lstrcat(CompressedName,TEXT("._")); } return(CompressedName); } DWORD DnpLocateSourceFile( IN PTSTR Filename, OUT PWIN32_FIND_DATA FindData, OUT PTSTR *ActualFilename ) /*++ Routine Description: Determine whether a source file exists and is accessible, and if so, which name (compressed form vs. uncompressed form) is used. Whether we look for the compressed or uncompressed name first depends on what form was found the last time this routine succeeded. Arguments: Filename - supplies full path of file to open. This should be the uncompressed form of the filename. FindData - if the routine succeeds this receives Win32 Find data for the file. ActualFilename - if the filename is in compressed form, receives the name of the file. Otherwise receives NULL. Not valid unless this routine returns TRUE. Return Value: NO_ERROR if the file was located. Other Win32 error code if not. --*/ { BOOL TryCompressedFirst; HANDLE FindHandle; PTSTR FirstName,SecondName; DWORD ec; TryCompressedFirst = (BOOL)TlsGetValue(TlsIndex); if(TryCompressedFirst) { FirstName = DnpGenerateCompressedName(Filename); SecondName = Filename; } else { FirstName = Filename; } FindHandle = FindFirstFile(FirstName,FindData); if(FindHandle != INVALID_HANDLE_VALUE) { FindClose(FindHandle); *ActualFilename = TryCompressedFirst ? FirstName : NULL; TlsSetValue(TlsIndex,(PVOID)(FirstName != Filename)); return(NO_ERROR); } if(!TryCompressedFirst) { SecondName = DnpGenerateCompressedName(Filename); } FindHandle = FindFirstFile(SecondName,FindData); if(FindHandle == INVALID_HANDLE_VALUE) { ec = GetLastError(); } else { FindClose(FindHandle); if(TryCompressedFirst) { *ActualFilename = NULL; FREE(FirstName); } else { *ActualFilename = SecondName; } TlsSetValue(TlsIndex,(PVOID)(SecondName != Filename)); return(NO_ERROR); } // // Use if statement instead of ?: because FREE is a macro // that involves taking the address of its operand. // if(FirstName == Filename) { FREE(SecondName); } else { FREE(FirstName); } return(ec); } BOOL DnCopyFilesInSection( IN HWND hdlg, IN PTSTR Section, IN PTSTR SourceDir, IN PTSTR TargetDir, IN BOOL ForceNoComp ) { DWORD line = 0; PTSTR Filename,p,q; PTSTR Targname; DWORD d; while(Filename = DnGetSectionLineIndex(InfHandle,Section,line++,0)) { Targname = DnGetSectionLineIndex(InfHandle,Section,line-1,1); if(!Targname) { Targname = Filename; } p = MALLOC((lstrlen(SourceDir)+lstrlen(Filename)+2)*sizeof(TCHAR)); q = MALLOC((lstrlen(TargetDir)+lstrlen(Targname)+2)*sizeof(TCHAR)); wsprintf(p,TEXT("%s\\%s"),SourceDir,Filename); wsprintf(q,TEXT("%s\\%s"),TargetDir,Targname); d = DnCopyOneFile(hdlg,p,q,NULL); if((d != -1) && ForceNoComp) { // // then ensure this file isn't using NTFS compression // ForceFileNoCompress(q); } FREE(p); FREE(q); if(d == (DWORD)(-1)) { return(FALSE); } } return(TRUE); }