[comp.sys.amiga] MRBackup V1.3 Source, Part 2 of 5

mrr@softie.UUCP (09/26/87)

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file"
# Created Fri Sep 25 07:25:56 1987
#
# This archive contains:
#		CopyFile.c
#		Gadget.h
#		InitBuffer.c
#		MRBackup.h
#		Main.c
echo "Creating CopyFile.c"
cat > CopyFile.c <<"***EOF CopyFile.c***"
/* MRBackup: File Copy Routine.
 * Date:		09/04/87
 * Notes:
 *		To enhance the performance of MRBackup, this package was copied
 * from my Misc library and modified to couple it more tightly with
 * MRBackup.  It now uses a global buffer area allocated during
 * initialization.
 *
 * History:		(most recent change first)
 *
 * 09/04/87 -MRR- CopyFile, upon failure, now returns the error status
 *                code from IoErr().
 */

#include "MRBackup.h"

/* 
 * Copy file, preserving date.  
 * Depends upon file date routines in FileMisc.c 
 * Original author: Jeff Lydiatt, Vancouver, Canada
 */

#include <stdio.h>
#include <exec/types.h>
#include <libraries/dos.h>
#include <exec/memory.h>
#include <functions.h>

#define MAXSTR 127
 
extern long Chk_Abort();
extern BOOL GetFileDate(), SetFileDate();
extern long IoErr();

/* Copy the last modified date from one file to another.
 * Called with:
 *		from:		name of source file
 *		to:			name of destination file
 * Returns:
 *		0 => success, 1 => failure
 * Note:
 *		Dynamic memory allocation of the DateStamp struction is
 *		necessary to insure longword alignment.
 */

BOOL 
CopyFileDate(from,to)
	char *from, *to;
{
	struct DateStamp *date;
	int status = 1;				/* default is fail code */

	if (date = (struct DateStamp *) 
		AllocMem((long) sizeof(struct DateStamp), MEMF_PUBLIC)) {
		if (GetFileDate(from,date))
			if (SetFileDate(to,date))
				status = 0;
		FreeMem(date, (long) sizeof(struct DateStamp));
	}
	return status;
}

int     
CopyFile(from,to)
	char *from,*to;
{
	long status, count;
	struct FileHandle *fin,*fout;

#ifdef DEBUG
	char	errmsg[256];
	static char *errfmt = "I/O error %ld, file %s";
#endif

	if ((fin = Open(from, MODE_OLDFILE ))== NULL ){
badcopy:
		status = IoErr();
#ifdef DEBUG
		sprintf(errmsg, errfmt, status, from);
print_err:
		puts(errmsg);
#endif
		if (fin) Close(fin);
		if (fout) {
			Close(fout);
			unlink(to);				/* delete the bad copy */
		}
		return (int) status;
	}

	if ( !(fout = Open(to, MODE_NEWFILE)) ) goto badcopy;

	status = 0;
	while ( !status && (count = Read( fin, buffer, bufsize )) == bufsize )
		if ( Write(fout, buffer, count) != count)
				status = IoErr();

	if (!status && count > 0 ) {
		if (Write(fout, buffer, count) != count)
			status = IoErr();
	}

	if (status) goto badcopy;

	Close(fin);
	Close(fout);

	return CopyFileDate(from, to);
}
***EOF CopyFile.c***
echo "Creating Gadget.h"
cat > Gadget.h <<"***EOF Gadget.h***"

/*  Definitions for Gadget ID numbers               */ 

#define  HOMEPATHGAD 0
#define  BACKPATHGAD 1
#define  LISTPATHGAD 2
#define  XCLDPATHGAD  3
#define  STOPGAD	 4
***EOF Gadget.h***
echo "Creating InitBuffer.c"
cat > InitBuffer.c <<"***EOF InitBuffer.c***"

	for (bufsize = BUFMAX; bufsize > 2048; bufsize -= 2048 )
		if ((buffer = malloc((unsigned int)bufsize))!= NULL )
			break;

***EOF InitBuffer.c***
echo "Creating MRBackup.h"
cat > MRBackup.h <<"***EOF MRBackup.h***"
/* MRBackup - include file for global data and definitions.
 * Filename:	MRBackup.h
 * Date:		08/22/87
 *
 * History:		(most recent change first)
 *
 * 09/03/87 -MRR- V1.3: Changed window version and date.
 */

