jafischer@lily.waterloo.edu (Jonathan A. Fischer) (03/05/88)
Here is the (Mark Williams) C source for CT, the copier I posted yesterday. (Documentation was posted yesterday as well). ------------------------ Cut here & at bottom -------------------------------- /* * CT: A file-copying program that maintains time-stamps on the * destination file(s). * * AUTHOR: Jonathan Fischer, August 1987 * (latest version: 2.0, Jan. 1988) * * USAGE: ct [-bmnrv] <file1> <file2> * or ct [-bmnrv] <file1> ... <directory> * -b: Backup: only copy files newer than the destination files. * -m: Conserve memory; for MT C-Shell usage. * -n: No information is to be printed. * -r: Recursively copy directories and their contents. * -v: Request user verification when destination file exists. * * NOTE: CT now expands wildcards, so it may be called with wildcards * from the desktop. * * CT may require some hacking to compile under anything but * Mark Williams C. */ #include <stdio.h> #include <time.h> #include <ctype.h> #include <osbind.h> #define YES 1 #define NO 0 #define MAXBUFSIZE 500000 /* For Mega machines, make this as big as */ /* you like. This is typically the most */ /* I'd have free on my ST. */ #define MEMRESERVE 50000 /* Approximate amount of memory that should */ /* be reserved for mallocing. */ #define SMALLBUFSIZE (40000 + MEMRESERVE) /* * Variables used by the wildcard routines: * argv gets expanded (if wildcards exist) into newargv. */ #define ARGCLIMIT 2000 #define ARGCLIMITSTR "2000" int newargc; char *newargv[ARGCLIMIT];/* Allow for (hopefully) much more than */ /* enough space. */ typedef struct datimbuf { unsigned int seconds:5; unsigned int minutes:6; unsigned int hours:5; unsigned int day:5; unsigned int month:4; unsigned int year:7; } DATIMBUF; typedef struct { char reserved[21]; char attribute; DATIMBUF datime; int losize, hisize; char fname[14]; } DTABUF; char *malloc(), *lmalloc(), *rindex(), *strcat(), *strcpy(), *LowerStr(); void Usage(), Error(), Msg(), GoAway(), Files2Dir(), File2File(), FindWildcards(), ExpandWildcards(), CopyDir(), ReadRoot(); DATIMBUF finfo; DTABUF dta; long _stksize = 15000L; /* Override default 2K stack. */ unsigned long bufsize; int firstarg; /* First argument (after options). */ int ch; /* Character read from keyboard. */ char *buf; /* I/O buffer. */ char *prgname; /* Program name for error messages. */ char tempbuf[256]; char FromDesktop, info, verify, recurse, memconserve, backup; int main(argc, argv) int argc; char **argv; { register int loop; /* * Most shells pass the program name in argv[0]. But if argv[0] is * empty, then set the program name to "ct." */ if (*(prgname = argv[0]) == 0) { prgname = "ct"; /* Assume that ct is being run from the desktop. */ FromDesktop = YES; } /* if */ else FromDesktop = NO; if (argc < 3) { Usage(); } /* if one or no params */ /* * Set GEMDOS' dta buffer. */ Fsetdta(&dta); FindWildcards(&argc, &argv); /* Get any command line options (e.g., "-v"). */ backup = NO; memconserve = NO; info = YES; recurse = NO; verify = NO; if (argv[1][0] == '-') { firstarg = 2; for (loop = YES; argv[1][loop]; ++loop) { switch (argv[1][loop]) { case 'b': backup = YES; break; case 'm': memconserve = YES; break; case 'n': info = NO; break; case 'r': recurse = YES; break; case 'v': verify = YES; break; default: Usage(); } /* switch */ } /* for */ } /* if */ else firstarg = 1; /* Check for only one file parameter. */ if (argc - firstarg == 1) Usage(); /* * If memconserve: * Allocate a maximum of SMALLBUFSIZE bytes. * * else: * Allocate the largest buffer possible. */ bufsize = memconserve ? SMALLBUFSIZE : MAXBUFSIZE; while (((buf = lmalloc(bufsize)) == NULL) && (bufsize -= 2000) > 0) ; if (buf == NULL) Error("Out of memory.", NULL); /* * Now free the buffer and reallocate one of a size which leaves enough * room for all the mallocs later in the program. NOTE: this all could * have been done with a Malloc(-1L), but mixing Malloc and malloc seems * to be a definite no-no, and I prefer to use malloc over Malloc. */ free(buf); buf = lmalloc(bufsize -= MEMRESERVE); /* * Format I: * Backup one or more files to a destination directory. */ if (IsDir(argv[argc-1])) Files2Dir(argc, argv); /* * Format II: * Copy one file to another. */ else if (argc - firstarg == 2) File2File(argv[firstarg], argv[firstarg+1]); else Error(argv[argc-1], " is not a directory."); GoAway(0); } /* main() */ void Usage() { Cconws("Usage:\t"); Cconws(prgname); Cconws(" [-bmnrv] <file1> <file2>\r\n\t"); Cconws(prgname); Cconws(" [-bmnrv] <file> ... <directory>\r\n\n\ \t-b: Backup: only copy files newer than the destination files.\r\n\ \t-m: Conserve memory (for MT C-Shell).\r\n"); Cconws("\t-n: No information is to be printed.\r\n\ \t-r: Recursively copy directories and their contents.\r\n\ \t-v: Request user verification when destination file exists.\r\n"); GoAway(1); } /* Usage() */ void Error(string1, string2) char *string1, *string2; { Cconws(prgname); Cconws(":\t"); Cconws(string1); if (string2 != NULL) Cconws(string2); Cconws("\r\n"); GoAway(1); } /* Error() */ void Msg(string1, string2) char *string1, *string2; { Cconws(prgname); Cconws(":\t"); Cconws(string1); if (string2 != NULL) Cconws(string2); Cconws("\r\n"); } /* Msg() */ void GoAway(code) int code; { if (FromDesktop) { Cconws("\r\nHit any key to return to desktop..."); Cnecin(); Cconws("\033f"); } /* if */ exit(code); } /* GoAway() */ void File2File(source, dest) char *source, *dest; { unsigned long rcount; char retry_open; int sfile, dfile; retry_open = YES; /* Open the files. */ retry: if ((sfile = Fopen(source, 0)) < 0) { if (IsDir(source)) { if (recurse) { CopyDir(source, dest); return; } /* if */ else { Msg(source, " is a directory."); return; } /* else */ } /* if */ /* * If a disk has been swapped, GEMDOS doesn't know about any * files contained in subdirectories. So in case this is the problem, * get GEMDOS to read the root directory of the disk in question. * Then it will know about subdirectories on the new disk. */ else if (retry_open) { retry_open = NO; /* Only want to retry once. */ ReadRoot(source); goto retry; } /* if */ Msg("Can't open ", source); return; } /* if can't open file. */ /* * If "-b" is specified, check if the file exists, and if it does * then only copy over it if the source file is more recent. * * If the "-v" option has been specified, then first check if * the destination file already exists. If so, query whether * or not to replace it. */ if ((verify || backup) && Fsfirst(dest, 0x23) == 0) { if (backup) { /* Store the destination timestamp in 'finfo.' */ finfo = dta.datime; /* Structure assignment. */ Fsfirst(source, 0x23); /* Get info for source file. */ /* * Compare timestamps of source and dest. If the source isn't * more recent than the dest, then return. */ if (!MoreRecent(dta.datime, finfo)) return; } /* if */ if (verify) while (1) { Cconws("Replace file \""); Cconws(dest); Cconws("\"? (Yes/No/All) "); ch = tolower(Cnecin()); Cconws("\r\033K"); if (ch == 'y') break; if (ch == 'n') return; if (ch == 'a') { verify = NO; break; } /* if */ } /* while and if*/ } /* if */ /* Open the destination file. */ if ((dfile = Fcreate(dest, 0)) < 0) { Fclose(sfile); if (IsDir(dest)) { Msg(dest, " exists and is a directory."); return; } /* if */ /* (Note that there is no need to retry with ReadRoot() here, since */ /* this is performed by IsDir() on the destination file.) */ Msg("Can't open ", dest); return; } /* if */ if (info) { Cconws("Copying "); Cconws(source); Cconws(" to "); Cconws(dest); Cconws("\r\n"); } /* if */ /* Now read from the source file and write to the dest file. */ do { rcount = Fread(sfile, bufsize, buf); if (rcount < 0) { Fclose(sfile); Fclose(dfile); Msg("Read error on file ", source); return; } /* if */ else if (Fwrite(dfile, rcount, buf) < rcount) { Fclose(sfile); Fclose(dfile); /* Since the file wasn't successfully copied, delete it. */ Fdelete(dest); Msg("Write error on file ", dest); return; } /* else if */ } while (rcount == bufsize); Fclose(dfile); /* Read the time and date stamp of the source file. */ Fdatime(&finfo, sfile, 0); Fclose(sfile); /* And close it. */ /* * Write the stamp to the dest file. Fdatime() only seems * to work if the file is *Fopened*, rather than *Fcreated*. * As well, the third parameter value is the opposite of what * is listed in the "ATARI ST INTERNALS," and elsewhere, I * believe. */ dfile = Fopen(dest, 1); Fdatime(&finfo, dfile, 1); Fclose(dfile); } /* File2File() */ /* * ReadRoot(fname): * - Read the root directory of the drive specified in fname. This * fixes a GEMDOS bug where it won't access files in subdirectories until * it has specifically looked at the root directory of the new disk. */ void ReadRoot(fname) char *fname; { char dirspec[7]; if (fname[1] == ':') dirspec[0] = fname[0]; else dirspec[0] = Dgetdrv() + 'a'; dirspec[1] = 0; strcat(dirspec, ":\\*.*"); Fsfirst(dirspec, 0); } /* ReadRoot() */ /* MoreRecent(time1, time2): true if time1 is more recent than time2. */ int MoreRecent(time1, time2) DATIMBUF time1, time2; { if (time1.year > time2.year) return YES; else if (time1.year < time2.year) return NO; if (time1.month > time2.month) return YES; else if (time1.month < time2.month) return NO; if (time1.day > time2.day) return YES; else if (time1.day < time2.day) return NO; if (time1.hours > time2.hours) return YES; else if (time1.hours < time2.hours) return NO; if (time1.minutes > time2.minutes) return YES; else if (time1.minutes < time2.minutes) return NO; if (time1.seconds > time2.seconds) return YES; return NO; } /* MoreRecent() */ void Files2Dir(argc, argv) register int argc; register char **argv; { char *destdir, dname[128]; int loop; char *basename(); destdir = argv[argc-1]; for (loop = firstarg; loop < argc-1; ++loop) { if (destdir[strlen(destdir)-1] != '\\') File2File(argv[loop], strcat(strcat(strcpy(dname, destdir), "\\"), basename(argv[loop]))); else File2File(argv[loop], strcat(strcpy(dname, destdir), basename(argv[loop]))); } /* for */ } /* Files2Dir() */ /* * basename(fullname) * - extract the filename part of 'fullname', which may contain * the path as well. Ex: basename("a:\dir1\dir2\filename.ext") * returns a ptr to "filename.ext". */ char * basename(fullname) register char *fullname; { register char *search; return (search = rindex(fullname, '\\')) == (char *)NULL ? fullname : search + 1; } /* basename() */ /* * IsDir(): * - returns YES if its parameter is a directory. */ int IsDir(fname) char *fname; { register int stat; char curpath[256]; int curdrive; /* * Simplest way to determine if fname is a directory is to attempt * to set the current path to fname. */ stat = !SwapDirs(&curdrive, curpath, fname); /* * Failure may be due to switching disks in a floppy drive. So, do the * ReadRoot() hack. */ if (!stat) { /* Restore path. */ Dsetdrv(curdrive); Dsetpath(curpath); ReadRoot(fname); stat = !SwapDirs(&curdrive, curpath, fname); } /* if */ /* Restore path. */ Dsetdrv(curdrive); Dsetpath(curpath); return stat; } /* IsDir() */ char * LowerStr(string) char *string; { register char *chpos; for (chpos = string; *chpos; ++chpos) *chpos = tolower(*chpos); ; return string; } /* LowerStr() */ /* * CopyDir() is a recursive routine that is called by File2File() when a * directory file is encountered and "-r" had been specified by the user. * It builds a list of all of the files in the directory, and then calls * File2File() for each file. If one of these files is itself a directory, * then CopyDir() will again be called by File2File(), and so on. */ void CopyDir(sdir, ddir) char *sdir, *ddir; { /* * Need these variables on stack (as opposed to being static) * for recursion. */ char curpath[256], /* Current system path. */ destfile[256], /* Two buffers for holding */ sourcefile[256];/* full file names. */ int curdrive, stat; /* * Note: at first glance, implementing dirfiles as a linked list * of structures seems a bit ridiculous, but on a hard drive someone * may have (who knows?) one or two thousand files in one subdirectory, * and reserving space for at least that many filenames on the stack * would make the stack huge with just a few levels of recursion * (well over 100000 bytes of stack for just 4 levels of * recursion). For most (99.9%, surely) situations, just having * an array "filenames[250][14]" would be room enough, and wouldn't * take up too much room on the stack. But this code should be as * robust as possible, and I don't want it blown out of the water * by the odd extreme case. */ typedef struct dirlist { char filename[14]; struct dirlist *next; }; /* List of file names in dir. */ struct dirlist *dirfiles, *current, *tmpptr; /* * First, check for stack overflow (too much recursion). This is an MWC * trick, and not portable. */ if ((long)&stat <= _stksize+32) /* The 32 is arbitrary. */ Error("*** Stack overflow ***", NULL); if (info) { Cconws("\033pCopying directory "); Cconws(sdir); Cconws(" to "); Cconws(ddir); Cconws("\033q\r\n"); } /* if */ /* * Create the destination directory. (If it already exists, the Dcreate * will fail.) (Or should I check for bizarre errors?) */ stat = Dcreate(ddir); SwapDirs(&curdrive, curpath, sdir); /* * Must build up a complete list of files in the directory before * doing any copying, since Fsfirst/Fsnext won't work with recursion. */ for (dirfiles = NULL, stat = Fsfirst("*.*", 0x33); !stat; stat = Fsnext()) { /* Ignore "." and ".." */ if (strcmp(dta.fname, ".") && strcmp(dta.fname, "..")) { /* * Allocate a new node in the linked list of file * names for this directory: */ if (dirfiles == NULL) { /* Allocate the head of the list. */ if ((current = dirfiles = malloc(sizeof(struct dirlist))) == NULL) Error("Out of memory.", NULL); } /* if */ else { if ((current->next = malloc(sizeof(struct dirlist))) == NULL) Error("Out of memory.", NULL); current = current->next; } /* if */ current->next = NULL; /* Probably unnecessary. */ strcpy(current->filename, LowerStr(dta.fname)); } /* if */ } /* for */ /* * Restore the current directory, now that we're done looking at the * source directory. */ Dsetdrv(curdrive); Dsetpath(curpath); /* Now copy the files in the directory. */ current = dirfiles; while (current != NULL) { File2File(strcat(strcat(strcpy(sourcefile, sdir), "\\"), current), strcat(strcat(strcpy(destfile, ddir), "\\"), current)); tmpptr = current->next; free(current); current = tmpptr; } /* for */ return; } /* CopyDir() */ /* * SwapDirs() saves the current directory, and changes to the specified * new directory. Returns an error code. */ int SwapDirs(curdrive, curpath, destpath) int *curdrive; char *curpath, *destpath; { register char *pathpart; /* Points to path part of destpath. */ register int stat; /* Save the current directory path. */ *curdrive = Dgetdrv(); Dgetpath(curpath, 0); if (curpath[0] == 0) { curpath[0] = '\\'; curpath[1] = 0; } /* if */ /* Set the current directory to the source directory. */ if (destpath[1] == ':') { /* Is a drive specification included? */ if ((stat = Dsetdrv(_toupper(destpath[0]) - 'A')) < 0) return stat; pathpart = destpath + 2; } /* if */ else pathpart = destpath; stat = Dsetpath(pathpart); return stat; } /* SwapDirs() */ /* * FindWildcards(): * - Finds wildcard parameters in "oldargv" (passed by main()), and * calls ExpandWildcards() to expand them. */ void FindWildcards(oldargc, oldargv) int *oldargc; char ***oldargv; { register int arg; register char *chpos; /* * Search through arguments and find any wildcard expressions. Since * MWC hasn't implemented strpbrk(), I'll have to search * for the wildcard characters in a loop. * While we're at it, force the argument into lower case. */ for (arg = newargc = 1; arg < *oldargc; ++arg) { LowerStr((*oldargv)[arg]); for (chpos = (*oldargv)[arg]; *chpos; ++chpos) if (*chpos == '*' || *chpos == '?' || *chpos == '[' || *chpos == ']') break; if (*chpos) ExpandWildcards((*oldargv)[arg]); else { newargv[newargc++] = (*oldargv)[arg]; if (newargc >= ARGCLIMIT) Error("Too many file names; limit is ", ARGCLIMITSTR); } /* else */ } /* for */ /* Now set oldargc and oldargv to newargc and newargv. */ *oldargc = newargc; *oldargv = newargv; } /* FindWildcards() */ void ExpandWildcards(wildcard) char *wildcard; { int curdrive, stat; int IncludesPath; /* Flag. */ char curpath[256], *base; /* Determine if a directory path is contained in the wildcard. */ if (IncludesPath = ((base = basename(wildcard)) != wildcard)) { *(base-1) = 0; /* Lop off the filename part of the path. */ SwapDirs(&curdrive, curpath, wildcard); } /* if */ /* Search directory entries for matches with the wildcard expression. */ for (stat = Fsfirst("*.*", 0x33); !stat; stat = Fsnext()) { /* Ignore "." and ".." */ if (strcmp(dta.fname, ".") && strcmp(dta.fname, "..")) { if (pnmatch(LowerStr(dta.fname), base, 0)) { /* * If the wildcard includes a path spec, then concatenate * the filename on the end of the path. */ if (IncludesPath) strcat(strcat(strcpy(tempbuf, wildcard), "\\"), dta.fname); else strcpy(tempbuf, dta.fname); if ((newargv[newargc] = malloc(strlen(tempbuf))) == NULL) Error("Out of memory.", NULL); strcpy(newargv[newargc++], tempbuf); if (newargc >= ARGCLIMIT) Error("Too many file names; limit is ", ARGCLIMITSTR); } /* if */ } /* if */ } /* for */ /* * Restore the current directory, now that we're done looking at the * source directory. */ if (IncludesPath) { Dsetdrv(curdrive); Dsetpath(curpath); } /* if */ } /* ExpandWildcards() */ -- - Jonathan A. Fischer, jafischer@lily.waterloo.edu ...{ihnp4,allegra,decvax,utzoo,utcsri}!watmath!lily!jafischer "My computer understands me." - Old button of mine.