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.