Rev 2764 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*============================================================================** 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 <tchar.h>#include <stdio.h>#include <windows.h>#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 chmod\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);}