/* Main.c defines MAIN.  It should not defined elsewhere. */

#ifdef MAIN
#define EXTERN 
#else
#define EXTERN extern
#endif

#include <exec/memory.h>
#include <exec/types.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include <time.h>
#include <functions.h>

#include "gadget.h"
#include "menu.h"

/* Constants */

#define false	0		/* for short parameter requirements */
#define true	1		/* for short parameter requirements */
#define BUFMAX (32L * 1024L) /* max size for copy/compress buffer */
#define LINES_PER_PAGE  60

#define VOLUME_MAX	32	/* max characters in volume name */
#define PATH_MAX	256 /* max characters in pathname (very arbitrary) */

/* Define error recovery constants.  Note that these are all powers
 * of 2 to allow creating 'sets' of allowable options during the
 * recovery prompt.
 */

#define NERRCODE			5	/* number of error recovery codes */

#define ERR_NONE			0	/* what we want ALL the time :-) */
#define ERR_ABORT			1	/* give up the ship */
#define ERR_RETRY_FILE		2	/* let's try that file one more time */
#define ERR_RESTART_VOLUME 	4	/* for media errors on output floppy */
#define ERR_IGNORE			8	/* ignore this error and trudge on */

/* Macros */

/* determine if a menu item is "checked" */

#define GadgetString(g) ((struct StringInfo *) g->SpecialInfo)->Buffer
#define IsChecked(item) (item->Flags & CHECKED == CHECKED)

typedef struct t_pattern {
	struct t_pattern * next_pattern;
	char *pattern;
	} T_PATTERN;

/* The following structure is used to link file and directory node
 * information into a doubly-linked list.  This provides a way to
 * defer processing of sub-directory nodes until all files in a
 * current directory are processed.  As nodes are "consumed", they 
 * are returned to free memory.
 */

typedef struct t_file {
	struct t_file  *previous,*next;
	char   			*filename;
	USHORT			blocks;
	BOOL 			is_dir;			/* TRUE => it's a directory */
	} T_FILE;

/* The following structure links lists of T_FILE nodes. */

typedef struct t_file_list {
	T_FILE *first_file;
	T_FILE *last_file;
	} T_FILE_LIST;

/* External and forward function declarations */

extern char   	*calloc(), *index(), *rindex();
extern long   	DiskBlocks();
extern int	  	errno;
T_FILE 		  	*FindFile();

/* External data */

extern struct Gadget StopGad;
extern struct Menu Titles[];
extern struct MenuItem Items[];
extern struct Window *pathwindow;

/* Global data */

#ifdef DEBUG
EXTERN struct FileHandle *debugconsole;
EXTERN char debugmsg[512];
#endif

EXTERN short back;					/* backup disk serial number */
EXTERN UBYTE *buffer;				/* file copy/cmprs buffer (AllocMem) */
EXTERN ULONG bufsize;				/* size of buffer allocated */
EXTERN struct FileHandle *console;	/* for informative messages */
EXTERN char conmsg[512];
EXTERN T_FILE *current_dir = NULL;	/* current directory node */

EXTERN char	destpath[PATH_MAX+1];
EXTERN char destvol[VOLUME_MAX+1];

EXTERN BOOL exclude_has_changed;	/* true when new path specified */
EXTERN T_PATTERN *excludelist, *lastexclude;
EXTERN char excludepath[81]; 		/* list of file patterns to exclude */
EXTERN struct IntuitionBase *IntuitionBase;

EXTERN USHORT level;				/* file nesting level */
EXTERN USHORT linecount;			/* number of lines in listing */
EXTERN FILE *listing;
EXTERN T_FILE_LIST main_list;
EXTERN struct Window *mywindow;

EXTERN struct DateStamp *now, *since; /* for date comparisons */
EXTERN short size;					/* floppy blocks remaining */
EXTERN char	srcpath[PATH_MAX];
EXTERN char	srcvol[VOLUME_MAX+1];	/* source volume name */
EXTERN char temp[256];

/* The following flags suppress repetition of spoken
 * messages.  After all, let's not over-do it.
 */

EXTERN UBYTE at_your_service;

/* Preset data */

#ifdef MAIN

char backpath[81] = "DF0:";			/* where backups go and restores
									   come from. */
