/* 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)); }