2760 lines
80 KiB
C
2760 lines
80 KiB
C
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// fcopy.c Program for mastering floppy disks, either as an //
|
|
// image file or directly writing the floppy disk. //
|
|
// //
|
|
// Code is targeted as Win32 console application. //
|
|
// //
|
|
// Author: Tom McGuire (tommcg) //
|
|
// //
|
|
// Original version written June 1993. //
|
|
// //
|
|
// (C) Copyright 1993-1996, Microsoft Corporation //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
#include "bootsect.h"
|
|
|
|
#define dwShow( dwValue ) printf( #dwValue " = 0x%08X\n", (dwValue) )
|
|
|
|
#define NEW_NT_EXTENDED_FAT_DIRECTORY_ENTRY_INTERPRETATION 1
|
|
|
|
#define MAX_FAT_ENTRIES 0xC00 // 3.5" HD 1.44MB floppy
|
|
#define MAX_FAT_NAME 14 // 8.3 plus NULL, dot, and one extra
|
|
|
|
//
|
|
// MAX_ROOT_ENTRIES is no longer a constant
|
|
//
|
|
#define MAX_ROOT_ENTRIES_STD 224 // 224 * 32bytes = 0x1C00 bytes total root
|
|
#define MAX_ROOT_ENTRIES_DMF 16 // 16 * 32 = 0x200 bytes total root (1 sector)
|
|
WORD wMaxRootEntries;
|
|
|
|
#define MAX_ENV 1024 // max size of environment variable we'll handle
|
|
|
|
#define DISKTYPE_35 35
|
|
#define DISKTYPE_525 525
|
|
#define DISKTYPE_DMF 18
|
|
#define DISKTYPE_UNKNOWN 0
|
|
|
|
//
|
|
// CLUSTER_SIZE is no longer a constant
|
|
//
|
|
#define SECTOR_SIZE 512
|
|
#define CLUSTER_SIZE_STD 512 // 1 sector/cluster
|
|
#define CLUSTER_SIZE_DMF 1024 // 2 sectors/cluster
|
|
WORD wClusterSize;
|
|
|
|
#define BUFFER_SIZE 0x10000
|
|
|
|
#define DOS_ATTRIBUTE_NORMAL 0x00
|
|
#define DOS_ATTRIBUTE_READONLY 0x01
|
|
#define DOS_ATTRIBUTE_HIDDEN 0x02
|
|
#define DOS_ATTRIBUTE_SYSTEM 0x04
|
|
#define DOS_ATTRIBUTE_VOLUME 0x08
|
|
#define DOS_ATTRIBUTE_DIRECTORY 0x10
|
|
#define DOS_ATTRIBUTE_ARCHIVE 0x20
|
|
|
|
#define FILE_ATTRIBUTE_NOT_NORMAL ( FILE_ATTRIBUTE_HIDDEN | \
|
|
FILE_ATTRIBUTE_SYSTEM | \
|
|
FILE_ATTRIBUTE_DIRECTORY )
|
|
|
|
#define ROUNDUP2( x, n ) (((x) + ((n) - 1 )) & ~((n) - 1 ))
|
|
|
|
#pragma pack( 1 ) // following is an on-disk structure, no padding
|
|
|
|
typedef struct _DIRENTRY {
|
|
CHAR BaseName[ 8 ];
|
|
CHAR Extension[ 3 ];
|
|
BYTE Attribute;
|
|
union {
|
|
BYTE Reserved[ 10 ];
|
|
struct {
|
|
BYTE NtByte;
|
|
BYTE CreationTimeMilliSeconds;
|
|
WORD CreationTime;
|
|
WORD CreationDate;
|
|
WORD LastAccessDate;
|
|
WORD ExtendedAttributes;
|
|
} Extended;
|
|
};
|
|
WORD TimeStamp; // LastWriteTime
|
|
WORD DateStamp; // LastWriteDate
|
|
WORD Cluster;
|
|
DWORD FileSize;
|
|
} DIRENTRY, *PDIRENTRY;
|
|
|
|
#pragma pack()
|
|
|
|
typedef struct _FILENODE {
|
|
LPSTR pszTargetFileName; // malloc'd, unique to node, looks like "foo.txt"
|
|
LPSTR pszSourceFileName; // malloc'd, unique to node, looks like "foo.txt"
|
|
LPSTR pszSourcePath; // malloc'd, shared by nodes, looks like "d:\foo\"
|
|
DWORD dwFileSize;
|
|
DWORD dwStartingCluster;
|
|
DWORD dwWin32Attributes;
|
|
FILETIME ftWin32TimeStamp; // last write time
|
|
UINT nSourceIndex;
|
|
struct _FILENODE * pParentDir;
|
|
struct _FILENODE * pNextFile;
|
|
struct _FILENODE * pFirstFile;
|
|
} FILENODE, *PFILENODE;
|
|
|
|
typedef struct _PARAM {
|
|
LPSTR pszParam;
|
|
BOOL bRecurse;
|
|
LPSTR pszTargetDir;
|
|
struct _PARAM *pNext;
|
|
} PARAM, *PPARAM;
|
|
|
|
PPARAM LoadParameters( int argc, char *argv[] );
|
|
PPARAM RemoveParam( PPARAM pParam, PPARAM pPrev );
|
|
PPARAM ParseResponseFile( LPSTR pszFileName, PPARAM pLast );
|
|
void ParseOptions( PPARAM pParamList );
|
|
void ParseDestination( PPARAM pParamList );
|
|
void ParseSources( PPARAM pParamList );
|
|
UINT ParseSourceParameter( PFILENODE pParentDir,
|
|
LPSTR pszSourcePath,
|
|
LPSTR pszSourceFileName,
|
|
LPSTR pszTargetFileName,
|
|
BOOL bRecursive,
|
|
UINT nSourceIndex );
|
|
void ParseTimeStamp( LPSTR pArg );
|
|
void PrepareStructures( void );
|
|
void WriteDestination( void );
|
|
void ReWriteDestination( LPSTR pszMessage );
|
|
void VerifyDestination( void );
|
|
void FormatTarget( void );
|
|
BOOL AddFileToList( WIN32_FIND_DATA *fd,
|
|
PFILENODE pParentDir,
|
|
LPSTR pszSourcePath,
|
|
LPSTR pszTargetFileName,
|
|
UINT nSourceIndex,
|
|
PFILENODE *pThisNode );
|
|
BOOL AddDirectoryToList( LPSTR pszPathName,
|
|
PFILENODE pParentDir,
|
|
UINT nSourceIndex,
|
|
PFILENODE *pThisNode );
|
|
UINT CopyFileContents( HANDLE hTarget, HANDLE hSource, DWORD dwSize );
|
|
void ErrorMessage( const char *szFormat, ... );
|
|
void ErrorExit( const char *szFormat, ... );
|
|
void Usage( void );
|
|
void Copyright( void );
|
|
BOOL IsDigit( char ch );
|
|
UINT Min( UINT a, UINT b );
|
|
void GetBootSector( void );
|
|
DWORD CompareAligned( PVOID pBuffer1, PVOID pBuffer2, DWORD dwLength );
|
|
PVOID MyAlloc( UINT nBytes );
|
|
PVOID MyAllocZ( UINT nBytes );
|
|
VOID MyFree( PVOID pAlloc );
|
|
BOOL IsFileNameDotOrDotDot( LPCSTR pszFileName );
|
|
PFILENODE NewFileNode( void );
|
|
LPSTR DuplicateString( LPCSTR pString );
|
|
void AssignDirectoryClusters( PFILENODE pDir );
|
|
void AssignFileClusters( PFILENODE pDir );
|
|
DWORD AllocateFat( DWORD dwFileSize );
|
|
void PadCopy( LPSTR pDest, LPCSTR pSource, UINT nLength );
|
|
UINT CreateAndWriteDirectories( PFILENODE pDir );
|
|
UINT WalkListAndWriteFiles( PFILENODE pDir );
|
|
UINT OpenAndCopySourceFile( PFILENODE pNode );
|
|
void CreateVolumeLabelEntry( PDIRENTRY pDirEntry );
|
|
void CreateTagFileEntry( PDIRENTRY pDirEntry );
|
|
void CreateDirEntry( PDIRENTRY pDirEntry,
|
|
LPSTR pszFileName,
|
|
DWORD dwWin32Attributes,
|
|
FILETIME ftWin32TimeStamp,
|
|
DWORD dwStartingCluster,
|
|
DWORD dwFileSize );
|
|
BOOL TrimTheTree( PFILENODE pDir );
|
|
void InitializeOverlapped( void );
|
|
void OverlappedVerifyComplete( HANDLE hFile );
|
|
void OverlappedWriteFile( HANDLE hFile, LPVOID pData, DWORD nBytes );
|
|
void OverlappedReadFile( HANDLE hFile, LPVOID pData, DWORD nBytes, PDWORD pdwActualBytes );
|
|
void OverlappedSetFilePointer( HANDLE hFile, DWORD dwAbsoluteOffset );
|
|
LPSTR ReplaceEnvironmentStrings( LPCSTR pszInputString, LPSTR pTargetBuffer );
|
|
void BuildFullTargetName( PFILENODE pNode, LPSTR pBuffer );
|
|
void AssignDmfSignature( PUCHAR pBuffer );
|
|
|
|
USHORT Fat[ MAX_FAT_ENTRIES ];
|
|
|
|
CHAR chFatImage[ 0x1200 ];
|
|
|
|
UCHAR BootSector[ SECTOR_SIZE ];
|
|
|
|
PFILENODE pRootDir;
|
|
|
|
UINT nMaxFatEntries;
|
|
|
|
UINT nNextAvailableCluster;
|
|
|
|
CHAR chDest[ MAX_PATH ];
|
|
CHAR chLabel[ MAX_FAT_NAME ];
|
|
CHAR chTagFile[ MAX_FAT_NAME ];
|
|
CHAR chBootSpecifier[ MAX_PATH ];
|
|
|
|
PVOID pAlignedBuffer;
|
|
|
|
HANDLE hTarget, hDevice;
|
|
|
|
PUCHAR pWholeDiskBuffer, pWholeDiskCurrentLocation;
|
|
|
|
DWORD fDiskType = DISKTYPE_UNKNOWN;
|
|
|
|
BOOL bDestIsDevice;
|
|
BOOL bZeroSlack;
|
|
BOOL bUseGlobalTime;
|
|
BOOL bDoubleWrite;
|
|
BOOL bFormatTarget;
|
|
BOOL bVerifyTarget;
|
|
BOOL bIncludeEmptyDirectories;
|
|
BOOL bCopyAttributes;
|
|
BOOL bFormatTargetDMF;
|
|
BOOL bWritableDMF;
|
|
BOOL bWriteToTarget;
|
|
BOOL bWriteToBuffer;
|
|
|
|
WORD wDosDate;
|
|
WORD wDosTime;
|
|
|
|
PPARAM pParamList;
|
|
|
|
HANDLE hProcessHeap;
|
|
UINT nMaxSourceLength;
|
|
|
|
OVERLAPPED olControl;
|
|
DWORD dwOverlappedFilePointer;
|
|
DWORD dwOverlappedExpectedBytes;
|
|
BOOL bOverlappedOutstanding;
|
|
BOOL bOverlappedWriting;
|
|
PVOID pOverlappedBuffer[ 2 ];
|
|
INT iAvailableOverlappedBuffer;
|
|
|
|
SYSTEMTIME stGlobalSystemTime;
|
|
FILETIME ftGlobalFileTime;
|
|
|
|
|
|
void _cdecl main( int argc, char *argv[] ) {
|
|
|
|
Copyright();
|
|
|
|
if ( argc < 3 )
|
|
Usage();
|
|
|
|
SetFileApisToOEM();
|
|
|
|
SetErrorMode( SEM_FAILCRITICALERRORS );
|
|
|
|
hProcessHeap = GetProcessHeap();
|
|
|
|
GetLocalTime( &stGlobalSystemTime );
|
|
SystemTimeToFileTime( &stGlobalSystemTime, &ftGlobalFileTime );
|
|
FileTimeToDosDateTime( &ftGlobalFileTime, &wDosDate, &wDosTime );
|
|
|
|
|
|
pAlignedBuffer = VirtualAlloc( NULL, BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE );
|
|
if ( pAlignedBuffer == NULL )
|
|
ErrorExit( "\nOut of memory\n" );
|
|
|
|
pParamList = LoadParameters( argc, argv );
|
|
|
|
ParseOptions( pParamList );
|
|
|
|
ParseDestination( pParamList );
|
|
|
|
if ( (bFormatTarget || bFormatTargetDMF) && bDestIsDevice )
|
|
FormatTarget();
|
|
|
|
pWholeDiskBuffer = VirtualAlloc( NULL, 0x1C0000, MEM_COMMIT, PAGE_READWRITE );
|
|
if ( pWholeDiskBuffer == NULL )
|
|
ErrorExit( "\nOut of memory\n" );
|
|
pWholeDiskCurrentLocation = pWholeDiskBuffer;
|
|
|
|
ParseSources( pParamList );
|
|
|
|
PrepareStructures();
|
|
|
|
WriteDestination();
|
|
|
|
exit( 0 );
|
|
|
|
}
|
|
|
|
|
|
void WriteDestination( void ) {
|
|
|
|
DWORD dwBytes, nClusters, nBuffers;
|
|
UINT i, last;
|
|
|
|
#ifdef DONTCOMPILE // now leaving hDevice open as global
|
|
hTarget = CreateFile( chDest,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
bDestIsDevice ? OPEN_EXISTING
|
|
: CREATE_ALWAYS,
|
|
bDestIsDevice ? FILE_FLAG_NO_BUFFERING |
|
|
FILE_FLAG_OVERLAPPED
|
|
: FILE_ATTRIBUTE_NORMAL |
|
|
FILE_FLAG_OVERLAPPED,
|
|
NULL );
|
|
|
|
if ( hTarget == INVALID_HANDLE_VALUE )
|
|
ErrorExit( "\nCreateFile( \"%s\" ) failed (GLE=%d)\n",
|
|
chDest,
|
|
GetLastError() );
|
|
#endif
|
|
|
|
if ( bDestIsDevice )
|
|
hTarget = hDevice;
|
|
|
|
else {
|
|
|
|
hTarget = CreateFile( chDest,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
|
|
NULL );
|
|
|
|
if ( hTarget == INVALID_HANDLE_VALUE )
|
|
ErrorExit( "\nCreateFile( \"%s\" ) failed (GLE=%d)\n",
|
|
chDest,
|
|
GetLastError() );
|
|
}
|
|
|
|
InitializeOverlapped();
|
|
|
|
if ( fDiskType == DISKTYPE_DMF ) {
|
|
bWriteToTarget = FALSE;
|
|
bWriteToBuffer = TRUE;
|
|
bZeroSlack = TRUE;
|
|
}
|
|
else {
|
|
bWriteToTarget = TRUE;
|
|
bWriteToBuffer = ( bVerifyTarget || ( bDoubleWrite && bDestIsDevice ));
|
|
}
|
|
|
|
OverlappedWriteFile( hTarget, BootSector, SECTOR_SIZE );
|
|
|
|
if ( fDiskType == DISKTYPE_35 ) {
|
|
dwBytes = 0x1200;
|
|
} else if ( fDiskType == DISKTYPE_525 ) {
|
|
dwBytes = 0xE00;
|
|
} else { // else we're using DMF format
|
|
dwBytes = 0xA00;
|
|
}
|
|
|
|
OverlappedWriteFile( hTarget, chFatImage, dwBytes );
|
|
OverlappedWriteFile( hTarget, chFatImage, dwBytes );
|
|
|
|
nClusters = 1;
|
|
|
|
nClusters += CreateAndWriteDirectories( pRootDir );
|
|
|
|
ErrorMessage( "done\n" ); // "creating directories"
|
|
|
|
ErrorMessage( "Copying files...\n" );
|
|
|
|
nClusters += WalkListAndWriteFiles( pRootDir );
|
|
|
|
if ( nNextAvailableCluster != ( nClusters + 1 )) {
|
|
ErrorExit( "\nASSERTION FAILURE: Cluster count mismatch:\n"
|
|
" nClusters = %d\n"
|
|
" nNextAvailableCluster = %d\n",
|
|
nClusters,
|
|
nNextAvailableCluster );
|
|
}
|
|
|
|
if ( bZeroSlack ) {
|
|
|
|
if ( fDiskType == DISKTYPE_35 ) {
|
|
last = 2848;
|
|
} else if ( fDiskType == DISKTYPE_525 ) {
|
|
last = 2372;
|
|
} else { // (fDiskType == DISKTYPE_DMF)
|
|
last = 1675;
|
|
}
|
|
|
|
if ( nClusters < last ) {
|
|
|
|
ErrorMessage( "Zeroing slack space..." );
|
|
|
|
dwBytes = ( last - nClusters ) * wClusterSize;
|
|
nBuffers = ROUNDUP2( dwBytes, BUFFER_SIZE ) / BUFFER_SIZE;
|
|
|
|
ZeroMemory( pAlignedBuffer, Min( dwBytes, BUFFER_SIZE ));
|
|
|
|
for ( i = 0; i < nBuffers; i++ ) {
|
|
OverlappedWriteFile( hTarget, pAlignedBuffer, Min( dwBytes, BUFFER_SIZE ));
|
|
dwBytes -= BUFFER_SIZE;
|
|
}
|
|
|
|
ErrorMessage( "done\n" );
|
|
|
|
}
|
|
}
|
|
|
|
bWriteToBuffer = FALSE;
|
|
bWriteToTarget = TRUE;
|
|
|
|
if ( fDiskType == DISKTYPE_DMF ) {
|
|
#ifdef DMFSIGNATURE
|
|
AssignDmfSignature( pWholeDiskBuffer );
|
|
#endif
|
|
ReWriteDestination( "Writing image to target..." );
|
|
}
|
|
|
|
if ( bDoubleWrite && bDestIsDevice ) {
|
|
ReWriteDestination( "Rewriting each sector..." );
|
|
}
|
|
|
|
if ( bVerifyTarget )
|
|
VerifyDestination();
|
|
|
|
OverlappedVerifyComplete( hTarget );
|
|
|
|
CloseHandle( hTarget );
|
|
|
|
}
|
|
|
|
|
|
UINT CopyFileContents( HANDLE hTarget, HANDLE hSource, DWORD dwSize ) {
|
|
|
|
DWORD dwBytes, dwClusterBytes;
|
|
UINT i, nClusters, nBuffers;
|
|
|
|
nClusters = ROUNDUP2( dwSize, wClusterSize ) / wClusterSize;
|
|
nBuffers = ROUNDUP2( dwSize, BUFFER_SIZE ) / BUFFER_SIZE;
|
|
|
|
for ( i = 0; i < nBuffers; i++ ) {
|
|
|
|
if ( ! ReadFile( hSource, pAlignedBuffer, BUFFER_SIZE, &dwBytes, NULL ))
|
|
ErrorExit( "\nError reading file (GLE=%d)\n", GetLastError() );
|
|
|
|
if ( dwBytes < BUFFER_SIZE ) {
|
|
dwClusterBytes = ROUNDUP2( dwBytes, wClusterSize );
|
|
ZeroMemory( (PCHAR)pAlignedBuffer + dwBytes, dwClusterBytes - dwBytes );
|
|
dwBytes = dwClusterBytes;
|
|
}
|
|
|
|
OverlappedWriteFile( hTarget, pAlignedBuffer, dwBytes );
|
|
|
|
}
|
|
|
|
return nClusters;
|
|
|
|
}
|
|
|
|
|
|
void PrepareStructures( void ) {
|
|
|
|
UINT i;
|
|
CHAR *p;
|
|
|
|
ErrorMessage( "Creating directories..." );
|
|
|
|
GetBootSector();
|
|
|
|
//
|
|
// Choose serial number from tick count
|
|
//
|
|
|
|
*(UNALIGNED DWORD*)&BootSector[ 0x27 ] = GetTickCount();
|
|
|
|
if ( *chLabel != '\0' ) {
|
|
memcpy( &BootSector[ 0x2B ], chLabel, Min( strlen( chLabel ), 11 ));
|
|
}
|
|
|
|
if ( fDiskType == DISKTYPE_35 ) {
|
|
|
|
Fat[ 0 ] = 0xFF0;
|
|
nMaxFatEntries = 0xB20;
|
|
*(UNALIGNED WORD*)&BootSector[ 0x13 ] = 0xB40;
|
|
*(UNALIGNED BYTE*)&BootSector[ 0x15 ] = 0xF0;
|
|
*(UNALIGNED WORD*)&BootSector[ 0x16 ] = 0x09;
|
|
*(UNALIGNED WORD*)&BootSector[ 0x18 ] = 0x12;
|
|
|
|
} else if ( fDiskType == DISKTYPE_525 ) {
|
|
|
|
Fat[ 0 ] = 0xFF9;
|
|
nMaxFatEntries = 0x944;
|
|
*(UNALIGNED WORD*)&BootSector[ 0x13 ] = 0x960;
|
|
*(UNALIGNED BYTE*)&BootSector[ 0x15 ] = 0xF9;
|
|
*(UNALIGNED WORD*)&BootSector[ 0x16 ] = 0x07;
|
|
*(UNALIGNED WORD*)&BootSector[ 0x18 ] = 0x0F;
|
|
|
|
} else if (fDiskType == DISKTYPE_DMF ) {
|
|
|
|
Fat[ 0 ] = 0xFF0;
|
|
nMaxFatEntries = 0x68C;
|
|
|
|
//
|
|
// Write OEM Name & Version, which is special to DMF. If the
|
|
// signature is exactly "MSDMF3.2", then it will be treated
|
|
// as write-protected which is what we want. If -w option
|
|
// (writable DMF) has been specified, then we'll tweak this
|
|
// string to "NSDMF3.2" which will make it un-write-protected.
|
|
//
|
|
|
|
CopyMemory( (UNALIGNED BYTE*)&BootSector[ 0x03 ], "MSDMF3.2", 8 );
|
|
|
|
if ( bWritableDMF ) {
|
|
*(UNALIGNED BYTE*)&BootSector[ 0x03 ] = 'N';
|
|
}
|
|
|
|
*(UNALIGNED WORD*)&BootSector[ 0x0B ] = 0x200; // Bytes Per Sector
|
|
*(UNALIGNED BYTE*)&BootSector[ 0x0D ] = 0x02; // Sectors Per Cluster
|
|
*(UNALIGNED WORD*)&BootSector[ 0x0E ] = 0x01; // Reserved Sectors
|
|
*(UNALIGNED BYTE*)&BootSector[ 0x10 ] = 0x02; // Number of Copies of FAT
|
|
*(UNALIGNED WORD*)&BootSector[ 0x11 ] = 0x10; // Max. Number of Root Dir Entries
|
|
*(UNALIGNED WORD*)&BootSector[ 0x13 ] = 0xD20; // Total Sectors
|
|
*(UNALIGNED BYTE*)&BootSector[ 0x15 ] = 0xF0; // Media Descriptor Byte
|
|
*(UNALIGNED WORD*)&BootSector[ 0x16 ] = 0x05; // Sectors Per FAT
|
|
*(UNALIGNED WORD*)&BootSector[ 0x18 ] = 0x15; // Sectors Per Track
|
|
|
|
|
|
} else {
|
|
ErrorExit( "\nUnknown target disk type\n" );
|
|
}
|
|
|
|
Fat[ 1 ] = 0xFFF;
|
|
|
|
nNextAvailableCluster = 2;
|
|
|
|
AssignDirectoryClusters( pRootDir );
|
|
|
|
AssignFileClusters( pRootDir );
|
|
|
|
for ( i = 0, p = chFatImage; i < nNextAvailableCluster; i += 2, p += 3 )
|
|
*(UNALIGNED DWORD *)p = Fat[ i ] | ( Fat[ i + 1 ] << 12 );
|
|
|
|
}
|
|
|
|
|
|
void ParseDestination( PPARAM pParamList ) {
|
|
|
|
//
|
|
// The destination parameter is required and is always the
|
|
// last parameter on the command line. We'll remove it
|
|
// after parsing it so the ParseSources routine won't use it.
|
|
//
|
|
|
|
PPARAM pPrev, pParam;
|
|
|
|
if ( pParamList->pNext == NULL )
|
|
ErrorExit( "\nNo destination parameter specified\n" );
|
|
|
|
for ( pPrev = pParamList, pParam = pParamList->pNext;
|
|
pParam->pNext != NULL;
|
|
pPrev = pParam, pParam = pParam->pNext );
|
|
|
|
strcpy( chDest, pParam->pszParam );
|
|
MyFree( pParam );
|
|
pPrev->pNext = NULL;
|
|
|
|
if ( ! _stricmp( chDest, "A:" )) {
|
|
strcpy( chDest, "\\\\.\\A:" );
|
|
bDestIsDevice = TRUE;
|
|
}
|
|
else if ( ! _stricmp( chDest, "B:" )) {
|
|
strcpy( chDest, "\\\\.\\B:" );
|
|
bDestIsDevice = TRUE;
|
|
}
|
|
else {
|
|
bDestIsDevice = FALSE;
|
|
bZeroSlack = TRUE;
|
|
}
|
|
|
|
if ( bDestIsDevice ) {
|
|
|
|
DISK_GEOMETRY dgArray[ 20 ];
|
|
DISK_GEOMETRY dg;
|
|
DWORD dwActual;
|
|
DWORD dwGLE;
|
|
UINT n, nElements;
|
|
BOOL bSuccess;
|
|
|
|
hDevice = CreateFile( chDest,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL );
|
|
|
|
if ( hDevice == INVALID_HANDLE_VALUE )
|
|
ErrorExit( "\nCould not open device %s (GLE=%d)\n",
|
|
chDest + 4,
|
|
GetLastError() );
|
|
|
|
bSuccess = DeviceIoControl( hDevice,
|
|
IOCTL_DISK_GET_MEDIA_TYPES,
|
|
NULL,
|
|
0,
|
|
&dgArray,
|
|
sizeof( dgArray ),
|
|
&dwActual,
|
|
NULL );
|
|
|
|
if ( ! bSuccess ) {
|
|
|
|
if (( dwGLE = GetLastError() ) == ERROR_NOT_READY )
|
|
ErrorExit( "\nDevice %s not ready\n", chDest + 4 );
|
|
else
|
|
ErrorExit( "\nCannot access device %s (GLE=%d)\n",
|
|
chDest + 4,
|
|
GetLastError() );
|
|
}
|
|
|
|
fDiskType = DISKTYPE_UNKNOWN;
|
|
nElements = dwActual / sizeof( dgArray[ 0 ] );
|
|
|
|
for ( n = 0; n < nElements; n++ ) {
|
|
switch ( dgArray[ n ].MediaType ) {
|
|
case F5_1Pt2_512:
|
|
fDiskType = DISKTYPE_525;
|
|
break;
|
|
case F3_1Pt44_512:
|
|
if ( bFormatTargetDMF )
|
|
fDiskType = DISKTYPE_DMF;
|
|
else
|
|
fDiskType = DISKTYPE_35;
|
|
break;
|
|
case F3_2Pt88_512:
|
|
if ( bFormatTargetDMF )
|
|
ErrorExit( "\nCannot create DMF diskette in 2.88MB drive\n" );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( fDiskType == DISKTYPE_UNKNOWN )
|
|
ErrorExit( "Device %s does not support required media\n",
|
|
chDest + 4 );
|
|
|
|
bSuccess = DeviceIoControl( hDevice,
|
|
IOCTL_DISK_GET_DRIVE_GEOMETRY,
|
|
NULL,
|
|
0,
|
|
&dg,
|
|
sizeof( dg ),
|
|
&dwActual,
|
|
NULL );
|
|
|
|
if ( ! bSuccess ) {
|
|
|
|
if (( dwGLE = GetLastError() ) == ERROR_NOT_READY )
|
|
ErrorExit( "\nDevice %s not ready\n", chDest + 4 );
|
|
else
|
|
ErrorExit( "\nCannot access device %s (GLE=%d)\n",
|
|
chDest + 4,
|
|
GetLastError() );
|
|
}
|
|
|
|
switch ( dg.MediaType ) {
|
|
case Unknown:
|
|
bFormatTarget = TRUE;
|
|
if ( fDiskType == DISKTYPE_DMF )
|
|
bFormatTargetDMF = TRUE;
|
|
break;
|
|
case F5_1Pt2_512:
|
|
if ( fDiskType == DISKTYPE_DMF )
|
|
ErrorExit( "\nCannot format 5.25\" media with DMF\n" );
|
|
else if ( fDiskType != DISKTYPE_525 )
|
|
ErrorExit( "\nIncompatible media in device %s\n",
|
|
chDest + 4 );
|
|
break;
|
|
case F3_1Pt44_512:
|
|
if ( fDiskType == DISKTYPE_DMF ) {
|
|
bFormatTargetDMF = TRUE;
|
|
bFormatTarget = TRUE;
|
|
}
|
|
else if ( fDiskType == DISKTYPE_35 ) {
|
|
if ( dg.SectorsPerTrack != 18 )
|
|
bFormatTarget = TRUE; // back to 1.44 from DMF
|
|
}
|
|
else {
|
|
ErrorExit( "\nIncompatible media in device %s\n",
|
|
chDest + 4 );
|
|
}
|
|
break;
|
|
default:
|
|
ErrorMessage( "\nMedia in device %s contains a format other than that specified."
|
|
"\nWill attempt to re-format %s to specified format.\n",
|
|
chDest + 4,
|
|
chDest + 4 );
|
|
bFormatTarget = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
wMaxRootEntries = (fDiskType == DISKTYPE_DMF) ?
|
|
MAX_ROOT_ENTRIES_DMF :
|
|
MAX_ROOT_ENTRIES_STD;
|
|
|
|
wClusterSize = (fDiskType == DISKTYPE_DMF) ?
|
|
CLUSTER_SIZE_DMF :
|
|
CLUSTER_SIZE_STD;
|
|
}
|
|
|
|
|
|
void ParseSources( PPARAM pParamList ) {
|
|
|
|
//
|
|
// All remaining pParamList entries should be source file
|
|
// specifiers. Each separate parameter should be expanded
|
|
// and sorted, then output in that order.
|
|
//
|
|
|
|
CHAR chSourcePath[ MAX_PATH ];
|
|
CHAR chSourceFileName[ MAX_PATH ];
|
|
UINT nFiles, nSourceSpecifiers, nFilesThisSource;
|
|
LPSTR pszFileNameInSourcePath;
|
|
LPSTR pszTargetFileName;
|
|
PFILENODE pTargetDirForThisSource;
|
|
LPSTR pszPreviousTargetDir, p;
|
|
DWORD dwAttrib;
|
|
PPARAM pPrev, pParam;
|
|
BOOL bAbortAfterScan = FALSE;
|
|
|
|
nFiles = 0;
|
|
nSourceSpecifiers = 0;
|
|
pszPreviousTargetDir = NULL;
|
|
pTargetDirForThisSource = NULL;
|
|
|
|
pRootDir = NewFileNode();
|
|
pRootDir->pszTargetFileName = DuplicateString( "" );
|
|
pRootDir->dwWin32Attributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
pRootDir->ftWin32TimeStamp = ftGlobalFileTime;
|
|
|
|
if ( pParamList->pNext == NULL ) {
|
|
ErrorMessage( "No source parameters specified\n" );
|
|
return;
|
|
}
|
|
|
|
ErrorMessage( "Scanning source files..." );
|
|
|
|
for ( pPrev = pParamList, pParam = pParamList->pNext;
|
|
pParam != NULL;
|
|
pPrev = pParam, pParam = pParam->pNext ) {
|
|
|
|
++nSourceSpecifiers;
|
|
|
|
if (( ! pParam->pszTargetDir ) ||
|
|
( ! *pParam->pszTargetDir )) {
|
|
ErrorExit( "\nASSERTION FAILURE: pParam->pszTargetDir\n" );
|
|
}
|
|
|
|
//
|
|
// If target dir is root (0x005C == "\"), don't add it.
|
|
//
|
|
// If target dir is same as previous target dir, no point
|
|
// in trying to add it to the list again because we can
|
|
// assume it's already there (merely an optimization).
|
|
//
|
|
|
|
if ( pParam->pszTargetDir != pszPreviousTargetDir ) {
|
|
|
|
if ( *(UNALIGNED WORD *)( pParam->pszTargetDir ) == 0x005C )
|
|
pTargetDirForThisSource = pRootDir;
|
|
else
|
|
AddDirectoryToList( pParam->pszTargetDir,
|
|
pRootDir,
|
|
nSourceSpecifiers,
|
|
&pTargetDirForThisSource );
|
|
|
|
pszPreviousTargetDir = pParam->pszTargetDir;
|
|
}
|
|
|
|
if (( pszTargetFileName = strchr( pParam->pszParam, '=' )) != NULL ) {
|
|
|
|
//
|
|
// source\foo.txt=bar.txt
|
|
//
|
|
// foo.txt becomes bar.txt on target
|
|
//
|
|
|
|
*pszTargetFileName++ = '\0';
|
|
|
|
if ( strchr( pszTargetFileName, '=' )) {
|
|
ErrorMessage( "\n%s: invalid target file name",
|
|
pParam->pszParam );
|
|
bAbortAfterScan = TRUE;
|
|
}
|
|
|
|
if ( *pszTargetFileName == '\\' ) {
|
|
pTargetDirForThisSource = pRootDir;
|
|
pszPreviousTargetDir = NULL;
|
|
do ++pszTargetFileName;
|
|
while ( *pszTargetFileName == '\\' );
|
|
}
|
|
|
|
while (( p = strchr( pszTargetFileName, '\\' )) != NULL ) {
|
|
|
|
do *p++ = '\0';
|
|
while ( *p == '\\' );
|
|
|
|
AddDirectoryToList( pszTargetFileName,
|
|
pTargetDirForThisSource,
|
|
nSourceSpecifiers,
|
|
&pTargetDirForThisSource );
|
|
|
|
pszPreviousTargetDir = NULL;
|
|
|
|
pszTargetFileName = p;
|
|
}
|
|
|
|
if ( *pszTargetFileName == '\0' )
|
|
pszTargetFileName = NULL;
|
|
|
|
}
|
|
|
|
if ( GetFullPathName( pParam->pszParam,
|
|
MAX_PATH,
|
|
chSourcePath,
|
|
&pszFileNameInSourcePath )) {
|
|
|
|
if ( pszFileNameInSourcePath ) {
|
|
|
|
dwAttrib = GetFileAttributes( chSourcePath );
|
|
|
|
if (( dwAttrib != 0xFFFFFFFF ) &&
|
|
( dwAttrib & FILE_ATTRIBUTE_DIRECTORY )) {
|
|
|
|
strcat( chSourcePath, "\\" );
|
|
strcpy( chSourceFileName, "*" );
|
|
}
|
|
else {
|
|
strcpy( chSourceFileName, pszFileNameInSourcePath );
|
|
*pszFileNameInSourcePath = '\0';
|
|
}
|
|
}
|
|
else {
|
|
strcpy( chSourceFileName, "*" );
|
|
}
|
|
|
|
if (( pszTargetFileName != NULL ) &&
|
|
(( strchr( chSourceFileName, '*' )) ||
|
|
( strchr( chSourceFileName, '?' )))) {
|
|
|
|
ErrorMessage( "\n%s: cannot rename wildcard",
|
|
pParam->pszParam );
|
|
bAbortAfterScan = TRUE;
|
|
}
|
|
|
|
nFilesThisSource = ParseSourceParameter( pTargetDirForThisSource,
|
|
chSourcePath,
|
|
chSourceFileName,
|
|
pszTargetFileName,
|
|
pParam->bRecurse,
|
|
nSourceSpecifiers );
|
|
|
|
if ( nFilesThisSource == 0 ) {
|
|
ErrorMessage( "\n%s: file not found",
|
|
pParam->pszParam );
|
|
bAbortAfterScan = TRUE;
|
|
}
|
|
|
|
nFiles += nFilesThisSource;
|
|
|
|
}
|
|
else {
|
|
ErrorMessage( "\n%s: invalid file specifier",
|
|
pParam->pszParam );
|
|
bAbortAfterScan = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( bAbortAfterScan )
|
|
ErrorExit( "\n" );
|
|
|
|
if ( nSourceSpecifiers > 0 ) {
|
|
if ((( ! bIncludeEmptyDirectories ) && ( TrimTheTree( pRootDir )))
|
|
|| ( ! pRootDir->pFirstFile )) {
|
|
|
|
ErrorExit( "\nNo files to copy\n" );
|
|
|
|
}
|
|
}
|
|
|
|
ErrorMessage( "done\n" );
|
|
}
|
|
|
|
|
|
UINT ParseSourceParameter( PFILENODE pParentDir,
|
|
LPSTR pszSourcePath,
|
|
LPSTR pszSourceFileName,
|
|
LPSTR pszTargetFileName,
|
|
BOOL bRecursive,
|
|
UINT nSourceIndex ) {
|
|
|
|
LPSTR pszNewPortionOfSourcePath;
|
|
LPSTR pszAllocatedSourcePath;
|
|
PFILENODE pThisDir;
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hFind;
|
|
UINT nFilesInThisDir = 0;
|
|
UINT nFilesInSubDirs = 0;
|
|
UINT nPathLength;
|
|
|
|
//
|
|
// Put SourcePath in allocated memory so can reference
|
|
// through file node later.
|
|
//
|
|
// Optimization: would be better to search list of known
|
|
// source paths and reuse them rather than malloc'ing new
|
|
// buffer regardless. Oh well, memory is cheap, right?
|
|
//
|
|
|
|
nPathLength = strlen( pszSourcePath );
|
|
pszAllocatedSourcePath = MyAlloc( nPathLength + 1 );
|
|
memcpy( pszAllocatedSourcePath, pszSourcePath, nPathLength + 1 );
|
|
|
|
//
|
|
// We're going to modify the memory at pszSourcePath, but we'll
|
|
// restore it before returning. Saves having MAX_PATH buffers
|
|
// on the stack for each recursion. We'll set a marker,
|
|
// pszNewPortionOfSourcePath, to the end of the original
|
|
// string, and we'll only be modifying beyond that, so all
|
|
// we have to do before returning is restore the null at
|
|
// this address.
|
|
//
|
|
|
|
pszNewPortionOfSourcePath = pszSourcePath + nPathLength;
|
|
|
|
|
|
//
|
|
// Now we start on find loop for files in SourcePath that
|
|
// match pszFileName pattern. Note we're extending the
|
|
// pszSourcePath string at the NewPortionOf... location.
|
|
//
|
|
|
|
strcpy( pszNewPortionOfSourcePath, pszSourceFileName );
|
|
|
|
hFind = FindFirstFile( pszSourcePath, &fd );
|
|
|
|
if ( hFind != INVALID_HANDLE_VALUE ) {
|
|
|
|
do {
|
|
|
|
//
|
|
// ignore directories and hidden files. If we're recursive
|
|
// descent, we'll pick up directories on next find pass
|
|
//
|
|
|
|
if (( ! ( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )) &&
|
|
(( ! ( fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN )) ||
|
|
( bCopyAttributes ))) {
|
|
|
|
AddFileToList( &fd,
|
|
pParentDir,
|
|
pszAllocatedSourcePath,
|
|
pszTargetFileName,
|
|
nSourceIndex,
|
|
NULL );
|
|
|
|
++nFilesInThisDir;
|
|
|
|
}
|
|
}
|
|
while ( FindNextFile( hFind, &fd ));
|
|
|
|
FindClose( hFind );
|
|
|
|
if ( nFilesInThisDir == 0 ) {
|
|
|
|
//
|
|
// Well, we can at least free up the alloc'd path
|
|
// since nobody is pointing at it.
|
|
//
|
|
|
|
MyFree( pszAllocatedSourcePath );
|
|
|
|
}
|
|
}
|
|
|
|
if ( bRecursive ) {
|
|
|
|
//
|
|
// Now we go back through this directory looking for directories
|
|
// so we use "*" as the file specifier.
|
|
//
|
|
|
|
strcpy( pszNewPortionOfSourcePath, "*" );
|
|
|
|
hFind = FindFirstFile( pszSourcePath, &fd );
|
|
|
|
if ( hFind != INVALID_HANDLE_VALUE ) {
|
|
|
|
do {
|
|
|
|
//
|
|
// we're only looking for directories here, but not
|
|
// "." and ".." since we'll handle those separately.
|
|
//
|
|
|
|
if (( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) &&
|
|
( ! ( IsFileNameDotOrDotDot( fd.cFileName ))) &&
|
|
(( ! ( fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN )) ||
|
|
( bCopyAttributes ))) {
|
|
|
|
//
|
|
// Note that we're re-using the file pattern buffer here
|
|
// even though it was the buffer supplied to the
|
|
// FindFirstFile function and we're still doing
|
|
// FindNextFile calls -- the API spec doesn't say that
|
|
// the FindFirstFile pattern buffer must remain intact
|
|
// through subsequent FindNextFile calls.
|
|
//
|
|
|
|
AddFileToList( &fd,
|
|
pParentDir,
|
|
NULL,
|
|
NULL,
|
|
nSourceIndex,
|
|
&pThisDir );
|
|
|
|
strcpy( pszNewPortionOfSourcePath, fd.cFileName );
|
|
strcat( pszNewPortionOfSourcePath, "\\" );
|
|
|
|
nFilesInSubDirs = ParseSourceParameter( pThisDir,
|
|
pszSourcePath,
|
|
pszSourceFileName,
|
|
pszTargetFileName,
|
|
bRecursive,
|
|
nSourceIndex );
|
|
|
|
nFilesInThisDir += nFilesInSubDirs;
|
|
|
|
}
|
|
}
|
|
|
|
while ( FindNextFile( hFind, &fd ));
|
|
|
|
FindClose( hFind );
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now restore the source strings to their original
|
|
// contents by replacing the NULLs at their original
|
|
// locations (we didn't modify anything preceding the
|
|
// NULLS).
|
|
//
|
|
|
|
*pszNewPortionOfSourcePath = '\0';
|
|
|
|
return nFilesInThisDir;
|
|
|
|
}
|
|
|
|
|
|
BOOL AddFileToList( WIN32_FIND_DATA *fd,
|
|
PFILENODE pParentDir,
|
|
LPSTR pszSourcePath,
|
|
LPSTR pszTargetFileName,
|
|
UINT nSourceIndex,
|
|
PFILENODE *pThisNode ) {
|
|
|
|
PFILENODE pPrev, pNext, pNew;
|
|
CHAR chSourceFileName[ MAX_PATH ];
|
|
CHAR chTargetFileName[ MAX_PATH ];
|
|
LPSTR pExtension;
|
|
UINT nLength;
|
|
INT iCompare;
|
|
|
|
if ( *fd->cAlternateFileName )
|
|
strcpy( chSourceFileName, fd->cAlternateFileName );
|
|
else
|
|
strcpy( chSourceFileName, fd->cFileName );
|
|
|
|
if ( pszTargetFileName )
|
|
strcpy( chTargetFileName, pszTargetFileName );
|
|
else
|
|
strcpy( chTargetFileName, chSourceFileName );
|
|
|
|
_strupr( chTargetFileName );
|
|
pExtension = strchr( chTargetFileName, '.' );
|
|
|
|
if ((( pExtension == NULL ) && ( strlen( chTargetFileName ) > 8 )) ||
|
|
(( pExtension ) &&
|
|
(( pExtension - chTargetFileName > 8 ) ||
|
|
( strlen( pExtension + 1 ) > 3 )))) {
|
|
|
|
ErrorExit( "\n%s: invalid filename\n", chTargetFileName );
|
|
}
|
|
|
|
for ( pPrev = NULL, pNext = pParentDir->pFirstFile;
|
|
pNext != NULL;
|
|
pPrev = pNext, pNext = pNext->pNextFile ) {
|
|
|
|
iCompare = strcmp( pNext->pszTargetFileName, chTargetFileName );
|
|
|
|
if ( ! iCompare ) {
|
|
|
|
//
|
|
// This file target name already exists. If the new and
|
|
// existing entries are both directories, ignore the new
|
|
// entry and just return the existing one. If the new
|
|
// and existing entries aren't both directories, compare
|
|
// attributes, filesize, and timestamps to verify that it
|
|
// is the same file -- if so, ignore new entry and return
|
|
// existing one.
|
|
//
|
|
|
|
if (( ! ( pNext->dwWin32Attributes &
|
|
fd->dwFileAttributes &
|
|
FILE_ATTRIBUTE_DIRECTORY )) &&
|
|
(( pNext->dwWin32Attributes != fd->dwFileAttributes ) ||
|
|
( pNext->dwFileSize != fd->nFileSizeLow ) ||
|
|
( pNext->ftWin32TimeStamp.dwLowDateTime != fd->ftLastWriteTime.dwLowDateTime ))) {
|
|
|
|
ErrorExit( "\n%s: different file already specified with same name\n",
|
|
chSourceFileName );
|
|
}
|
|
|
|
if ( pThisNode )
|
|
*pThisNode = pNext;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
else if (( pNext->nSourceIndex >= nSourceIndex ) &&
|
|
( iCompare > 0 )) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
pNew = NewFileNode();
|
|
pNew->pszTargetFileName = DuplicateString( chTargetFileName );
|
|
pNew->pszSourceFileName = DuplicateString( chSourceFileName );
|
|
pNew->pszSourcePath = pszSourcePath;
|
|
pNew->dwFileSize = fd->nFileSizeLow;
|
|
pNew->dwWin32Attributes = fd->dwFileAttributes;
|
|
pNew->ftWin32TimeStamp = fd->ftLastWriteTime;
|
|
pNew->nSourceIndex = nSourceIndex;
|
|
pNew->pParentDir = pParentDir;
|
|
pNew->pNextFile = pNext;
|
|
|
|
if ( pPrev )
|
|
pPrev->pNextFile = pNew;
|
|
else
|
|
pParentDir->pFirstFile = pNew;
|
|
|
|
if ( pThisNode )
|
|
*pThisNode = pNew;
|
|
|
|
nLength = ( pszSourcePath ? strlen( pszSourcePath ) : 0 )
|
|
+ strlen( chSourceFileName );
|
|
|
|
if ( nLength > nMaxSourceLength )
|
|
nMaxSourceLength = nLength;
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
void ParseOptions( PPARAM pParamList ) {
|
|
|
|
PPARAM pPrev, pParam;
|
|
LPSTR pCurrentTargetDir;
|
|
BOOL bNextPatternIsRecursive;
|
|
char *p;
|
|
|
|
//
|
|
// The option arguments may appear anywhere in the command line
|
|
// (beginning, end, middle, mixed), so for each option we encounter,
|
|
// we'll remove it after processing it. Then, the other Parse
|
|
// routines can loop through the whole parameter list again.
|
|
//
|
|
|
|
pCurrentTargetDir = "\\";
|
|
bNextPatternIsRecursive = FALSE;
|
|
|
|
pPrev = pParamList;
|
|
pParam = pParamList->pNext;
|
|
|
|
while ( pParam ) {
|
|
|
|
p = pParam->pszParam;
|
|
|
|
if (( p ) && (( *p == '-' ) || ( *p == '/' ))) { // process flags
|
|
|
|
++p;
|
|
|
|
switch( tolower( *p )) {
|
|
|
|
case '?': // help (usage)
|
|
case 'h':
|
|
Usage();
|
|
break;
|
|
|
|
case 't': // timestamp
|
|
|
|
if ( *( ++p )) {
|
|
ParseTimeStamp( p );
|
|
}
|
|
else if ( pParam->pNext ) {
|
|
ParseTimeStamp( pParam->pNext->pszParam );
|
|
RemoveParam( pParam->pNext, pParam );
|
|
}
|
|
break;
|
|
|
|
case 'l': // volume label
|
|
|
|
if ( *( ++p )) {
|
|
strncpy( chLabel, p, 11 );
|
|
}
|
|
else if ( pParam->pNext ) {
|
|
strncpy( chLabel, pParam->pNext->pszParam, 11 );
|
|
RemoveParam( pParam->pNext, pParam );
|
|
}
|
|
_strupr( chLabel );
|
|
break;
|
|
|
|
case 'g': // tagfile
|
|
|
|
if ( *( ++p )) {
|
|
strncpy( chTagFile, p, 11 );
|
|
}
|
|
else if ( pParam->pNext ) {
|
|
strncpy( chTagFile, pParam->pNext->pszParam, 11 );
|
|
RemoveParam( pParam->pNext, pParam );
|
|
}
|
|
_strupr( chTagFile );
|
|
break;
|
|
|
|
case 'd': // double write
|
|
bDoubleWrite = TRUE;
|
|
break;
|
|
|
|
case 'f': // format first
|
|
bFormatTarget = TRUE;
|
|
break;
|
|
|
|
case 'm': // format 3.5" HD floppy as 1.7MB DMF
|
|
bFormatTargetDMF = TRUE;
|
|
fDiskType = DISKTYPE_DMF;
|
|
break;
|
|
|
|
case 'w': // mark DMF target writable
|
|
bWritableDMF = TRUE;
|
|
break;
|
|
|
|
case 'v': // verify target
|
|
bVerifyTarget = TRUE;
|
|
break;
|
|
|
|
case 'a': // copy file attributes
|
|
bCopyAttributes = TRUE;
|
|
break;
|
|
|
|
case 'z': // zero unused sectors
|
|
bZeroSlack = TRUE;
|
|
break;
|
|
|
|
case '3': // target is 3.5" HD
|
|
fDiskType = DISKTYPE_35;
|
|
break;
|
|
|
|
case '5': // target is 5.25" HD
|
|
fDiskType = DISKTYPE_525;
|
|
break;
|
|
|
|
case '8': // target is 3.5" HD DMF (1.7MB)
|
|
fDiskType = DISKTYPE_DMF;
|
|
bFormatTargetDMF = TRUE;
|
|
break;
|
|
|
|
case 'b': // boot sector specifier
|
|
|
|
if ( *( ++p )) {
|
|
strcpy( chBootSpecifier, p );
|
|
}
|
|
else if ( pParam->pNext ) {
|
|
strcpy( chBootSpecifier, pParam->pNext->pszParam );
|
|
RemoveParam( pParam->pNext, pParam );
|
|
}
|
|
break;
|
|
|
|
case 's': // include subdirs
|
|
bNextPatternIsRecursive = TRUE;
|
|
if ( *( ++p )) {
|
|
pParam->pszParam = p; // file pattern here
|
|
continue; // parse this pParam again
|
|
}
|
|
break;
|
|
|
|
case 'e': // include empty subs
|
|
|
|
bIncludeEmptyDirectories = TRUE;
|
|
bNextPatternIsRecursive = TRUE;
|
|
|
|
//
|
|
// note that /e may be specified with or without /s,
|
|
// so for either of them, mark recursive for following
|
|
// source specifier.
|
|
//
|
|
|
|
if ( *( ++p )) {
|
|
pParam->pszParam = p; // file pattern here
|
|
continue; // parse this pParam again
|
|
}
|
|
break;
|
|
|
|
case 'x': // target subdirectory
|
|
|
|
if ( *( ++p )) {
|
|
pCurrentTargetDir = p;
|
|
}
|
|
else if ( pParam->pNext ) {
|
|
pCurrentTargetDir = pParam->pNext->pszParam;
|
|
RemoveParam( pParam->pNext, pParam );
|
|
}
|
|
break;
|
|
|
|
default: // say what?!!!
|
|
ErrorExit( "\ninvalid option \"-%c\"\n", *p );
|
|
|
|
}
|
|
|
|
pParam = RemoveParam( pParam, pPrev );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
//
|
|
// Assume this is a file pattern argument.
|
|
//
|
|
|
|
pParam->pszTargetDir = pCurrentTargetDir;
|
|
pParam->bRecurse = bNextPatternIsRecursive;
|
|
|
|
bNextPatternIsRecursive = FALSE;
|
|
|
|
pPrev = pParam;
|
|
pParam = pParam->pNext;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ParseTimeStamp( LPSTR pArg ) {
|
|
|
|
//
|
|
// Expected form: month/day/year/hour/minute/second
|
|
// with any non-digit character being a delimiter.
|
|
//
|
|
|
|
UINT i, arg[ 6 ] = { 0, 0, 0, 0, 0, 0 };
|
|
char *p = pArg;
|
|
|
|
if ( p ) {
|
|
|
|
for ( i = 0; i < 6; i++ ) {
|
|
|
|
while ( IsDigit( *p ))
|
|
arg[ i ] = ( arg[ i ] * 10 ) + ( *p++ - '0' );
|
|
|
|
while (( *p ) && ( ! IsDigit( *p ))) ++p;
|
|
|
|
}
|
|
|
|
if ( arg[ 2 ] < 50 ) // if year is 00 to 49, assume 2000 to 2049
|
|
arg[ 2 ] += 2000;
|
|
|
|
if ( arg[ 2 ] < 100 ) // if year is 50 to 99, assume 1950 to 1999
|
|
arg[ 2 ] += 1900;
|
|
|
|
if (( arg[ 0 ] < 1 ) || ( arg[ 0 ] > 12 ) || // month
|
|
( arg[ 1 ] < 1 ) || ( arg[ 1 ] > 31 ) || // day
|
|
( arg[ 2 ] < 1980 ) || ( arg[ 2 ] > 2107 ) || // year
|
|
( arg[ 3 ] > 23 ) || // hour
|
|
( arg[ 4 ] > 59 ) || // minute
|
|
( arg[ 5 ] > 59 )) { // second
|
|
|
|
ErrorExit(
|
|
"\nInvalid time specified: %02d/%02d/%04d,%02d:%02d:%02d\n",
|
|
arg[ 0 ],
|
|
arg[ 1 ],
|
|
arg[ 2 ],
|
|
arg[ 3 ],
|
|
arg[ 4 ],
|
|
arg[ 5 ]
|
|
);
|
|
|
|
}
|
|
|
|
wDosDate = ((( arg[ 2 ] - 1980 ) << 9 ) & 0xFE00 ) | // years since 1980 (0..127)
|
|
(( arg[ 0 ] << 5 ) & 0x01E0 ) | // month (1..12)
|
|
( arg[ 1 ] & 0x001F ); // day (1..31)
|
|
|
|
wDosTime = (( arg[ 3 ] << 11 ) & 0xF800 ) | // hour (0..23)
|
|
(( arg[ 4 ] << 5 ) & 0x07E0 ) | // minute (0..59)
|
|
(( arg[ 5 ] >> 1 ) & 0x001F ); // seconds/2 (0..29)
|
|
}
|
|
|
|
bUseGlobalTime = TRUE;
|
|
|
|
}
|
|
|
|
void Usage( void ) {
|
|
|
|
ErrorExit(
|
|
|
|
"\n"
|
|
"fcopy [options] [@response] [[-s] source[=rename] ...] destination\n"
|
|
"\n"
|
|
" Options: -l -g -t -a -b -w -e -z -d -f -m -v -3 -5 -8\n"
|
|
"\n"
|
|
" Multiple sources can be specified (order preserved, alphabetically sorted\n"
|
|
" within wildcard specifiers). Destination may be floppy drive (A:, B:), or\n"
|
|
" the name of a floppy image file to create. A response file (@filename) can\n"
|
|
" be used to specify options rather than lengthy command line. Environment\n"
|
|
" variables are expanded in the response file same as command line. Rename\n"
|
|
" specifier can be full or partial target path and/or filename.\n"
|
|
"\n"
|
|
" Common options:\n"
|
|
"\n"
|
|
" -l volume label (e.g. -l DISK1)\n"
|
|
" -g create zero-length tagfile in root directory (e.g. -g DISK1)\n"
|
|
" -t time stamp for all files month/day/year/hour/minute/second\n"
|
|
" (no spaces, e.g. -t 12/31/91,15:01:00)\n"
|
|
" -a copy file attributes vs. ignoring hidden files/directories\n"
|
|
" -b boot sector specifier, can be filename or one of hardcoded:\n"
|
|
" \"DOS\" (MS-DOS 5.0, default), \"NT31SETUP\", \"NT35SETUP\"\n"
|
|
" -w (DMF only) mark target as writable versus write-protected\n"
|
|
" -s include subdirectories for next source specifier\n"
|
|
" -e include empty subdirectories as well (for all sources)\n"
|
|
" -x specify a subdirectory on the target for subsequent files\n"
|
|
" (e.g. put subsequent files in i386 directory: -x i386)\n"
|
|
"\n"
|
|
" Options when destination is a physical floppy disk:\n"
|
|
" -z zero-fill unused sectors vs. only writing necessary sectors\n"
|
|
" -d double-write mode writes each sector twice\n"
|
|
" -f format target disk prior to writing data (standard format)\n"
|
|
" -m format target 3.5\" HD floppy with 1.7MB DMF format\n"
|
|
" -v verify (read and compare) destination after writing\n"
|
|
"\n"
|
|
" Options when destination is a floppy image file:\n"
|
|
" -3 specify target is 3.5\" HD 1.44MB floppy (default)\n"
|
|
" -5 specify target is 5.25\" HD 1.2MB floppy\n"
|
|
" -8 specify target is 3.5\" HD 1.7MB DMF floppy\n"
|
|
"\n"
|
|
" For Microsoft internal use only -- DMF is proprietary and protected.\n"
|
|
"\n"
|
|
);
|
|
}
|
|
|
|
|
|
BOOL IsDigit( char ch ) {
|
|
|
|
if (( ch >= '0' ) && ( ch <= '9' ))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void ErrorMessage( const char *szFormat, ... ) {
|
|
va_list vaArgs;
|
|
|
|
va_start( vaArgs, szFormat );
|
|
vfprintf( stdout, szFormat, vaArgs );
|
|
va_end( vaArgs );
|
|
}
|
|
|
|
|
|
void ErrorExit( const char *szFormat, ... ) {
|
|
va_list vaArgs;
|
|
|
|
va_start( vaArgs, szFormat );
|
|
vfprintf( stdout, szFormat, vaArgs );
|
|
va_end( vaArgs );
|
|
|
|
exit( 1 ); // can't call ExitProcess since C-runtime not flushed
|
|
}
|
|
|
|
|
|
UINT Min( UINT a, UINT b ) {
|
|
if ( a < b )
|
|
return a;
|
|
else
|
|
return b;
|
|
}
|
|
|
|
|
|
void ReWriteDestination( LPSTR pszMessage ) {
|
|
|
|
PUCHAR pData = pWholeDiskBuffer;
|
|
UINT nBytes, nActual;
|
|
DWORD dwGLE;
|
|
|
|
ErrorMessage( pszMessage );
|
|
|
|
OverlappedVerifyComplete( hTarget );
|
|
|
|
nBytes = pWholeDiskCurrentLocation - pWholeDiskBuffer;
|
|
|
|
#ifdef THE_OLD_WAY
|
|
|
|
UINT i, nClusters, nBuffers;
|
|
|
|
OverlappedSetFilePointer( hTarget, 0 );
|
|
SetFilePointer( hTarget, 0, NULL, FILE_BEGIN );
|
|
|
|
nClusters = ROUNDUP2( nBytes, wClusterSize ) / wClusterSize;
|
|
nBuffers = nClusters / ( BUFFER_SIZE / wClusterSize );
|
|
|
|
for ( i = 0; i < nBuffers; i++ ) {
|
|
OverlappedWriteFile( hTarget, pData, BUFFER_SIZE );
|
|
pData += BUFFER_SIZE;
|
|
}
|
|
|
|
nClusters -= ( nBuffers * ( BUFFER_SIZE / wClusterSize ));
|
|
|
|
if ( nClusters )
|
|
OverlappedWriteFile( hTarget, pData, wClusterSize * nClusters );
|
|
|
|
#endif // THE_OLD_WAY
|
|
|
|
olControl.Offset = 0;
|
|
olControl.OffsetHigh = 0;
|
|
|
|
if ( WriteFile( hTarget,
|
|
pWholeDiskBuffer,
|
|
nBytes,
|
|
&nActual,
|
|
&olControl )) {
|
|
|
|
//
|
|
// completed synchronously
|
|
//
|
|
|
|
if ( nActual != nBytes )
|
|
ErrorExit( "\nAssert: nActual != nBytes\n" );
|
|
|
|
}
|
|
|
|
else if (( dwGLE = GetLastError() ) == ERROR_IO_PENDING ) {
|
|
|
|
//
|
|
// asynchronous -- wait for completion
|
|
//
|
|
|
|
if ( GetOverlappedResult( hTarget,
|
|
&olControl,
|
|
&nActual,
|
|
TRUE )) {
|
|
|
|
//
|
|
// completed, no error
|
|
//
|
|
|
|
if ( nActual != nBytes )
|
|
ErrorExit( "\nAssert: nActual != nBytes\n" );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
//
|
|
// asynchronous error
|
|
//
|
|
|
|
ErrorExit( "\nError writing %s %s (GLE=%d)\n",
|
|
bDestIsDevice ? "device" : "file",
|
|
bDestIsDevice ? chDest + 4 : chDest,
|
|
GetLastError() );
|
|
}
|
|
}
|
|
|
|
else {
|
|
|
|
//
|
|
// error
|
|
//
|
|
|
|
ErrorExit( "\nError writing %s %s (GLE=%d)\n",
|
|
bDestIsDevice ? "device" : "file",
|
|
bDestIsDevice ? chDest + 4 : chDest,
|
|
dwGLE );
|
|
}
|
|
|
|
ErrorMessage( "done\n" );
|
|
|
|
}
|
|
|
|
|
|
void FormatTarget( void ) {
|
|
|
|
DWORD dwGLE;
|
|
BOOL bSuccess;
|
|
FORMAT_PARAMETERS fp;
|
|
|
|
if ( fDiskType == DISKTYPE_DMF ) {
|
|
ErrorMessage( "Formatting (DMF)..." );
|
|
} else {
|
|
ErrorMessage( "Formatting..." );
|
|
}
|
|
|
|
#ifdef DONTCOMPILE // now leaving hDevice open as global
|
|
hDevice = CreateFile( chDest, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL );
|
|
|
|
if ( hDevice == INVALID_HANDLE_VALUE )
|
|
ErrorExit( "\nCould not open device %s (GLE=%d)\n",
|
|
chDest + 4,
|
|
GetLastError() );
|
|
#endif
|
|
|
|
if ( fDiskType == DISKTYPE_DMF ) {
|
|
|
|
bSuccess = DmfFormatTracks( hDevice, &dwGLE );
|
|
|
|
} else if ( fDiskType == DISKTYPE_525 ) {
|
|
fp.MediaType = F5_1Pt2_512;
|
|
} else if ( fDiskType == DISKTYPE_35 ) {
|
|
fp.MediaType = F3_1Pt44_512;
|
|
} else {
|
|
ErrorExit( "\nBad media type\n" );
|
|
}
|
|
|
|
if ( fDiskType != DISKTYPE_DMF) {
|
|
|
|
fp.StartCylinderNumber = 0;
|
|
fp.EndCylinderNumber = 79;
|
|
fp.StartHeadNumber = 0;
|
|
fp.EndHeadNumber = 1;
|
|
|
|
bSuccess = DeviceIoControl( hDevice,
|
|
IOCTL_DISK_FORMAT_TRACKS,
|
|
&fp,
|
|
sizeof( fp ),
|
|
NULL,
|
|
0,
|
|
&dwGLE,
|
|
NULL );
|
|
|
|
dwGLE = GetLastError();
|
|
}
|
|
|
|
#ifdef DONTCOMPILE // now leaving hDevice open as global
|
|
CloseHandle( hDevice );
|
|
#endif
|
|
|
|
if ( ! bSuccess ) {
|
|
|
|
if ( dwGLE == ERROR_NOT_READY )
|
|
ErrorExit( "\nDevice %s not ready\n", chDest + 4 );
|
|
|
|
ErrorExit( "\nCould not format disk in drive %s (GLE=%d)\n",
|
|
chDest + 4,
|
|
GetLastError() );
|
|
}
|
|
|
|
ErrorMessage( "done\n" );
|
|
}
|
|
|
|
|
|
void VerifyDestination( void ) {
|
|
|
|
UINT i, nBytes, nClusters, nBuffers;
|
|
PUCHAR pData = pWholeDiskBuffer;
|
|
DWORD dwBytes, dwOffset, dwBufferOffset;
|
|
|
|
ErrorMessage( "Verifying..." );
|
|
|
|
OverlappedSetFilePointer( hTarget, 0 );
|
|
|
|
nBytes = pWholeDiskCurrentLocation - pWholeDiskBuffer;
|
|
|
|
nClusters = ROUNDUP2( nBytes, wClusterSize ) / wClusterSize;
|
|
nBuffers = nClusters / ( BUFFER_SIZE / wClusterSize );
|
|
dwOffset = 0;
|
|
|
|
for ( i = 0; i < nBuffers; i++ ) {
|
|
|
|
OverlappedReadFile( hTarget, pAlignedBuffer, BUFFER_SIZE, &dwBytes );
|
|
OverlappedVerifyComplete( hTarget );
|
|
|
|
dwBufferOffset = CompareAligned( pAlignedBuffer, pData, BUFFER_SIZE );
|
|
if ( dwBufferOffset != BUFFER_SIZE )
|
|
ErrorExit( "\nVerification error mismatch (offset=0x%08X)\n",
|
|
dwOffset + dwBufferOffset );
|
|
|
|
dwOffset += BUFFER_SIZE;
|
|
pData += BUFFER_SIZE;
|
|
}
|
|
|
|
nClusters -= ( nBuffers * ( BUFFER_SIZE / wClusterSize ));
|
|
|
|
if ( nClusters ) {
|
|
|
|
OverlappedReadFile( hTarget, pAlignedBuffer, wClusterSize * nClusters, &dwBytes );
|
|
OverlappedVerifyComplete( hTarget );
|
|
|
|
dwBytes = wClusterSize * nClusters; // bytes remaining to compare
|
|
|
|
dwBufferOffset = CompareAligned( pAlignedBuffer, pData, dwBytes );
|
|
if ( dwBufferOffset != dwBytes )
|
|
ErrorExit( "\nVerification compare error (offset=0x%08X)\n",
|
|
dwOffset + dwBufferOffset );
|
|
|
|
}
|
|
|
|
ErrorMessage( "done\n" );
|
|
|
|
}
|
|
|
|
|
|
void GetBootSector( void ) {
|
|
|
|
//
|
|
// chBootSpecifier is either a filename or one of several possible
|
|
// hardcoded options which can be added here. Note that if no
|
|
// chBootSpecifier is specified, we default to DOS boot sector.
|
|
//
|
|
|
|
if (( *chBootSpecifier == '\0' ) ||
|
|
( _stricmp( chBootSpecifier, "DOS" ) == 0 ) ||
|
|
( _stricmp( chBootSpecifier, "DOS5" ) == 0 )) {
|
|
CopyMemory( BootSector, Dos5BootSector, SECTOR_SIZE );
|
|
}
|
|
else if ( _stricmp( chBootSpecifier, "NT31SETUP" ) == 0 ) {
|
|
CopyMemory( BootSector, Nt31SetupBootSector, SECTOR_SIZE );
|
|
}
|
|
else if ( _stricmp( chBootSpecifier, "NT35SETUP" ) == 0 ) {
|
|
CopyMemory( BootSector, Nt35SetupBootSector, SECTOR_SIZE );
|
|
}
|
|
else {
|
|
|
|
HANDLE hFile;
|
|
DWORD dwBytes;
|
|
|
|
hFile = CreateFile( chBootSpecifier,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL );
|
|
|
|
if ( hFile == INVALID_HANDLE_VALUE )
|
|
ErrorExit( "\nCannot open boot sector file %s (GLE=%d)\n",
|
|
chBootSpecifier,
|
|
GetLastError() );
|
|
|
|
if ( ! ReadFile( hFile, BootSector, SECTOR_SIZE, &dwBytes, NULL ))
|
|
ErrorExit( "\nCannot read boot sector file %s (GLE=%d)\n",
|
|
chBootSpecifier,
|
|
GetLastError() );
|
|
|
|
if ( dwBytes != SECTOR_SIZE ) {
|
|
ErrorExit( "\nBoot sector file %s not %d bytes\n",
|
|
chBootSpecifier, SECTOR_SIZE );
|
|
}
|
|
|
|
CloseHandle( hFile );
|
|
|
|
}
|
|
}
|
|
|
|
|
|
DWORD CompareAligned( PVOID pBuffer1, PVOID pBuffer2, DWORD dwLength ) {
|
|
|
|
//
|
|
// Buffers are expected to be DWORD aligned. The return
|
|
// value is the offset of the miscompare, or dwLength if
|
|
// buffers are equivalent. The offset returned may be a
|
|
// non-DWORD aligned number such as 6 if that is the byte
|
|
// offset of the first miscompare.
|
|
//
|
|
|
|
DWORD dwOffset, dwAlignedLength;
|
|
union {
|
|
PUCHAR pch;
|
|
PDWORD pdw;
|
|
} p1, p2;
|
|
|
|
p1.pdw = pBuffer1;
|
|
p2.pdw = pBuffer2;
|
|
|
|
dwAlignedLength = dwLength & ( ~ ( sizeof( DWORD ) - 1 ));
|
|
|
|
for ( dwOffset = 0;
|
|
dwOffset < dwAlignedLength;
|
|
dwOffset += sizeof( DWORD ), p1.pdw++, p2.pdw++ ) {
|
|
|
|
if ( *p1.pdw != *p2.pdw )
|
|
break;
|
|
|
|
}
|
|
|
|
while (( dwOffset < dwLength ) && ( *p1.pch++ == *p2.pch++ ))
|
|
++dwOffset;
|
|
|
|
return dwOffset;
|
|
}
|
|
|
|
|
|
PVOID MyAllocZ( UINT nBytes ) {
|
|
PVOID pAlloc = MyAlloc( nBytes );
|
|
ZeroMemory( pAlloc, nBytes );
|
|
return pAlloc;
|
|
}
|
|
|
|
|
|
PVOID MyAlloc( UINT nBytes ) {
|
|
|
|
//
|
|
// assume single-threaded access for HEAP_NO_SERIALIZE
|
|
//
|
|
|
|
PVOID pAlloc = HeapAlloc( hProcessHeap, HEAP_NO_SERIALIZE, nBytes );
|
|
|
|
if ( pAlloc == NULL ) {
|
|
ErrorExit( "\nOut of memory\n" );
|
|
}
|
|
|
|
return pAlloc;
|
|
}
|
|
|
|
|
|
VOID MyFree( PVOID pAlloc ) {
|
|
HeapFree( hProcessHeap, HEAP_NO_SERIALIZE, pAlloc );
|
|
}
|
|
|
|
|
|
BOOL IsFileNameDotOrDotDot( LPCSTR pszFileName ) {
|
|
|
|
DWORD dwFirstFourBytes = *(UNALIGNED DWORD *)pszFileName;
|
|
|
|
//
|
|
// assume four bytes are readable even if string is only one or two bytes
|
|
// but we won't assume it's dword-aligned. A dot character is hex 2E,
|
|
// and we have to verify it's followed by NULL to qualify.
|
|
//
|
|
|
|
if ((( dwFirstFourBytes & 0x0000FFFF ) == 0x0000002E ) ||
|
|
(( dwFirstFourBytes & 0x00FFFFFF ) == 0x00002E2E ))
|
|
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
PFILENODE NewFileNode( void ) {
|
|
return MyAllocZ( sizeof( FILENODE ));
|
|
}
|
|
|
|
|
|
LPSTR DuplicateString( LPCSTR pString ) {
|
|
UINT nLength = strlen( pString ) + 1;
|
|
LPSTR pAlloc = MyAlloc( nLength );
|
|
CopyMemory( pAlloc, pString, nLength );
|
|
return pAlloc;
|
|
}
|
|
|
|
|
|
void AssignDirectoryClusters( PFILENODE pDir ) {
|
|
|
|
PFILENODE pNode;
|
|
UINT nCount;
|
|
|
|
if ( pDir->pParentDir == NULL ) {
|
|
|
|
//
|
|
// This is the root directory -- don't add . and ..
|
|
// counts, and don't allocate clusters.
|
|
//
|
|
|
|
for ( pNode = pDir->pFirstFile,
|
|
nCount = (( *chLabel ) ? 1 : 0 ) + (( *chTagFile ) ? 1 : 0 );
|
|
pNode;
|
|
pNode = pNode->pNextFile,
|
|
nCount++ );
|
|
|
|
if ( nCount > wMaxRootEntries ) {
|
|
ErrorExit( "\nMore than %d files in root directory\n",
|
|
wMaxRootEntries );
|
|
}
|
|
|
|
pDir->dwFileSize = wMaxRootEntries * 32; // 32 bytes/directory entry
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for ( pNode = pDir->pFirstFile,
|
|
nCount = 2;
|
|
pNode;
|
|
pNode = pNode->pNextFile,
|
|
nCount++ );
|
|
|
|
pDir->dwFileSize = nCount * sizeof( DIRENTRY );
|
|
pDir->dwStartingCluster = AllocateFat( pDir->dwFileSize );
|
|
|
|
}
|
|
|
|
for ( pNode = pDir->pFirstFile;
|
|
pNode;
|
|
pNode = pNode->pNextFile ) {
|
|
|
|
if ( pNode->dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY ) {
|
|
AssignDirectoryClusters( pNode );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void AssignFileClusters( PFILENODE pDir ) {
|
|
|
|
PFILENODE pNode;
|
|
|
|
for ( pNode = pDir->pFirstFile; pNode; pNode = pNode->pNextFile ) {
|
|
if ( ! ( pNode->dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY )) {
|
|
pNode->dwStartingCluster = AllocateFat( pNode->dwFileSize );
|
|
}
|
|
}
|
|
|
|
for ( pNode = pDir->pFirstFile; pNode; pNode = pNode->pNextFile ) {
|
|
|
|
if ( pNode->dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY ) {
|
|
AssignFileClusters( pNode );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DWORD AllocateFat( DWORD dwFileSize ) {
|
|
|
|
DWORD dwStartingCluster, dwNextCluster;
|
|
UINT nClusters;
|
|
|
|
if ( dwFileSize == 0 )
|
|
return 0;
|
|
|
|
nClusters = ROUNDUP2( dwFileSize, wClusterSize ) / wClusterSize;
|
|
dwStartingCluster = dwNextCluster = nNextAvailableCluster;
|
|
|
|
if (( nNextAvailableCluster += nClusters ) > nMaxFatEntries ) {
|
|
ErrorExit( "\nFiles won't fit\n" );
|
|
}
|
|
|
|
for ( ; --nClusters; dwNextCluster++ ) {
|
|
Fat[ dwNextCluster ] = (WORD)( dwNextCluster + 1 );
|
|
}
|
|
|
|
Fat[ dwNextCluster ] = 0xFFF; // 12-bit FAT EOF
|
|
|
|
return dwStartingCluster;
|
|
|
|
}
|
|
|
|
|
|
UINT CreateAndWriteDirectories( PFILENODE pDir ) {
|
|
|
|
PDIRENTRY pDirBuffer, pNextDirEntry;
|
|
DWORD dwFileAllocationSize, dwBytes;
|
|
PFILENODE pNode;
|
|
UINT nClusters;
|
|
PCHAR pWorkingBuffer;
|
|
|
|
dwFileAllocationSize = ROUNDUP2( pDir->dwFileSize, (pDir == pRootDir) ? SECTOR_SIZE : wClusterSize );
|
|
|
|
pNextDirEntry = pDirBuffer = MyAllocZ( dwFileAllocationSize );
|
|
|
|
if ( pDir == pRootDir ) {
|
|
nClusters = 0; // root directory space not allocated from FAT
|
|
if ( *chLabel ) {
|
|
CreateVolumeLabelEntry( pNextDirEntry++ );
|
|
}
|
|
if ( *chTagFile ) {
|
|
CreateTagFileEntry( pNextDirEntry++ );
|
|
}
|
|
}
|
|
else {
|
|
nClusters = dwFileAllocationSize / wClusterSize;
|
|
|
|
CreateDirEntry( pNextDirEntry++,
|
|
".",
|
|
FILE_ATTRIBUTE_DIRECTORY,
|
|
pDir->ftWin32TimeStamp,
|
|
pDir->dwStartingCluster,
|
|
0 );
|
|
|
|
CreateDirEntry( pNextDirEntry++,
|
|
"..",
|
|
FILE_ATTRIBUTE_DIRECTORY,
|
|
pDir->ftWin32TimeStamp,
|
|
pDir->pParentDir->dwStartingCluster,
|
|
0 );
|
|
}
|
|
|
|
for ( pNode = pDir->pFirstFile; pNode; pNode = pNode->pNextFile ) {
|
|
CreateDirEntry( pNextDirEntry++,
|
|
pNode->pszTargetFileName,
|
|
pNode->dwWin32Attributes,
|
|
pNode->ftWin32TimeStamp,
|
|
pNode->dwStartingCluster,
|
|
pNode->dwFileSize );
|
|
}
|
|
|
|
pWorkingBuffer = (PCHAR)pDirBuffer;
|
|
|
|
while ( dwFileAllocationSize ) {
|
|
dwBytes = Min( dwFileAllocationSize, BUFFER_SIZE );
|
|
OverlappedWriteFile( hTarget, pWorkingBuffer, dwBytes );
|
|
pWorkingBuffer += dwBytes;
|
|
dwFileAllocationSize -= dwBytes;
|
|
}
|
|
|
|
MyFree( pDirBuffer );
|
|
|
|
for ( pNode = pDir->pFirstFile; pNode; pNode = pNode->pNextFile ) {
|
|
if ( pNode->dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY ) {
|
|
nClusters += CreateAndWriteDirectories( pNode );
|
|
}
|
|
}
|
|
|
|
return nClusters;
|
|
}
|
|
|
|
|
|
void PadCopy( LPSTR pDest, LPCSTR pSource, UINT nLength ) {
|
|
|
|
if ( pSource )
|
|
for ( ; nLength && *pSource; nLength-- )
|
|
*pDest++ = *pSource++;
|
|
|
|
for ( ; nLength; nLength-- )
|
|
*pDest++ = ' ';
|
|
|
|
}
|
|
|
|
|
|
void CreateDirEntry( PDIRENTRY pDirEntry,
|
|
LPSTR pszFileName,
|
|
DWORD dwWin32Attributes,
|
|
FILETIME ftWin32TimeStamp,
|
|
DWORD dwStartingCluster,
|
|
DWORD dwFileSize ) {
|
|
|
|
CHAR chNameBuffer[ MAX_PATH ];
|
|
FILETIME ftLocalFileTime;
|
|
PCHAR pExtension;
|
|
BYTE bDosAttribute;
|
|
|
|
strcpy( chNameBuffer, pszFileName );
|
|
pExtension = strchr( chNameBuffer, '.' );
|
|
if ( pExtension ) {
|
|
if ( pExtension == chNameBuffer )
|
|
pExtension = NULL; // . or .. so leave alone
|
|
else
|
|
*pExtension++ = '\0';
|
|
}
|
|
PadCopy( pDirEntry->BaseName, chNameBuffer, 8 );
|
|
PadCopy( pDirEntry->Extension, pExtension, 3 );
|
|
|
|
bDosAttribute = DOS_ATTRIBUTE_NORMAL;
|
|
|
|
if ( dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY )
|
|
bDosAttribute |= DOS_ATTRIBUTE_DIRECTORY;
|
|
|
|
if ( bCopyAttributes ) {
|
|
if ( dwWin32Attributes & FILE_ATTRIBUTE_READONLY )
|
|
bDosAttribute |= DOS_ATTRIBUTE_READONLY;
|
|
if ( dwWin32Attributes & FILE_ATTRIBUTE_HIDDEN )
|
|
bDosAttribute |= DOS_ATTRIBUTE_HIDDEN;
|
|
if ( dwWin32Attributes & FILE_ATTRIBUTE_SYSTEM )
|
|
bDosAttribute |= DOS_ATTRIBUTE_SYSTEM;
|
|
if ( dwWin32Attributes & FILE_ATTRIBUTE_ARCHIVE )
|
|
bDosAttribute |= DOS_ATTRIBUTE_ARCHIVE;
|
|
}
|
|
|
|
pDirEntry->Attribute = bDosAttribute;
|
|
|
|
if ( bUseGlobalTime ) {
|
|
pDirEntry->TimeStamp = wDosTime;
|
|
pDirEntry->DateStamp = wDosDate;
|
|
}
|
|
else {
|
|
FileTimeToLocalFileTime( &ftWin32TimeStamp,
|
|
&ftLocalFileTime );
|
|
FileTimeToDosDateTime( &ftLocalFileTime,
|
|
&pDirEntry->DateStamp,
|
|
&pDirEntry->TimeStamp );
|
|
}
|
|
|
|
#ifdef NEW_NT_EXTENDED_FAT_DIRECTORY_ENTRY_INTERPRETATION
|
|
pDirEntry->Extended.NtByte = 0;
|
|
pDirEntry->Extended.CreationTimeMilliSeconds = 0;
|
|
pDirEntry->Extended.CreationTime = pDirEntry->TimeStamp;
|
|
pDirEntry->Extended.CreationDate = pDirEntry->DateStamp;
|
|
pDirEntry->Extended.LastAccessDate = pDirEntry->DateStamp;
|
|
pDirEntry->Extended.ExtendedAttributes = 0;
|
|
#else
|
|
memset( pDirEntry->Reserved, 0, sizeof( pDirEntry->Reserved ));
|
|
#endif
|
|
|
|
pDirEntry->Cluster = (WORD)dwStartingCluster;
|
|
|
|
if ( dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY )
|
|
pDirEntry->FileSize = 0;
|
|
else
|
|
pDirEntry->FileSize = dwFileSize;
|
|
|
|
}
|
|
|
|
|
|
void CreateVolumeLabelEntry( PDIRENTRY pDirEntry ) {
|
|
|
|
PadCopy( pDirEntry->BaseName, chLabel, 11 );
|
|
pDirEntry->Attribute = DOS_ATTRIBUTE_VOLUME;
|
|
pDirEntry->TimeStamp = wDosTime;
|
|
pDirEntry->DateStamp = wDosDate;
|
|
|
|
}
|
|
|
|
|
|
void CreateTagFileEntry( PDIRENTRY pDirEntry ) {
|
|
|
|
CHAR chNameBuffer[ MAX_PATH ];
|
|
PCHAR pExtension;
|
|
|
|
strcpy( chNameBuffer, chTagFile );
|
|
pExtension = strchr( chNameBuffer, '.' );
|
|
if ( pExtension )
|
|
*pExtension++ = '\0';
|
|
PadCopy( pDirEntry->BaseName, chNameBuffer, 8 );
|
|
PadCopy( pDirEntry->Extension, pExtension, 3 );
|
|
pDirEntry->Attribute = DOS_ATTRIBUTE_NORMAL;
|
|
pDirEntry->TimeStamp = wDosTime;
|
|
pDirEntry->DateStamp = wDosDate;
|
|
|
|
}
|
|
|
|
|
|
UINT WalkListAndWriteFiles( PFILENODE pDir ) {
|
|
|
|
PFILENODE pNode;
|
|
UINT nClusters;
|
|
|
|
nClusters = 0;
|
|
|
|
for ( pNode = pDir->pFirstFile; pNode; pNode = pNode->pNextFile ) {
|
|
if ( ! ( pNode->dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY )) {
|
|
nClusters += OpenAndCopySourceFile( pNode );
|
|
}
|
|
}
|
|
|
|
for ( pNode = pDir->pFirstFile; pNode; pNode = pNode->pNextFile ) {
|
|
if ( pNode->dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY ) {
|
|
nClusters += WalkListAndWriteFiles( pNode );
|
|
}
|
|
}
|
|
|
|
return nClusters;
|
|
|
|
}
|
|
|
|
|
|
void BuildFullTargetName( PFILENODE pNode, LPSTR pBuffer ) {
|
|
|
|
if ( pNode == pRootDir ) {
|
|
|
|
if ( bDestIsDevice )
|
|
strcpy( pBuffer, chDest + 4 );
|
|
else
|
|
strcpy( pBuffer, chDest );
|
|
}
|
|
|
|
else {
|
|
|
|
BuildFullTargetName( pNode->pParentDir, pBuffer );
|
|
|
|
if ( pNode->pszTargetFileName ) {
|
|
strcat( pBuffer, "\\" );
|
|
strcat( pBuffer, pNode->pszTargetFileName );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
UINT OpenAndCopySourceFile( PFILENODE pNode ) {
|
|
|
|
CHAR chFullSourceName[ MAX_PATH ] = "";
|
|
CHAR chFullTargetName[ MAX_PATH ] = "";
|
|
HANDLE hSource;
|
|
UINT nClusters;
|
|
|
|
if ( pNode->pszSourcePath )
|
|
strcpy( chFullSourceName, pNode->pszSourcePath );
|
|
if ( pNode->pszSourceFileName )
|
|
strcat( chFullSourceName, pNode->pszSourceFileName );
|
|
|
|
BuildFullTargetName( pNode, chFullTargetName );
|
|
|
|
_strlwr( chFullSourceName );
|
|
_strlwr( chFullTargetName );
|
|
|
|
ErrorMessage( "%-*s -> %s ", nMaxSourceLength, chFullSourceName, chFullTargetName );
|
|
|
|
if ( pNode->dwFileSize == 0 ) {
|
|
ErrorMessage( "\n" );
|
|
return 0;
|
|
}
|
|
|
|
hSource = CreateFile( chFullSourceName,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL );
|
|
|
|
if ( hSource == INVALID_HANDLE_VALUE )
|
|
ErrorExit( "\nError opening file (GLE=%d)\n", GetLastError() );
|
|
|
|
nClusters = CopyFileContents( hTarget,
|
|
hSource,
|
|
pNode->dwFileSize );
|
|
|
|
CloseHandle( hSource );
|
|
|
|
ErrorMessage( "\n" );
|
|
|
|
return nClusters;
|
|
|
|
}
|
|
|
|
|
|
BOOL TrimTheTree( PFILENODE pDir ) {
|
|
|
|
PFILENODE pPrev, pNode, pNext;
|
|
|
|
pPrev = NULL;
|
|
pNode = pDir->pFirstFile;
|
|
|
|
while( pNode ) {
|
|
|
|
pNext = pNode->pNextFile;
|
|
|
|
if (( pNode->dwWin32Attributes & FILE_ATTRIBUTE_DIRECTORY ) &&
|
|
( TrimTheTree( pNode ))) {
|
|
|
|
MyFree( pNode );
|
|
|
|
if ( pPrev )
|
|
pPrev->pNextFile = pNext;
|
|
else
|
|
pDir->pFirstFile = pNext;
|
|
|
|
}
|
|
else {
|
|
pPrev = pNode;
|
|
}
|
|
|
|
pNode = pNext;
|
|
|
|
}
|
|
|
|
return (( pDir->pFirstFile == NULL ) ? TRUE : FALSE );
|
|
|
|
}
|
|
|
|
|
|
void InitializeOverlapped( void ) {
|
|
|
|
if ( ! ( olControl.hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ))) {
|
|
ErrorExit( "\nCreateEvent failed (GLE=%d)\n", GetLastError() );
|
|
}
|
|
|
|
dwOverlappedFilePointer = 0;
|
|
bOverlappedOutstanding = FALSE;
|
|
|
|
pOverlappedBuffer[ 0 ] = VirtualAlloc( NULL,
|
|
BUFFER_SIZE,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE );
|
|
|
|
pOverlappedBuffer[ 1 ] = VirtualAlloc( NULL,
|
|
BUFFER_SIZE,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE );
|
|
|
|
if (( pOverlappedBuffer[ 0 ] == NULL ) ||
|
|
( pOverlappedBuffer[ 1 ] == NULL )) {
|
|
|
|
ErrorExit( "\nOut of memory\n" );
|
|
}
|
|
|
|
|
|
iAvailableOverlappedBuffer = 0;
|
|
|
|
}
|
|
|
|
|
|
void OverlappedVerifyComplete( HANDLE hFile ) {
|
|
|
|
DWORD dwActualBytes;
|
|
|
|
if ( bOverlappedOutstanding ) {
|
|
if ( ! GetOverlappedResult( hFile, &olControl, &dwActualBytes, TRUE )) {
|
|
ErrorExit( "\nError overlapped %s %s %s (GLE=%d)\n",
|
|
bOverlappedWriting ? "writing" : "reading",
|
|
bDestIsDevice ? "device" : "file",
|
|
bDestIsDevice ? chDest + 4 : chDest,
|
|
GetLastError() );
|
|
}
|
|
if ( dwActualBytes != dwOverlappedExpectedBytes ) {
|
|
ErrorExit( "\nAssert: overlapped dwActualBytes != dwExpected\n" );
|
|
}
|
|
dwOverlappedFilePointer += dwActualBytes;
|
|
bOverlappedOutstanding = FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
void OverlappedWriteFile( HANDLE hFile, LPVOID pData, DWORD nBytes ) {
|
|
|
|
DWORD dwActualBytes, dwGLE;
|
|
|
|
if ( bWriteToTarget ) {
|
|
|
|
CopyMemory( pOverlappedBuffer[ iAvailableOverlappedBuffer ],
|
|
pData,
|
|
nBytes );
|
|
|
|
//
|
|
// First, we have to wait for the previous write to complete
|
|
// if there's one outstanding.
|
|
//
|
|
|
|
OverlappedVerifyComplete( hFile );
|
|
|
|
|
|
//
|
|
// Now the previous overlapped write is complete so we can
|
|
// load up the overlapped structure for our new write
|
|
//
|
|
|
|
olControl.Offset = dwOverlappedFilePointer;
|
|
olControl.OffsetHigh = 0;
|
|
|
|
if ( WriteFile( hFile,
|
|
pOverlappedBuffer[ iAvailableOverlappedBuffer ],
|
|
nBytes,
|
|
&dwActualBytes,
|
|
&olControl )) {
|
|
|
|
// completed synchronously
|
|
|
|
if ( dwActualBytes != nBytes )
|
|
ErrorExit( "\nAssert: dwActualBytes != nBytes\n" );
|
|
|
|
dwOverlappedFilePointer += dwActualBytes;
|
|
|
|
}
|
|
|
|
else if (( dwGLE = GetLastError() ) == ERROR_IO_PENDING ) {
|
|
|
|
// overlapped
|
|
|
|
dwOverlappedExpectedBytes = nBytes;
|
|
bOverlappedOutstanding = TRUE;
|
|
bOverlappedWriting = TRUE;
|
|
iAvailableOverlappedBuffer = ! iAvailableOverlappedBuffer; // toggle 1-0
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// a real error
|
|
|
|
ErrorExit( "\nError writing %s %s (GLE=%d)\n",
|
|
bDestIsDevice ? "device" : "file",
|
|
bDestIsDevice ? chDest + 4 : chDest,
|
|
dwGLE );
|
|
}
|
|
}
|
|
|
|
if ( bWriteToBuffer ) {
|
|
CopyMemory( pWholeDiskCurrentLocation, pData, nBytes );
|
|
pWholeDiskCurrentLocation += nBytes;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void OverlappedReadFile( HANDLE hFile, LPVOID pData, DWORD nBytes, PDWORD pdwActualBytes ) {
|
|
|
|
DWORD dwGLE;
|
|
|
|
if ((DWORD)pData & 0x1FF )
|
|
ErrorExit( "\nAssert: ReadFile unaligned buffer 0x%08X\n", pData );
|
|
|
|
if ( ROUNDUP2( nBytes, wClusterSize ) != nBytes )
|
|
ErrorExit( "\nAssert: ReadFile 0x%X byte write requested\n", nBytes );
|
|
|
|
//
|
|
// First, we have to wait for the previous i/o to complete
|
|
// if there's one outstanding.
|
|
//
|
|
|
|
OverlappedVerifyComplete( hFile );
|
|
|
|
|
|
//
|
|
// Now the previous overlapped write is complete so we can
|
|
// load up the overlapped structure for our new write
|
|
//
|
|
|
|
olControl.Offset = dwOverlappedFilePointer;
|
|
olControl.OffsetHigh = 0;
|
|
|
|
if ( ReadFile( hFile, pData, nBytes, pdwActualBytes, &olControl )) {
|
|
|
|
// completed synchronously
|
|
|
|
dwOverlappedFilePointer += *pdwActualBytes;
|
|
|
|
}
|
|
|
|
else if (( dwGLE = GetLastError() ) == ERROR_IO_PENDING ) {
|
|
|
|
// overlapped
|
|
|
|
dwOverlappedExpectedBytes = nBytes;
|
|
bOverlappedOutstanding = TRUE;
|
|
bOverlappedWriting = FALSE;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// a real error
|
|
|
|
ErrorExit( "\nError reading %s %s (GLE=%d)\n",
|
|
bDestIsDevice ? "device" : "file",
|
|
bDestIsDevice ? chDest + 4 : chDest,
|
|
dwGLE );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void OverlappedSetFilePointer( HANDLE hFile, DWORD dwAbsoluteOffset ) {
|
|
|
|
OverlappedVerifyComplete( hFile );
|
|
dwOverlappedFilePointer = dwAbsoluteOffset;
|
|
|
|
}
|
|
|
|
|
|
PPARAM LoadParameters( int argc, char *argv[] ) {
|
|
|
|
PPARAM pList, pLast, pNew;
|
|
char *pArg;
|
|
int i;
|
|
|
|
pList = MyAllocZ( sizeof( PARAM ));
|
|
pLast = pList;
|
|
|
|
for ( i = 1; i < argc; i++ ) {
|
|
|
|
pArg = argv[ i ];
|
|
|
|
if ( *pArg == '@' ) {
|
|
|
|
pLast = ParseResponseFile( pArg + 1, pLast );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
pNew = MyAllocZ( sizeof( PARAM ));
|
|
|
|
pNew->pszParam = pArg;
|
|
pNew->pNext = NULL;
|
|
pLast->pNext = pNew;
|
|
pLast = pNew;
|
|
|
|
}
|
|
}
|
|
|
|
return pList;
|
|
}
|
|
|
|
|
|
PPARAM ParseResponseFile( LPSTR pszFileName, PPARAM pLast ) {
|
|
|
|
CHAR chBuffer[ MAX_ENV ];
|
|
HANDLE hFile;
|
|
PCHAR pFile, pToken, pStartOfThisLine, pStartOfNextLine;
|
|
DWORD dwFileSize;
|
|
PPARAM pNew;
|
|
|
|
hFile = CreateFile( pszFileName,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL );
|
|
|
|
if ( hFile == INVALID_HANDLE_VALUE ) {
|
|
ErrorExit( "\nCan't open response file %s (GLE=%d)\n",
|
|
pszFileName,
|
|
GetLastError() );
|
|
}
|
|
|
|
dwFileSize = GetFileSize( hFile, NULL );
|
|
|
|
pFile = MyAllocZ( dwFileSize + 1 );
|
|
|
|
if ( pFile == NULL ) {
|
|
ErrorExit( "\nOut of memory\n" );
|
|
}
|
|
|
|
if ( ! ReadFile( hFile, pFile, dwFileSize, &dwFileSize, NULL )) {
|
|
ErrorExit( "\nCan't read response file %s (GLE=%d)\n",
|
|
pszFileName,
|
|
GetLastError() );
|
|
}
|
|
|
|
CloseHandle( hFile );
|
|
|
|
*( pFile + dwFileSize ) = '\0'; // strtok needs terminator
|
|
|
|
#ifdef ANAL
|
|
|
|
//
|
|
// anal retentive -- if we want to handle embedded NULLs in file
|
|
// as simple delimiters as opposed to end-of-file, replace them
|
|
// with spaces before processing.
|
|
//
|
|
|
|
for ( pToken = pFile; pToken < ( pFile + dwFileSize ); pToken++ )
|
|
if ( *pToken == 0 )
|
|
*pToken = ' ';
|
|
|
|
#endif
|
|
|
|
pStartOfThisLine = pFile;
|
|
|
|
while ( pStartOfThisLine ) {
|
|
|
|
pStartOfNextLine = strchr( pStartOfThisLine, '\n' );
|
|
if ( pStartOfNextLine ) {
|
|
do *pStartOfNextLine++ = '\0';
|
|
while ( *pStartOfNextLine == '\n' );
|
|
}
|
|
|
|
pToken = strtok( pStartOfThisLine, " \t\r\f\v" );
|
|
|
|
while ( pToken ) {
|
|
|
|
if (( *pToken == '#' ) ||
|
|
( *pToken == ';' ) ||
|
|
( *(UNALIGNED WORD *) pToken == 0x2F2F )) {
|
|
|
|
//
|
|
// '#' ';' "//" (0x2F2F) all indicate comment to end of line
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
ReplaceEnvironmentStrings( pToken, chBuffer );
|
|
pNew = MyAllocZ( sizeof( PARAM ));
|
|
pNew->pszParam = DuplicateString( chBuffer );
|
|
pNew->pNext = NULL;
|
|
pLast->pNext = pNew;
|
|
pLast = pNew;
|
|
pToken = strtok( NULL, " \t\r\f\v" );
|
|
}
|
|
|
|
pStartOfThisLine = pStartOfNextLine;
|
|
}
|
|
|
|
MyFree( pFile );
|
|
return pLast;
|
|
}
|
|
|
|
|
|
PPARAM RemoveParam( PPARAM pThis, PPARAM pPrev ) {
|
|
PPARAM pReturnNext = pPrev->pNext = pThis->pNext;
|
|
MyFree( pThis );
|
|
return pReturnNext;
|
|
}
|
|
|
|
|
|
LPSTR ReplaceEnvironmentStrings( LPCSTR pszInputString, LPSTR pTargetBuffer ) {
|
|
|
|
CHAR szVariableName[ MAX_ENV ];
|
|
LPCSTR pSource = pszInputString;
|
|
LPSTR pDestin = pTargetBuffer;
|
|
LPCSTR pName;
|
|
DWORD dwLen;
|
|
|
|
while (( pName = strchr( pSource, '%' )) != NULL ) {
|
|
|
|
memcpy( pDestin, pSource, ( pName - pSource ));
|
|
pDestin += ( pName - pSource );
|
|
pSource = strchr( pName + 1, '%' );
|
|
|
|
if ( pSource == NULL ) {
|
|
pSource = pName;
|
|
break;
|
|
}
|
|
else if ( pSource == ( pName + 1 )) {
|
|
*pDestin++ = '%';
|
|
}
|
|
else {
|
|
memcpy( szVariableName, pName + 1, ( pSource - pName - 1 ));
|
|
*( szVariableName + ( pSource - pName - 1 )) = '\0';
|
|
dwLen = GetEnvironmentVariable( szVariableName, pDestin, MAX_ENV );
|
|
if ( dwLen > MAX_ENV )
|
|
ErrorExit( "\n%s: environment variable value too long\n",
|
|
szVariableName );
|
|
pDestin += dwLen;
|
|
}
|
|
|
|
++pSource;
|
|
}
|
|
|
|
strcpy( pDestin, pSource );
|
|
return pTargetBuffer;
|
|
|
|
}
|
|
|
|
|
|
BOOL AddDirectoryToList( LPSTR pszPathName,
|
|
PFILENODE pParentDir,
|
|
UINT nSourceIndex,
|
|
PFILENODE *pThisNode ) {
|
|
|
|
PFILENODE pPrev, pNext, pNew;
|
|
CHAR chFileName[ MAX_PATH ];
|
|
LPSTR pRestOfPath;
|
|
LPSTR pExtension;
|
|
INT iCompare;
|
|
|
|
while ( *pszPathName == '\\' )
|
|
++pszPathName;
|
|
|
|
strcpy( chFileName, pszPathName );
|
|
pRestOfPath = strchr( chFileName, '\\' );
|
|
|
|
if ( pRestOfPath )
|
|
*pRestOfPath++ = '\0';
|
|
|
|
_strupr( chFileName );
|
|
pExtension = strchr( chFileName, '.' );
|
|
|
|
if ((( pExtension == NULL ) && ( strlen( chFileName ) > 8 )) ||
|
|
(( pExtension ) &&
|
|
(( pExtension - chFileName > 8 ) ||
|
|
( strlen( pExtension + 1 ) > 3 )))) {
|
|
|
|
ErrorExit( "\n%s: invalid pathname\n", chFileName );
|
|
}
|
|
|
|
for ( pPrev = NULL, pNext = pParentDir->pFirstFile;
|
|
pNext != NULL;
|
|
pPrev = pNext, pNext = pNext->pNextFile ) {
|
|
|
|
iCompare = strcmp( pNext->pszTargetFileName, chFileName );
|
|
|
|
if ( ! iCompare ) {
|
|
|
|
//
|
|
// This file or pathname already exists. Verify
|
|
// that it is a directory versus a file, then
|
|
// process remainder of path if any, else mark
|
|
// this node and return FALSE indicating already
|
|
// existed.
|
|
//
|
|
|
|
if ( pNext->dwWin32Attributes != FILE_ATTRIBUTE_DIRECTORY ) {
|
|
ErrorExit( "\n%s: directory and file have same name\n",
|
|
chFileName );
|
|
}
|
|
|
|
if ( pRestOfPath ) {
|
|
return AddDirectoryToList( pRestOfPath,
|
|
pNext, // parent
|
|
nSourceIndex,
|
|
pThisNode );
|
|
}
|
|
else {
|
|
if ( pThisNode )
|
|
*pThisNode = pNext;
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
else if (( pNext->nSourceIndex >= nSourceIndex ) &&
|
|
( iCompare > 0 )) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
pNew = NewFileNode();
|
|
pNew->pszTargetFileName = DuplicateString( chFileName );
|
|
pNew->dwFileSize = 0;
|
|
pNew->dwWin32Attributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
pNew->ftWin32TimeStamp = ftGlobalFileTime;
|
|
pNew->nSourceIndex = nSourceIndex;
|
|
pNew->pParentDir = pParentDir;
|
|
pNew->pNextFile = pNext;
|
|
|
|
if ( pPrev )
|
|
pPrev->pNextFile = pNew;
|
|
else
|
|
pParentDir->pFirstFile = pNew;
|
|
|
|
if ( pRestOfPath ) {
|
|
return AddDirectoryToList( pRestOfPath,
|
|
pNew, // parent
|
|
nSourceIndex,
|
|
pThisNode );
|
|
}
|
|
else {
|
|
if ( pThisNode )
|
|
*pThisNode = pNew;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
void Copyright( void ) {
|
|
printf( "\n"
|
|
"FCOPY Diskette Mastering Utility Version 1.54"
|
|
#ifdef DMFSIGNATURE
|
|
"s"
|
|
#endif
|
|
"\n"
|
|
"Copyright (C) Microsoft, 1993-1996. All rights reserved.\n"
|
|
"For Microsoft internal use only.\n"
|
|
"\n" );
|
|
}
|
|
|
|
|
|
|