char destdrive[5] = "DF0:";
USHORT do_compress = 1;				/* compression flag */
USHORT do_listing = 1;				/* listing flag */
USHORT do_speech = 1;				/* speech flag */
char *erropts[NERRCODE] = {			/* error recovery options */
	"No error", 
	"Abort processing",
	"Retry this file",
	"Restart the output volume",
	"Ignore this error"
	};

char homepath[81] = "DH0:";			/* where files are backed up from and
									   restored to. */
char listpath[81] = "PRT:";			/* where we send all of that vital
									   information about backups */
struct NewWindow    nw = { 			/* New window structure */
	0,0,640,200,0,1,

/* IDCMP Flags */

	MENUPICK | MOUSEBUTTONS | DISKINSERTED |
	CLOSEWINDOW | GADGETDOWN | GADGETUP | REQSET,

/* Flags */
	WINDOWCLOSE | WINDOWDEPTH | ACTIVATE ,

	NULL,							/* First gadget */
	NULL,							/* Checkmark */
	(UBYTE *)"MRBackup Version 1.3  09/03/87",/* Window title */
	NULL,							/* No custom streen */
	NULL,							/* Not a super bitmap window */
	0,0,640,200,					/* Not used, but set up anyway */
	WBENCHSCREEN
};
#else
/* Declare preset external data without the presets. */
extern char backpath[81];
extern char destdrive[5];
extern USHORT do_compress;			/* compression flag */
extern USHORT do_listing;			/* listing flag */
extern USHORT do_speech;			/* speech flag */
extern char destdrive[];
extern char *erropts[];
extern char homepath[81];
extern char listpath[81];
extern struct NewWindow nw;
#endif
***EOF MRBackup.h***
echo "Creating Main.c"
cat > Main.c <<"***EOF Main.c***"
/* MRBackup - Amiga Hard Disk Backup Utility
 * Filename:	Main.c
 * Author:		Mark R. Rinfret
 * Date:		08/01/87
 *
 * This program has been contributed to the public domain.  It represents
 * a collection of original work and other public domain offerings that 
 * were found to be useful in writing this application.  To the best of
 * my knowledge, this program works as described in the accompanying
 * document, but no warranties are made in this regard.
 * USE AT YOUR OWN RISK.
 *
 * If you find this program useful, or if you have comments or suggestions
 * for enhancing it, please send email to mark@unisec.USI.COM or U.S. Mail
 * to:
 *		Mark R. Rinfret
 *		348 Indian Avenue
 *		Portsmouth, RI  02871
 *		401-846-7639 (home)
 *		401-849-4174 (work)
 *
 * History:		(most recent change first)
 *
 * 09/04/87 -MRR- V1.3: Extracted routines related to the backup function
 *                and placed them in package Backup.c.
 *
 * 09/03/87 -MRR- V1.3: Fixed a bug in the routines which handle the
 *                listing file and pathname.
 *				  Extended the IsCompressed() function to check for
 *                .ARC and .ZOO files.
 *
 * 08/22/87 -MRR- V1.2: Extracted global data and definitions and placed
 *                them in a common include file.
 *
 * 08/11/87 -MRR- V1.1: BUG!  The variable 'back' (backup sequence number) 
 *				  was not set to 0 by Backup().
 */

#define MAIN

#include "MRBackup.h"
extern struct Requester *pathrequest;
extern struct Window *pathwindow;


/* Main program  - N.S.D.T.! */

main(argc,argv)
int     argc;
char   *argv[];
{

	Initialize();
	User();					/* it's in the user's hands */
	CleanUp(NULL, 0);
}

/* Initialize the program. */

Initialize()
{
	if (! (IntuitionBase = (struct IntuitionBase *)
			OpenLibrary("intuition.library", 33L ) ) ) {
		CleanUp("Can't open Intuition library!", 20);
	}

	if (!( mywindow = OpenWindow(&nw) ) )
		CleanUp("Can't open program window!", 20);

#ifdef DEBUG
	if (!(debugconsole = 
		Open("CON:0/100/640/40/Debug Info", MODE_NEWFILE))) 
		CleanUp("Can't open debug console!", 20);
#endif

	if (!(console = 
		Open("CON:0/100/640/90/Progress Report", MODE_NEWFILE)))
		CleanUp("Can't open console!", 20);

	SetMenuStrip(mywindow, &Titles[0]);

	InitPathRequest();

	AddGadget(mywindow, &StopGad, -1L);
	OnGadget(&StopGad, mywindow, NULL);

	now = (struct DateStamp *) 
		AllocMem((long) sizeof(struct DateStamp), MEMF_PUBLIC);

	since = (struct DateStamp *)
		AllocMem( (long) sizeof(struct DateStamp), MEMF_PUBLIC);

	InitBuffer();					/* Allocate copy/compress buffer */
	GetUserPrefs();					/* Get user preferences */
	SetSpeech();
}

