/*============================================================================ ** 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+x+l ** 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+ ** ***==========================================================================*/ #include #include #include #include #include #include #include #include void ErrorExit (char * lpszMessage, char * lpszMessage2); void createPaths ( char *path ); void DeleteDir( int argc, char* argv[] ); char * makePath( char *base, char *path); void DeleteOneDirectoryTree( char* baseDir ); void copyOneFile( int argc, char* argv[] ); int CopyFile ( char *src, char *dst, mode_t st_mode ); int CopyLink ( char *src, char *dst, int lsize ); void RmItem( int argc, char* argv[] ); int wildcmp(char *string, char *wild ); int DeleteOneFile (char * dst ); void DeleteOneDirectory (char *path ); void stdCheck( char *name, int argBad, char *txt ); void url_decode(char *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 main(int argc, char* argv[]) { 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: %s:\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, "---- %s %s\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, "---- %s %s\n", argv[2], argv[3]); fflush(stderr) ; DeleteOneFile(argv[3]); 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++ ) { DeleteOneFile(argv[ii]); } 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, "%s", 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, char *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, "%s\n",txt); fflush(stderr) ; } } /*---------------------------------------------------------------------------- ** FUNCTION : createPaths ** ** DESCRIPTION : Create the path to the target ** ** ** INPUTS : path ** ** RETURNS : Will not return in error ** ----------------------------------------------------------------------------*/ void createPaths ( char *path ) { struct stat fstat; int rv; char *ptr = path; while ( *ptr ) { if ( *ptr == '/' ) { *ptr = 0; /* fprintf(stderr, "createPaths: %s\n", path); */ rv = stat( path, &fstat ); if ( rv ) { if ( verbose > 1 ) { fprintf(stderr, "createPaths: %s\n", path); fflush(stderr) ; } rv = mkdir( path, 0777 ); if ( rv ) { 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 / Copy mode ** ** RETURNS : ** ----------------------------------------------------------------------------*/ void copyOneFile( int argc, char* argv[] ) { int rv; char * src; char * dst; struct stat fstat; struct stat lfstat; int symlink = 0; dst = argv[3]; src = argv[4]; /* ** Check that the source is a file */ if ( verbose > 2) fprintf(stderr, "Validate Source File: %s\n", src); rv = stat( src, &fstat ); if ( rv != 0 ) { /* Need to be a better message */ fprintf(stderr, "Source: %s\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 ); /* ** Sometimes symlinks are handled differently ** If marked as a symlink ** And it is a symlink */ if (strstr( argv[5], "+l" )) { rv = lstat(src, &lfstat); if ( rv == 0 && S_ISLNK(lfstat.st_mode)) { if ( verbose > 2) fprintf(stderr, "Copy Symlink: %s\n", src); symlink = 1; if ( ! CopyLink( src, dst, lfstat.st_size ) ) { ErrorExit("Copy Symlink Error: ", argv[4]); } /* ** Test for file (symlink) existence ** Doesn't need the target to be present */ if ( verbose > 1 ) fprintf(stderr, "Test symlink was created: %s\n", dst); rv = lstat( dst, &lfstat ); if ( rv != 0 ) { /* Need to be a better message */ ErrorExit("Symlink not found after creation: ", argv[3]); } } } if (!symlink) { /* ** Copy the file */ if ( ! CopyFile( src, dst, fstat.st_mode ) ) { ErrorExit("Copy Error: ", argv[4]); } /* ** Test for files existence */ if ( verbose > 1 ) fprintf(stderr, "Test target was copied: %s\n", dst); rv = stat( dst, &fstat ); if ( rv != 0 ) { /* Need to be a better message */ ErrorExit("File not found after copy: ", argv[3]); } /* ** Set the files attributes, unless copying a symlink ** Assume read-only */ if (strstr(argv[5], "-w")) { if ( verbose > 1 ) fprintf(stderr, "Set target read-only: %s\n", dst); fstat.st_mode &= ~(S_IWRITE | S_IWOTH | S_IWGRP ); } if ( strstr( argv[5], "+w" ) ) { if ( verbose > 1 ) fprintf(stderr, "Set target writable: %s\n", dst); fstat.st_mode |= (S_IWRITE | S_IWOTH | S_IWGRP ); } if ( strstr( argv[5], "+x" ) ) { if ( verbose > 1 ) fprintf(stderr, "Set target executable: %s\n", dst); fstat.st_mode |= ( S_IXUSR | S_IXOTH | S_IXGRP ); } if ( strstr( argv[5], "-x" ) ) { if ( verbose > 1) fprintf(stderr, "Set target not executable: %s\n", dst); fstat.st_mode &= ~( S_IXUSR | S_IXOTH | S_IXGRP ); } if ( verbose > 1 ) fprintf(stderr, "Set target perms: %s, 0%o\n", dst, fstat.st_mode); rv = chmod( dst, fstat.st_mode ); if ( rv != 0 ) { ErrorExit("Setting ReadOnly: ", argv[3]); } } } /*---------------------------------------------------------------------------- ** FUNCTION : CopyFile ** ** DESCRIPTION : Just copy a file ** ** ** INPUTS : src - source path - already exists ** dst - path. Dirs already exist ** st_mode - Creation mode. Copied from source file ** ** RETURNS : false - Error ** ----------------------------------------------------------------------------*/ #define COPYSIZE 1024 int CopyFile ( char *src, char *dst, mode_t st_mode ) { ssize_t rlen = 0 ; ssize_t wlen = 0 ; int in; int out; int ferror = 0; char buffer[COPYSIZE] = { '\0' } ; if ( verbose > 2 ) { fprintf(stderr, "CopyFile: Output Mode: 0%o\n", st_mode); fflush(stderr) ; } in = open( src, O_RDONLY ) ; if ( in < 0 ) ErrorExit("Could not open source:", src); out = open( dst, O_WRONLY | O_CREAT, st_mode | S_IWRITE ); if ( out < 0 ) ErrorExit("Could not open dst:", dst); while( (rlen = read( in, buffer, COPYSIZE )) > 0 ) { wlen = write( out, buffer, rlen ) ; if ( wlen != rlen ) { ferror = 1; break; } } close(in) ; close(out) ; /* ** File error ** Delete target */ if ( ferror || rlen < 0 ) { unlink(dst) ; return 0; } return 1; } /*---------------------------------------------------------------------------- ** FUNCTION : CopyLink ** ** DESCRIPTION : Just copy a symlink ** ** ** INPUTS : src - source path to a symlink - already exists ** dst - path. Dirs already exist ** lsize - size of the link text ** ** RETURNS : false - Error ** ----------------------------------------------------------------------------*/ int CopyLink ( char *src, char *dst, int lsize ) { char *linkname = malloc(lsize + 1); ssize_t rlen = 0 ; int rv; if (linkname == NULL) { ErrorExit("Malloc error: %s", src); } rlen = readlink(src, linkname, lsize + 1); if (rlen < 0) { ErrorExit("Reading Link: %s", src); } rv = symlink( linkname, dst); if (rv) { unlink(dst) ; ErrorExit("Reading Link: %s", src); } return 1; } /*---------------------------------------------------------------------------- ** 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, char* argv[] ) { int rv; struct stat fstat; char* baseDir; DIR *dir; struct dirent *dirent; char *target; /* ** Extract the base directory from the argument list ** This must be a directory */ baseDir = argv[3]; /* ** Ensure that the base directory exists */ rv = stat( baseDir, &fstat ); if ( rv != 0 ) { /* ** Directory does not exists ** Assume its already deleted */ if ( verbose > 1 ) fprintf(stderr, "Base dir does not exist: %s\n", baseDir); return; } if ( !(fstat.st_mode & S_IFDIR)) { /* ** Target is not a directory ** Don't do anything */ if ( verbose > 1 ) fprintf(stderr, "Base dir is not a directory: %s\n", baseDir); return; } /* ** Process all the suffixes ** They may contain a wild card */ dir = opendir( baseDir ); if ( dir == NULL ) { /* ** Target is not a directory ** Don't do anything */ if ( verbose > 1 ) fprintf(stderr, "Base dir is not a directory: %s\n", baseDir); return; } /* ** Read next directory entry */ while ( (dirent = readdir(dir)) != NULL ) { int ii; if ( strcmp( ".", dirent->d_name ) == 0 ) continue; if ( strcmp( "..", dirent->d_name ) == 0 ) continue; if ( verbose > 2 ) fprintf(stderr, "Directory Entry:%s,%s\n", baseDir, dirent->d_name ); /* ** Compare against each item in the user list */ for ( ii = 4; ii < argc ; ii++) { if ( wildcmp(dirent->d_name, argv[ii] ) ) { if ( verbose > 1 ) fprintf(stderr, "Matching: %s, %s --- Found\n", dirent->d_name, argv[ii] ); /* ** Matching file found */ target = makePath( baseDir, dirent->d_name); DeleteOneFile(target); free(target); } } } closedir(dir); /* ** Finally delete the diretory ** Unless its '.' */ if ( strcmp( ".", baseDir) != 0 ) { DeleteOneDirectory(baseDir); } } /*---------------------------------------------------------------------------- ** 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( char* baseDir ) { struct stat fstat; DIR *dir; struct dirent *dirent; char *target; int rv; /* ** A bit of a sanity test */ if ( strcmp( ".", baseDir) == 0 || strcmp( "..", baseDir) == 0 ) { fprintf(stderr, "DeleteOneDirectoryTree will not delete '.' or '..'\n"); return; } /* ** Ensure that the directory exists ** Force writable and searchable */ rv = stat( baseDir, &fstat ); if ( rv == 0 ) { if ( fstat.st_mode & S_IFDIR ) { mode_t newMode = fstat.st_mode | S_IRUSR | S_IWUSR | S_IXUSR; if ( fstat.st_mode != newMode ) { if ( verbose > 1 ) fprintf(stderr, "Force u+rwx: %s\n", baseDir); rv = chmod( baseDir, newMode ); if ( rv != 0 ) { fprintf(stderr, "Warning: Attempt to allow u+rwx access: %s\n", baseDir); } } } } /* ** Process all entries */ dir = opendir( baseDir ); if ( dir == NULL ) { /* ** Target is not a directory ** Don't do anything */ if ( verbose > 1 ) fprintf(stderr, "Base dir is not a directory: %s\n", baseDir); return; } /* ** Read next directory entry */ while ( (dirent = readdir(dir)) != NULL ) { if ( strcmp( ".", dirent->d_name ) == 0 ) continue; if ( strcmp( "..", dirent->d_name ) == 0 ) continue; if ( verbose > 2 ) fprintf(stderr, "Directory Entry:%s,%s\n", baseDir, dirent->d_name ); target = makePath( baseDir, dirent->d_name); rv = lstat( target, &fstat ); if ( rv == 0 ) { if ( fstat.st_mode & S_IFDIR ) { DeleteOneDirectoryTree( target ); } else { DeleteOneFile (target); } } free(target); } closedir(dir); /* ** Finally delete the directory ** It should now be empty */ DeleteOneDirectory(baseDir); } /*---------------------------------------------------------------------------- ** 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, char* argv[] ) { int ii; struct stat fstat; for ( ii = 3; ii < argc ; ii++) { int rv; if ( verbose > 2) fprintf(stderr, "RmItem: %s\n", argv[ii]); rv = lstat( argv[ii], &fstat ); if ( rv == 0 ) { if (fstat.st_mode & S_IFDIR) { DeleteOneDirectory(argv[ii]); } else { DeleteOneFile(argv[ii]); } } } } /*---------------------------------------------------------------------------- ** 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 (char * dst ) { int rv; struct stat fstat; int result = 0; if ( verbose > 1) fprintf(stderr, "Delete File: %s\n", dst); rv = lstat( dst, &fstat ); if ( rv == 0 ) { if ( verbose ) fprintf(stderr, "Delete file: %s : Attr: 0%o\n", dst, fstat.st_mode); if ( !(fstat.st_mode & S_IWRITE) ) { fstat.st_mode |= S_IWRITE; rv = chmod( dst, fstat.st_mode ); if ( rv != 0 ) { fprintf(stderr, "Warning: Attempt to allow write access: %s\n", dst); } } if ( unlink(dst) ) { fprintf(stderr, "Warning: Did not remove file: %s\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 (char *path ) { if ( verbose ) fprintf(stderr, "Delete Directory: %s\n", path); if ( rmdir(path)) { if ( verbose ) fprintf(stderr, "Directory not deleted: %s\n", path); } } /*---------------------------------------------------------------------------- ** FUNCTION : makePath ** ** DESCRIPTION : Create a path from two elements ** Join 2 components together ** Allocate memory ** ** ** INPUTS : base - Part 1 ** file - Part 2 or NULL ** ** RETURNS : ** ----------------------------------------------------------------------------*/ char * makePath( char *base, char *path) { int len1 = strlen(base); int len2 = strlen(path); char *data; data = (char *)malloc(len1 + len2 + 10); if ( data == NULL ) { ErrorExit ("Malloc error:makePath",""); } strcpy( data,base); strcpy( data + len1, "/"); strcpy( data + len1 + 1, path); return data; } /*---------------------------------------------------------------------------- ** FUNCTION : wildcmp ** ** DESCRIPTION : Wildcard comparision ** ** ** INPUTS : string - String ** wild - Wildcard template ** ** RETURNS : TRUE - Match ** ----------------------------------------------------------------------------*/ int wildcmp(char *string, char *wild ) { char *cp, *mp; while ((*string) && (*wild != '*')) { if ((*wild != *string) && (*wild != '?')) { return 0; } wild++; string++; } while (*string) { if (*wild == '*') { if (!*++wild) { return 1; } mp = wild; cp = string+1; } else if ((*wild == *string) || (*wild == '?')) { wild++; string++; } else { wild = mp; string = cp++; } } while (*wild == '*') { wild++; } return !*wild; } /*---------------------------------------------------------------------------- ** FUNCTION : from_hex ** ** DESCRIPTION : Converts a hex character to its integer value ** Expects well formed HEX ** ** ** INPUTS : Character to convert ** ** RETURNS : Character ** ----------------------------------------------------------------------------*/ char from_hex(char 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(char *str) { char *pstr = str; char *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, char * lpszMessage2) { if ( lpszMessage ) { fprintf(stderr, "JatsFileUtil:Error:%s%s\n", lpszMessage,lpszMessage2); } fflush(stderr) ; exit(-1); }