[comp.sys.atari.st] CT.C

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.