/* Allocate the buffer used for copy/compress operations. */

InitBuffer()
{
	for (bufsize = BUFMAX; bufsize > 2048; bufsize -= 2048 )
		if (buffer = AllocMem(bufsize, MEMF_CHIP))
			return;

	CleanUp("Not enough memory for copy buffer!\n",20);
}

/* Handle program termination.
 * Called with:
 *		msg:		termination message or NULL
 *		code:	 	exit code (0 => normal termination)
 */

CleanUp(msg, code)
	char *msg; int code;
{
	if (msg)
		puts(msg);

#ifdef DEBUG
	if (debugconsole) Close(debugconsole);
#endif

	if (console) Close(console);

	if (listing) fclose(listing);

	if (mywindow) {
		ClearMenuStrip(mywindow);
		CancelPathRequest();
		CloseWindow(mywindow);
	}

	if (IntuitionBase) CloseLibrary(IntuitionBase);
	if (now) FreeMem( now, (long) sizeof(struct DateStamp));
	if (since) FreeMem( since, (long) sizeof(struct DateStamp));
	if (buffer) FreeMem(buffer, bufsize);
	exit(code);
}

/* Break a full file specification into its volume and pathname components.
 * Called with:
 *		fullpath:	full pathname string (input)
 *		volume:		volume name component, sans colon (output)
 *		path:		pathname component (output)
 * Returns:
 *		status code: 1 => no volume name specified, 0 otherwise
 */

BreakPath(fullpath,volume,path)
	char *fullpath, *volume, *path;
{
	char c, *fp, *s;
	unsigned has_volume = 1;			/* assume it has volume component */

	fp = fullpath;
	s = volume;
	if ( index(fp, ':') ) {				/* volume name specified? */
		s = volume;
		while ((c = *fp++) != ':') *s++ = c;
	}
	else
		has_volume = 0;

	*s = '\0';							/* terminate volume */
	strcpy(path, fp);					/* the rest is pathname stuff */
	return has_volume;
}

/* Do a quick poll on the STOP gadget.
 * Returns 1 if STOP gadget hit, 0 otherwise.
 */

int
CheckStop()
{
	ULONG class;
	struct Gadget *gadget;
	struct IntuiMessage *msg;
	int status = 0;

	if (msg = (struct IntuiMessage *) GetMsg(mywindow->UserPort)) {
		class = msg->Class;
		gadget = (struct Gadget *) msg->IAddress;
		ReplyMsg(msg);
		if (class == GADGETUP && gadget->GadgetID == STOPGAD) {
			TypeAndSpeak("I am stopping, as you requested.\n");
			status = ERR_ABORT;		/* quit */
		}
	}
	return status;
}

#ifdef DEBUG
DebugWrite(msg)
	char *msg;
{
	Write(debugconsole, msg, (long) strlen(msg));
}
#endif

/* Handle a gadget action.
 * Called with:
 *      window:		window that gadget is displayed in
 *		class:		message class (GADGETUP/GADGETDOWN/?)
 *		addr:		pointer to gadget structure
 */
DoGadget(window, class, addr)
	struct Window *window; ULONG class; struct Gadget *addr;
{
	USHORT id;					/* gadget identifier */
	struct Gadget *upgad;
	ULONG upclass;
	struct IntuiMessage *upmsg;	/* require gadget up to complete */

	id = addr->GadgetID;

#ifdef TESTING
	if (class == GADGETDOWN && window == pathwindow) {/* pathname gadget */
		ActivateWindow(pathwindow);
		do {
			Wait(1L << pathwindow->UserPort->mp_SigBit);
			if (upmsg = (struct IntuiMessage *) 
				GetMsg(pathwindow->UserPort)) {
				upclass = upmsg->Class;
				upgad = (struct Gadget *) upmsg->IAddress;
				ReplyMsg(upmsg);
				if (upclass == GADGETUP && upgad->GadgetID == id) {
					class = GADGETUP;	/* use new class */
				}
				else {
					upmsg = NULL;
					ActivateGadget(addr, pathwindow, pathrequest);
				}
			}
		}
		while (! upmsg );
	}
#endif

