/*============================================================================ ** Copyright (C) 1998-2012 Vix Technology, All rights reserved **============================================================================ ** ** Project/Product : ** Filename : JatsFileUtil.c ** Author(s) : DDP ** ** Description : Jats Build System File utility ** Used by the generated makefiles to perform specific operations ** ** The program exists to solve problems: ** Windows: Shell is very very slow to start up ** Windows: Some commands have ~260 character path length issue ** Windows/Solaris/Linux: Compatibility issues with the 'rm' command ** All: Consistent use of '/' as a path separator ** ** Note: There are two flavors of this program that MUST be ** kept in sync. ** ** The program will perform the following operations: ** (c) CopyFile ** (d) DeleteFile ** (r) Remove Files (wildcard) ** (D) DeleteDir after deleting specified files (wildcard) ** (T) Delete Directory Trees ** (R) Remove Files and Empty Directories ** (P) Print Arguments ** ** Example Usage ** ** JatsFileUtil c9 'copyFile' aaa/bbb/ccc/dddd/file build_test.pl +w ** JatsFileUtil d9 'unCopyFile' aaa/bbb/ccc/dddd/file ** JatsFileUtil r9 'deleteFile' a1 b2 c3 ** JatsFileUtil D9 'DeleteFiles' src/WIN32P.OBJ *.err *.pch '*' ** JatsFileUtil T9 'DeleteTree' interface ** JatsFileUtil R9 'RmItem' build ** ** First two arguments are common to all ** argv[1] - Mode specifier, Debug specifier ** argv[2] - Display Text ** ** Information : ** Compiler : ANSI C ** Target : Windows 2000+, Linux, Solaris8+ ** ***==========================================================================*/ #define _CRT_SECURE_NO_WARNINGS #include #include #include #define INVALID_FILE_ATTIBUTES ((DWORD) -1) VOID ErrorExit (char *lpszMessage, LPTSTR lpszMessage2); void createPaths ( _TCHAR *path ); void DeleteDir( int argc, _TCHAR* argv[] ); _TCHAR * makePath( _TCHAR *base, _TCHAR *path); void DeleteOneDirectoryTree( _TCHAR* baseDir ); void copyOneFile( int argc, _TCHAR* argv[] ); void RmItem( int argc, _TCHAR* argv[] ); int DeleteOneFile (_TCHAR * dst ); void DeleteOneDirectory (_TCHAR *path ); void stdCheck( char *name, int argBad, _TCHAR *txt ); void url_decode(_TCHAR *str); /* ** Global */ char verbose = 1; /* Debugging aid */ char *helpText = "Usage: JatsFileUtil Mode Text Arguments\n" "\n" " Where 'Mode' is two characters:\n" " 1 - Operation Specifier\n" " 2 - Debug Mode. 0..9\n" " Where 'Text' is a, possibly empty, display string\n" "\n" " By Example:\n" " c9 copyFile dstPath srcPath modes(wxl)\n" " d9 unCopyFile dstPath\n" " r9 RmFile file+\n" " D9 DeleteFiles dstDir file+ - supports (?*)\n" " T9 DeleteTree dstDir+\n" " R9 RmItem (dir|file)+\n" " P9 PrintArgs ...\n"; /*---------------------------------------------------------------------------- ** FUNCTION : main ** ** DESCRIPTION : Main entry points ** ** ** INPUTS : argc - Argument Count ** argv - Argument Vector ** ** RETURNS : 0 - All is good ** ----------------------------------------------------------------------------*/ int _tmain(int argc, _TCHAR* argv[]) { _TCHAR * dst; int ii; /* ** User must provide some thing */ if ( argc < 2 ) { fprintf(stderr, helpText ); return 1; } /* ** Examine the first argument ** Must be a character string of ** [0] - Mode : One character ** [1] - Verbose : 0 .. 9 */ if ( argc > 1 && ( argv[1][1] >= '0' && argv[1][1] <= '9' ) ) { verbose = argv[1][1] - '0'; } /* ** URL Decode most arguments ** To get past the stupidities of shells and make the arguments ** will have been URL(modified) encoded ** ** Decode all args in place */ for ( ii = 2; ii < argc ; ii++ ) { url_decode(argv[ii]); /* ** If Verbose, then display arguments */ if ( verbose > 2 ) fprintf(stderr, "Arg%d: %ls:\n", ii, argv[ii] ); } fflush(stderr) ; /* ** Switch to required operation */ switch( argv[1][0] ) { /* ** CopyFile ** argv[2] - Text ** argv[3] - target path ** argv[4] - Source path ** argv[5] - File attributes */ case 'c': stdCheck( "CopyFile", argc != 6, NULL ); fprintf(stderr, "---- %ls %ls\n", argv[2], argv[3]); fflush(stderr) ; copyOneFile(argc, argv); break; /* ** UnCopy a file ** argv[2] - Text ** argv[3] - target path */ case 'd': stdCheck( "UnCopy", argc != 4, NULL ); fprintf(stderr, "---- %ls %ls\n", argv[2], argv[3]); fflush(stderr) ; dst = makePath(argv[3], NULL); DeleteOneFile(dst); free (dst); break; /* ** Remove named files ** argv[2] - Text ** argv[3]+ - target path */ case 'r': stdCheck( "RmFile", argc <= 3, argv[2] ); for ( ii = 3; ii < argc ; ii++ ) { _TCHAR * dst = makePath(argv[ii], NULL); DeleteOneFile(dst); free (dst); } break; /* ** Delete files in directory - with wildcards ** argv[2] - Text ** argv[3] - Base directory ** argv[4]+ - Files in dir to delete. */ case 'D': stdCheck( "DeleteDir", argc < 4, argv[2] ); DeleteDir(argc, argv ); break; /* ** Delete files recursively ** argv[2] - Text ** argv[3]+ Base directory */ case 'T': stdCheck( "DeleteDirTree", argc < 3, argv[2] ); for ( ii = 3; ii < argc ; ii++) { DeleteOneDirectoryTree( argv[ii] ); } break; /* ** Delete Empty Directories ** argv[2] - Text ** argv[3]+ Base directory */ case 'R': stdCheck( "RmItem", argc < 3, argv[2] ); RmItem(argc, argv ); break; /* ** Print arguments with a space betweenn them ** All on the same line ** argv[2]+ - Text */ case 'P': for ( ii = 2; ii < argc ; ii++ ) { if ( ii > 2 ) fprintf(stderr, " "); fprintf(stderr, "%ls", argv[ii] ); } fprintf(stderr, "\n"); break; default : ErrorExit("Unknown mode: ",argv[1]); break; } return 0; } /*---------------------------------------------------------------------------- ** FUNCTION : stdCheck ** ** DESCRIPTION : Check arg count ** Print standard header ** ** ** INPUTS : name - Name of the operation ** argBad - Arg count Not Ok ** text - Text to print ** ** RETURNS : Will not return on error ** ----------------------------------------------------------------------------*/ void stdCheck( char *name, int argBad, _TCHAR *txt ) { if ( argBad ) { fprintf(stderr, "JatsFileUtil:Incorrect argument count for %s\n", name); ErrorExit(NULL, NULL); } /* ** Display user text ** Suppress display if the message is empty */ if ( txt && *txt ) { fprintf(stderr, "%ls\n",txt); fflush(stderr) ; } } /*---------------------------------------------------------------------------- ** FUNCTION : createPaths ** ** DESCRIPTION : Create the path to the target ** ** ** INPUTS : path ** Assumed to be a \\?\X:\ style ** ** RETURNS : Will not return in error ** ----------------------------------------------------------------------------*/ void createPaths ( _TCHAR *path ) { DWORD rv; _TCHAR *ptr = path + 7; while ( *ptr ) { if ( *ptr == '/' || *ptr == '\\') { *ptr = 0; //fprintf(stderr, "createPaths: %ls\n", path); if ( ! CreateDirectoryW ( path, NULL )) { rv = GetLastError(); if ( rv != ERROR_ALREADY_EXISTS ) ErrorExit("Could not create directories:", path); } *ptr = '\\'; } ptr++; } } /*---------------------------------------------------------------------------- ** FUNCTION : copyOneFile ** ** DESCRIPTION : Copy one file to a target ** Used to package and install files ** ** ** INPUTS : argc - Argc count ** argv - Argument list ** argv[2] - Display text Prefix ** argv[3] - Target path ** argv[4] - Source Path ** argv[5] - File attributes ** ** RETURNS : ** ----------------------------------------------------------------------------*/ void copyOneFile( int argc, _TCHAR* argv[] ) { DWORD rv; _TCHAR * src; _TCHAR * dst; dst = makePath(argv[3], NULL); src = makePath(argv[4], NULL); /* ** Check that the source is a file */ if ( verbose > 2) fprintf(stderr, "Validate Source File: %ls\n", src); rv = GetFileAttributesW( src ); if ( rv == INVALID_FILE_ATTIBUTES ) { /* Need to be a better message */ fprintf(stderr, "Source: %ls\n", src); ErrorExit("Source File not found: ", argv[4]); } /* ** Delete the destination file before the copy ** Will force it to be writable */ DeleteOneFile(dst); /* ** Create directories ** Use the path to the target - not the provided directory ** as the createPaths function will not create the last element */ createPaths( dst ); /* ** Copy the file */ if ( ! CopyFile( src, dst, FALSE ) ) { rv = GetLastError(); fprintf(stderr, "CopyFile Last Error: %ld\n", rv); ErrorExit("Copy Error: ", argv[4]); } /* ** Test for files existence */ if ( verbose > 1 ) fprintf(stderr, "Test target was copied: %ls\n", dst); rv = GetFileAttributesW( dst ); if ( rv == INVALID_FILE_ATTIBUTES ) { /* Need to be a better message */ ErrorExit("File not found after copy: ", argv[3]); } /* ** Set the files attributes ** Assume read-only */ if ( _tcsstr( argv[5], L"-w" ) ) { if ( verbose ) fprintf(stderr, "Set target read-only: %ls\n", dst); rv |= FILE_ATTRIBUTE_READONLY; rv &= ~FILE_ATTRIBUTE_NORMAL; rv = SetFileAttributesW( dst, rv ); if ( rv == 0 ) { ErrorExit("Setting ReadOnly: ", argv[3]); } } } /*---------------------------------------------------------------------------- ** FUNCTION : DeleteDir ** ** DESCRIPTION : Delete a list of files in a specified directory ** Ensure files are writable ** Wilcarding is supported ** ** Then delete the directory - if its empty ** ** ** INPUTS : argc - count of args ** argv - list of files to delete ** [3]: Base directory ** [4]+ Files in dir to delete ** ** RETURNS : Will not return on error ** ----------------------------------------------------------------------------*/ void DeleteDir( int argc, _TCHAR* argv[] ) { DWORD rv; _TCHAR* baseDir; WIN32_FIND_DATA FindFileData; HANDLE hFind; _TCHAR *target; _TCHAR *dirPath; int ii; /* ** Extract the base directory from the argument list ** This must be a directory */ baseDir = argv[3]; /* ** Ensure that the base directory exists */ dirPath = makePath(baseDir, NULL); rv = GetFileAttributesW( dirPath ); if ( rv == INVALID_FILE_ATTIBUTES ) { /* ** Directory does not exists ** Assume its already deleted */ if ( verbose > 1 ) fprintf(stderr, "Base dir does not exist: %ls\n", baseDir); free(dirPath); return; } if ( !(rv & FILE_ATTRIBUTE_DIRECTORY) ) { /* ** Target is not a directory ** Don't do anything */ if ( verbose > 1 ) fprintf(stderr, "Base dir is not a directory: %ls\n", baseDir); free(dirPath); return; } /* ** Process all the suffixes ** They may contain a wild card */ for ( ii = 4; ii < argc ; ii++) { _TCHAR * thisDir = makePath( baseDir, argv[ii]); hFind = FindFirstFile(thisDir, &FindFileData); free(thisDir); if (hFind == INVALID_HANDLE_VALUE) { /* ** No match */ if ( verbose > 1 ) fprintf(stderr, "No Match: %ls, %ls\n", baseDir, argv[ii]); continue; } /* ** Iterate over all the files */ do { if ( _tcscmp( TEXT("."), FindFileData.cFileName ) == 0 ) continue; if ( _tcscmp( TEXT(".."), FindFileData.cFileName ) == 0 ) continue; if ( verbose > 1) fprintf(stderr, "Match: %ls\n", FindFileData.cFileName); /* ** Matching file found */ target = makePath( baseDir, FindFileData.cFileName); DeleteOneFile(target); free (target); } while (FindNextFile(hFind, &FindFileData) != 0); FindClose(hFind); } /* ** Finally delete the directory ** Unless its '.' */ if ( _tcscmp( TEXT("."),baseDir) != 0 ) { DeleteOneDirectory(baseDir); } free(dirPath); } /*---------------------------------------------------------------------------- ** FUNCTION : DeleteOneDirectoryTree ** ** DESCRIPTION : Delete an entire directory tree(s) ** Force it writable and searchable before deletion ** Don't follow symbolic links - just delete them ** ** INPUTS : path - Dir to delete ** ** RETURNS : Will not return on error ** ----------------------------------------------------------------------------*/ void DeleteOneDirectoryTree( _TCHAR* baseDir ) { DWORD rv; WIN32_FIND_DATA FindFileData; HANDLE hFind; _TCHAR *target; _TCHAR *dirPath; _TCHAR *thisDir; /* ** A bit of a sanity test */ if ( _tcscmp( L".", baseDir) == 0 || _tcscmp( L"..", baseDir) == 0 ) { fprintf(stderr, "DeleteOneDirectoryTree will not delete '.' or '..'\n"); return; } /* ** Ensure that the base directory exists */ dirPath = makePath(baseDir, NULL); rv = GetFileAttributesW( dirPath ); if ( rv == INVALID_FILE_ATTIBUTES ) { /* ** Directory does not exists ** Assume its already deleted */ if ( verbose > 1 ) fprintf(stderr, "Base dir does not exist: %ls\n", baseDir); free(dirPath); return; } if ( !(rv & FILE_ATTRIBUTE_DIRECTORY) ) { /* ** Target is not a directory ** Don't do anything */ if ( verbose > 1 ) fprintf(stderr, "Base dir is not a directory: %ls\n", baseDir); free(dirPath); return; } /* ** Read next directory entry */ thisDir = makePath( baseDir, TEXT("*")); hFind = FindFirstFile(thisDir, &FindFileData); free(thisDir); if (hFind != INVALID_HANDLE_VALUE) { /* ** Iterate over all the files */ do { if ( _tcscmp( TEXT("."), FindFileData.cFileName ) == 0 ) continue; if ( _tcscmp( TEXT(".."), FindFileData.cFileName ) == 0 ) continue; if ( verbose > 2 ) fprintf(stderr, "Directory Entry:%ls,%ls\n", baseDir, FindFileData.cFileName ); /* ** Create a full path to the file */ target = makePath( baseDir, FindFileData.cFileName); if ( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { DeleteOneDirectoryTree( target ); } else { DeleteOneFile (target); } free (target); } while (FindNextFile(hFind, &FindFileData) != 0); FindClose(hFind); } /* ** Finally delete the directory ** It should now be empty */ DeleteOneDirectory(baseDir); free(dirPath); } /*---------------------------------------------------------------------------- ** FUNCTION : RmItem ** ** DESCRIPTION : Remove Empty directories and files ** ** INPUTS : argc - count of args ** argv - list of files to delete ** [3]+ Base directory ** ** RETURNS : Will not return on error ** ----------------------------------------------------------------------------*/ void RmItem( int argc, _TCHAR* argv[] ) { int ii; _TCHAR *target; DWORD rv; for ( ii = 3; ii < argc ; ii++) { if ( verbose > 2) fprintf(stderr, "RmItem: %ls\n", argv[ii]); target = makePath( argv[ii],NULL); rv = GetFileAttributesW( target ); if ( rv != INVALID_FILE_ATTIBUTES ) { if ( rv & FILE_ATTRIBUTE_DIRECTORY) { DeleteOneDirectory( target ); } else { DeleteOneFile (target); } } free(target); } } /*---------------------------------------------------------------------------- ** FUNCTION : DeleteOneFile ** ** DESCRIPTION : Delete a file ** Low level deletion operation to be used by other functions ** Force it writable before deletion ** Don't follow symbolic links - just delete them ** ** INPUTS : path - path to the file ** ** RETURNS : 0 - OK (file deleted or not present) ** ----------------------------------------------------------------------------*/ int DeleteOneFile (_TCHAR * dst ) { DWORD rv; int result = 0; if ( verbose > 1) fprintf(stderr, "Delete File: %ls\n", dst); rv = GetFileAttributesW( dst ); if ( rv != INVALID_FILE_ATTIBUTES ) { if ( verbose ) fprintf(stderr, "Delete file: %ls : Attr: 0x%lx\n", dst, rv); if ( rv & FILE_ATTRIBUTE_READONLY ) { rv &= ~FILE_ATTRIBUTE_READONLY; rv = SetFileAttributesW( dst, rv ); if ( rv == 0 ) { fprintf(stderr, "Warning: Attempt to allow write access: %ls\n", dst); } } if (! DeleteFile( dst ) ) { fprintf(stderr, "Warning: Did not remove file: %ls\n", dst); result = 1; } } return result; } /*---------------------------------------------------------------------------- ** FUNCTION : DeleteOneDirectory ** ** DESCRIPTION : Low level function to delete a directory ** Assumes that checks have been performed ** It is a directory ** It does exist ** ** ** INPUTS : path - Target Path ** ** RETURNS : Nothing ** ----------------------------------------------------------------------------*/ void DeleteOneDirectory (_TCHAR *path ) { if ( verbose ) fprintf(stderr, "Delete Directory: %ls\n", path); if ( ! RemoveDirectory(path)) { if ( verbose ) fprintf(stderr, "Directory not deleted: %ls\n", path); } } /*---------------------------------------------------------------------------- ** FUNCTION : makePath ** ** DESCRIPTION : Creates a full file path for user ** Join 2 components together ** Convert relative path to ABS path with \\?\ from ** Convert / into \ for windows ** ** Handle path elements ** ./ - Remove ** /bbb/../ - Remove ** /aaa/bbb/../../ - Remove ** ** ** INPUTS : base - Part 1 ** May already contain \\?\ ** file - Part 2 or NULL ** ** RETURNS : fullPath - User must free memory ** ----------------------------------------------------------------------------*/ _TCHAR * makePath ( _TCHAR * base, _TCHAR *file ) { size_t lenp = 0; size_t lencwd = 0; size_t len1 = _tcslen(base); size_t len2 = 0; size_t dlen = _tcslen(TEXT("\\\\?\\")); _TCHAR *data; _TCHAR *cdata; _TCHAR *udst; /* ** If the base contains a \\?\ then we don't need to add it */ if ( !( len1 > 3 && base[2] == '?' ) ) { lenp = dlen; } /* ** Unless an absolute path we need to insert CWD too ** Just determine the length of the entry for now */ if ( lenp && ( ( base[0] && (base[1] != ':') ) || base[0] == 0 ) ) { lencwd = GetCurrentDirectory(0,0) - 1; // determine size needed } /* ** 2nd file argument may be a NULL */ if ( file ) { len2 = _tcslen(file); } data = (_TCHAR *)malloc((lenp + lencwd+ len1 + len2 + 10) * sizeof(_TCHAR)); cdata = data; if ( data == NULL ) { ErrorExit ("Malloc error:makePath",L""); } /* ** Join all the strings together */ if ( lenp ) { _tcscpy( cdata,TEXT("\\\\?\\")); cdata += lenp; } if ( lencwd ) { GetCurrentDirectory((DWORD)(lencwd+1), cdata); cdata += lencwd; _tcscpy( cdata, TEXT("\\")); cdata += 1; } _tcscpy( cdata, base); cdata += len1; if ( len2 ) { _tcscpy( cdata, TEXT("\\")); _tcscpy( cdata+1, file); cdata += len2 + 1; } /* ** Convert / into \ */ for ( udst = data; *udst ; udst++) { if ( *udst == '/' ) *udst = '\\'; } /* ** Now remove silly relative path bits ** Also duplicate '\\' */ udst = data + dlen; while ( *udst ) { if ( udst[0] == '\\' && udst[1] == '\\' ) { TCHAR* ptr1 = udst; TCHAR* ptr2 = udst + 1; while ( *ptr1++ = *ptr2++ ) {} continue; } if ( udst[0] == '\\' && udst[1] == '.' && (udst[2] == '\\' || udst[2] == 0) ) { TCHAR* ptr1 = udst; TCHAR* ptr2 = udst + 2; while ( *ptr1++ = *ptr2++ ) {} } else if ( udst[0] == '\\' && udst[1] == '.' && udst[2] == '.' && udst[3] == '\\' ) { TCHAR* ptr = udst - 1; TCHAR* ptr2 = udst + 3; while ( *ptr-- != '\\' ) { if ( ptr - data < 6) { *ptr = 0; fprintf(stderr, "Path: %ls\n", data); ErrorExit("Bad relative path: ", data); } } ptr++; udst = ptr; while ( *ptr++ = *ptr2++ ) {} } else { *udst++; } } if ( verbose > 5) fprintf(stderr, "makePath: %ls\n", data); return data; } /*---------------------------------------------------------------------------- ** FUNCTION : from_hex ** ** DESCRIPTION : Converts a hex character to its integer value ** Expects well formed HEX ** ** ** INPUTS : Character to convert ** ** RETURNS : Character ** ----------------------------------------------------------------------------*/ _TCHAR from_hex(_TCHAR ch) { if ( ch >= '0' && ch <= '9' ) { return ch - '0'; } if ( ch >= 'A' && ch <= 'F' ) { return ch - 'A' + 10; } if ( ch >= 'a' && ch <= 'f' ) { return ch - 'f' + 10; } return 0; } /*---------------------------------------------------------------------------- ** FUNCTION : url_decode ** ** DESCRIPTION : Perform (modified) URL decoding of a string ** Only support %nn stuff ** Its done inplace ** ** ** INPUTS : str - String to process ** ** RETURNS : Nothing ** ----------------------------------------------------------------------------*/ void url_decode(_TCHAR *str) { _TCHAR *pstr = str; _TCHAR *pbuf = str; while (*pstr) { if (*pstr == '%') { if (pstr[1] && pstr[2]) { *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]); pstr += 2; } /* } */ /* } else if (*pstr == '+') { */ /* *pbuf++ = ' '; */ } else { *pbuf++ = *pstr; } pstr++; } *pbuf = '\0'; } /*---------------------------------------------------------------------------- ** FUNCTION : ErrorExit ** ** DESCRIPTION : Error processing ** Report an error and terminate process ** ** ** INPUTS : lpszMessage - Message to display ** ** RETURNS : Does't return ** Will exit with bad code ** ----------------------------------------------------------------------------*/ VOID ErrorExit (char *lpszMessage, LPTSTR lpszMessage2) { if ( lpszMessage ) { fprintf(stderr, "JatsFileUtil:Error:%s%ls\n", lpszMessage,lpszMessage2); } fflush(stderr) ; ExitProcess(-1); }