	if (class == GADGETUP) {
#ifdef DEBUG
		sprintf(debugmsg,"GADGETUP: %d\n",id);
		DebugWrite(debugmsg);
#endif
		switch (id) {
			case HOMEPATHGAD:
				GetHomePath(addr);
				break;
			case BACKPATHGAD:
				GetBackPath(addr);
				break;
			case LISTPATHGAD:
				GetListPath(addr);
				break;
			case XCLDPATHGAD:
				GetXcldPath(addr);
				break;
			case STOPGAD:
				TypeAndSpeak(
	"Use the STOP gadget during backup and restore operations.\n");
				break;
			default:
				TypeAndSpeak("Unknown gadget - program error!\n");
				break;
		}
	}
}

/* Get the backup device pathname specification. 
 * Called with:
 * Called with:
 *		gadget:		pointer to relevant string gadget
 *
 * Side-effects:
 *		If the pathname specification passes its requirements tests,
 *		the global string backpath will be set to the pathname.
 *		Otherwise, homepath is left unchanged and the gadget's string
 *		is reset to the current contents of homevolume.
 */

GetBackPath(gadget)
	struct Gadget *gadget;
{
	char volume[VOLUME_MAX+1];
	UBYTE *fullpath, path[PATH_MAX+1];

	fullpath = (UBYTE *) GadgetString(gadget);

	BreakPath(fullpath,volume,path);
	if (strlen(volume) != 3 ||
		tolower(volume[0]) != 'd' ||
		tolower(volume[1]) != 'f' ||
		volume[2] < '0' || volume[2] > '3') {
		TypeAndSpeak("Backup device must be DF0 through DF3\n");
		strcpy(fullpath, backpath);	/* restore previous value */
	}
	else
		strcpy(backpath, fullpath);		/* use new value */

	RefreshGadgets(gadget, pathwindow, NULL);
}

/* An error has occurred.  Find out what the user wants to do about it.
 * Called with:
 *		msg:		message string
 *		options:	the "set" of options available
 *					0 => sorry - no options, ERR_ABORT returned
 * Returns:
 *		error recovery option
 */

int
GetErrOpt(msg, options)
	char *msg; int options;
{
	int i, mask, option_count = 0, select;
	int option_list[NERRCODE];

	sprintf(conmsg,"An error has interrupted processing:\n%s\n\n",msg);
	TypeAndSpeak(conmsg);
	return ERR_ABORT;				/* ...implement recovery... */
}

/* Get the home pathname specification (volume and pathname).
 * Called with:
 *		gadget:		pointer to relevant string gadget
 *
 * Side-effects:
 *		If the pathname specification passes its requirements tests,
 *		the global string homepath will be set to the pathname.
 *		Otherwise, homevolume is left unchanged and the gadget's string
 *		is reset to the current contents of homevolume.
 */

GetHomePath(gadget)
	struct Gadget *gadget;
{
	UBYTE *fullpath, path[PATH_MAX+1], volume[VOLUME_MAX+1];

	RemoveGadget(pathwindow, gadget);
	fullpath = (UBYTE *) GadgetString(gadget);

	BreakPath(fullpath, volume, path);
	if (!IsDir(fullpath)) {
badpath:
		TypeAndSpeak(
	"Home path must be a disk device with optional directory name!\n");
		strcpy(fullpath, homepath);	/* restore previous value */
	}
	else if (strlen(volume) != 3 ||
			tolower(volume[0]) != 'd' ||
			(tolower(volume[1]) != 'h' && tolower(volume[1]) != 'f') ||
			!isdigit(volume[2])) {
#ifdef DEBUG
			sprintf(debugmsg,"home device = \"%s\" ? \n",volume);
			DebugWrite(debugmsg);
#endif
			goto badpath;
	}
	else {
		strcpy(homepath, fullpath);
		sprintf(conmsg,"Home path is %s\n", homepath);
		WriteConsole(conmsg);
	}
	ResetStringInfo(gadget->SpecialInfo);
	AddGadget(pathwindow, gadget, -1L);
	RefreshGadgets(gadget, pathwindow, NULL);
}	

/* Get the listing pathname.
 * Called with:
 *		gadget:		pointer to relevant string gadget
 *
 * Side-effects:
 */

GetListPath(gadget)
	struct Gadget *gadget;
{
	UBYTE *path;

	RemoveGadget(pathwindow, gadget);
	path = (UBYTE *) GadgetString(gadget);
	if (!do_listing) {
		TypeAndSpeak("Listing mode is not active.  ");
		TypeAndSpeak("Your change has been ignored.\n");
badpath:
		strcpy(path, listpath);
	}
	else {
		if (strcmp(path, listpath)) {	/* not same pathname */
			if (OpenList(path)) goto badpath;
			strcpy(listpath, path);
			sprintf(conmsg,"Listing path is %s\n", listpath);
			WriteConsole(conmsg);
		}
	}
	ResetStringInfo(gadget->SpecialInfo);
	AddGadget(pathwindow, gadget, -1L);
	RefreshGadgets(gadget, pathwindow, NULL);
}

GetXcldPath(gadget)
	struct Gadget *gadget;
{
	UBYTE *path;

	RemoveGadget(pathwindow, gadget);
	path = (UBYTE *) GadgetString(gadget);
	if (!strcmp(path, excludepath))		/* same pathname */
		return;
	if (access(path, 0)) {				/* can't access the file? */
		TypeAndSpeak("I can't access the exclude file!\n");
		strcpy(path, excludepath);		/* restore the gadget */
	}
	else {
		strcpy(excludepath, path);
		exclude_has_changed = true;
		sprintf(conmsg,"Exclude path is %s\n", excludepath);
	}
	ResetStringInfo(gadget->SpecialInfo);
	AddGadget(pathwindow, gadget, -1L);
	RefreshGadgets(gadget, pathwindow, NULL);
}

/* Output a new header to the listing file. */

Header()
{
	if (do_listing) {
		fprintf(listing,"\f    MRBackup Listing of Volume %s\n\n", destvol);
		linecount = 2;
	}
}

/* Determine if a file is compressed.  This is currently done by looking
 * at the file name suffix.  A rather dumb assessment of the file type
 * is made based on a match to one of these suffixes.
 * 
 * Called with:
 *		filename:	file name string
 * Returns:
 *		1 => file is compressed, 0 => file is not compressed
 */

int     
IsCompressed(filename)
	char *filename;
{
#define NSUFFIX 3			/* number of known suffixes */
typedef struct {
	char *sufx;
	USHORT length;
	} T_SUFFIX;

static T_SUFFIX suffixes[NSUFFIX] = {
	{".z", 2} ,
	{".arc", 4}, 
	{ ".zoo", 4}
	};

	USHORT i, length;
	char *s;

	length = strlen(filename);	/* get length of argument filename */

	for (i = 0; i < NSUFFIX; ++i) {
		if (length < suffixes[i].length) continue;
		/* We're just interested in the current suffix length. */
		s = filename + length - suffixes[i].length;
		if (!strcmpc(s, suffixes[i].sufx))
			return true;		/* suffixes match */
	}
	return false;
}

/* Output a line to the listing file.  Start a new line if necessary.
 * The output string is assumed to have no form control characters.
 * Called with:
 *		str:		string to be output
 */
ListLine(str)
	char *str;
{	
	if (do_listing) {
		if (linecount >= LINES_PER_PAGE) Header();
		fprintf(listing,"  %s\n",str);
		fflush(listing);
		++linecount;
	}
}

/* Create a new directory on the destination disk.
 * Called with:
 *		name:		directory pathname
 * Returns:
 *		false => success
 *		true  => failure
 * Notes:
 *		NewDir may be called by NewDisk() or BackupDirs().  You should
 *		note that a condition of mutual recursion between CheckSize and
 *		NewDir exists, due to the following:
 *
 *		When NewDir is called from BackupDirs(), it calls CheckSize(),
 *		which in turn calls NewDisk() if there is no space left.
 *		The create_dir parameter to CheckSize() (false) prevents a
 *		secondary call to NewDir() since the job is already being
 *		performed.
 *
 *		When CheckSize() is called as a result of BackupFiles()
 *		processing, the create_dir parameter is true so that NewDir()
 *		will be called if NewDisk() is called, thus creating a
 *		continuation of the current directory.  Confused?  Me too :-).
 */
int
NewDir(name)
	char   *name;
{
	char c;
	struct Lock *dirlock;
	int dirleng;
	int errnum;
	char dirname[256];
	int nameindx = 0, nameleng;

	size--;							/* takes a block for a directory */
	if (CheckSize(false))			/* have room on disk? */
		return 1;

	strcpy(dirname,destvol);		/* start with volume name */
	dirleng = strlen(dirname);
	nameleng = strlen(name);

	/* Parse the pathname, one directory node at a time, creating
	 * directories as needed.
	 */

	while (nameindx < nameleng) {
		if (nameindx)				/* 2nd - nth pass? */
			dirname[dirleng++] = '/'; /* directory separator */
		while ((c = name[nameindx++]) && c != '/')
			dirname[dirleng++] = c;
		dirname[dirleng] = '\0';	/* terminate with null */
		if (dirlock = Lock(dirname,SHARED_LOCK)) /* subdir exists? */
			UnLock(dirlock);
		else {						/* create subdirectory */
			if ((dirlock = CreateDir(dirname))== NULL){
				if ((errnum = IoErr())== ERROR_DIRECTORY_NOT_EMPTY){
					sprintf(conmsg,
						"Directory %s already exists!\n",dirname);
					TypeAndSpeak(conmsg);
				}
				else {
					sprintf(conmsg,
						"ERROR %d: Unable to create directory %s\n",
						errnum,dirname);
					TypeAndSpeak(conmsg);
					return 1;
				}
			}
			else
				UnLock(dirlock);
		}
	}								/* endwhile */
	return 0;
}

/* Skip 'n' lines in the listing file.
 * Called with:
 *		n:		number of lines to skip
 */

NewLine(n)
	int n;
{
	if (do_listing) {
		if (n + linecount >= LINES_PER_PAGE)
			Header();
		else while (n--) {
			fputc('\n', listing);
			++linecount;
		}
	}
}

/* Open the listing file.
 * Called with:
 *		name:		file or device pathname
 * Returns:
 *		status (0 => success)
 */
int
OpenList(name)
	char *name;
{
	int status = 0;

	if (listing) fclose(listing);		/* prior listing file open? */
	if (!(listing = fopen(name, "w"))) {
		status = errno;
		sprintf(conmsg,
				"I can't open the listing file \"%s\", error %d.\n",
				name, status);
		TypeAndSpeak(conmsg);
	}
	else
		linecount = LINES_PER_PAGE;
	return status;
}

/* Reset the variables in a StringInfo structure.
 * Called with:
 *		s:		pointer to a StringInfo
 */
ResetStringInfo(s)
	struct StringInfo *s;
{
	*(s->UndoBuffer) = '\0';
	s->BufferPos = 0;
	s->DispPos = 0;
	s->UndoPos = 0;
	s->NumChars = strlen(s->Buffer);
}

/* Enable/disable speech capability, based on global 'do_speech'. */

SetSpeech()
{
	if (do_speech) {
		if (!SpeechOn()) {
			DateStamp(now);
			if (now->ds_Tick < 1500)
				Say("That feels good!  Thanks for turning me on!");
			else
				Say("Hi.  How may I help you?");
		}
	}
	else
		SpeechOff();
}

Speak(msg)
	char *msg;
{
	if (do_speech) Say(msg);
}

/* Perform a case-insensitive string compare.
 * Called with:
 *		s1, s2:		strings to be compared
 * Returns:
 *		comparison code:	0 	=> strings match
 *						   <0	=> s1 < s2
 *						   >0	=> s1 > s2
 */
int
strcmpc(s1, s2)
	register char *s1, *s2;
{
	int c1, c2, cd;

	do {
		c1 = tolower(*s1++);
		c2 = tolower(*s2++);
		if (cd = (c1 - c2)) break;
	} while (c1 && c2);

	return cd;
}

/* Type a message to the console and optionally "speak" it.
 * Called with:
 *		msg:		message string
 */
TypeAndSpeak(msg)
	char *msg;
{
	WriteConsole(msg);
	if (do_speech) Say(msg);
}

/* Handle IDCMP messages generated by user actions. */

User()
{
	ULONG class;				/* message class */
	USHORT code;				/* message code */
	USHORT gadgid;				/* gadget ID */
	APTR Iadr;					/* address field from message */
	struct IntuiMessage *msg;	/* Intuition message pointer */
	struct Window *msgwindow;	/* window message occurred in */
	USHORT quit = 0;
	SHORT x,y;					/* mouse x and y position */
	ULONG waitbits;

	waitbits = (1L << mywindow->UserPort->mp_SigBit) |
			   (1L << pathwindow->UserPort->mp_SigBit);
#ifdef DEBUG
	sprintf(debugmsg,"User: waitbits = %08lx\n", waitbits);
	DebugWrite(debugmsg);
#endif

	while (!quit) {
		ActivateWindow(mywindow);
		Wait(waitbits);
		while (!quit) {
			if (!(msg = (struct IntuiMessage *) 
				GetMsg(mywindow->UserPort)))
				if (!(msg = (struct IntuiMessage *)
					  GetMsg(pathwindow->UserPort)))
					break;

			class = msg->Class;
			code = msg->Code;
			Iadr = msg->IAddress;
			x = msg->MouseX;
			y = msg->MouseY;
			msgwindow = msg->IDCMPWindow;
			ReplyMsg(msg);		/* acknowledge the message */
#ifdef DEBUG
			sprintf(debugmsg,"Message class: 0x%lx, code: 0x%x\n",
				class, code);
#endif
			switch (class) {
			case CLOSEWINDOW:
				++quit;
				break;

			case GADGETUP:
			case GADGETDOWN:
				DoGadget(msgwindow, class, Iadr);
				break;

			case MENUPICK:
				quit = UserMenu(code);
				break;

			default:
				break;			/* ignore the rest */
			}					/* end switch(class) */
		}
	}
}

/* Handle a menu selection. 
 * Called with:
 *		xcode:		menu selection code
 * Returns:
 *		status code (1 => Quit was selected)
 */

int
UserMenu(xcode)
	USHORT xcode;
{
	USHORT code = xcode;
	struct MenuItem *item;
	USHORT itemnum,menunum;

	while (code != MENUNULL) {
		menunum = MENUNUM(code);
		itemnum = ITEMNUM(code);

#ifdef DEBUG
		sprintf(debugmsg,"menu = %d, item = %d\n",menunum,itemnum);
		DebugWrite(debugmsg);
#endif
		item = ItemAddress(&Titles[0], code);

		switch (menunum) {
		case MENU_PROJECT:			/* Project Menu */
			switch (itemnum) {
			case ITEM_BACKUP:		/* Backup */
				Backup();
				break;
			case ITEM_RESTORE:		/* Restore */
				Restore();
				break;
			case ITEM_ABOUT:		/* About */
				About();
				break;
			case ITEM_QUIT:			/* Quit */
				return 1;			
			default:
				DisplayBeep(NULL);
				break;
			}
			break;

		case MENU_FLAGS:			/* Flags Menu */
			switch (itemnum) {
			case ITEM_COMPRESS:		/* Compression */
				do_compress = IsChecked(item); 
				break;
			case ITEM_NOCOMPRESS:	/* No Compression */
				do_compress = !IsChecked(item);
				break;
			case ITEM_LIST:			/* Listing */
				do_listing = IsChecked(item);
				break;
			case ITEM_NOLIST:		/* No Listing */
				do_listing = !IsChecked(item);
				break;
			case ITEM_SPEECH:		/* Speech */
				do_speech = IsChecked(item);
				SetSpeech();
				break;
			case ITEM_NOSPEECH:		/* No Speech */
				do_speech = !IsChecked(item);
				SetSpeech();
				break;
			default:
				DisplayBeep(NULL);
				break;
			}
		}
#define EXTENDED_SELECT_WORKS
#ifdef EXTENDED_SELECT_WORKS
		code = item->NextSelect; 
		/* This next line is a kludge.  Testing has revealed that
		 * the NextSelect field is returning 0000x.  Why?
		 */
		if (!code) break;
#else
		break;
#endif
	}
	return 0;
}


/* Write a message to the console window.
 * Called with:
 *		msg:	message string
 */
WriteConsole(msg)
	char *msg;
{
	Write(console, msg, (long) strlen(msg));
}
***EOF Main.c***
-- 
< Mark R. Rinfret,        mrr@softie, ..rayssd!unisec!softie!mrr             >
< SofTech, Inc.           Home: 401-846-7639                                 >
< 1 Silva Lane,           Work: 401-849-4174                                 >
< Middletown, RI 02840    "The name has changed but I'm still guilty."       >