/* MRBackup: Backup Routines * Filename: Backup.c * Author: Mark R. Rinfret * Date: 09/04/87 * * History: (most recent change first) * * 09/04/87 -MRR- This package was (finally) extracted from Main.c. */ #define MAIN #include "MRBackup.h" /* Add a file/directory name to the list. * Called with: * name: filename string * FIB: pointer to FileInfoBlock for this file or NULL. * If NULL, file is starting directory. * list: address of list header * Returns: * 0 => success, failure status otherwise * Notes: * The filename string MUST NOT contain the volume component, * since it is used to build both source and destination * pathnames. Also note that this routine inserts the name * into the list in sorted order (case sensitive). Though this * changes the "natural order" of the files, it makes them * easier to locate on the backup disks. */ int AddFile(name, FIB, list) char *name; struct FileInfoBlock *FIB; T_FILE_LIST *list; { USHORT blks; T_FILE *fnode, *tnode; /* See if the exclude list exists, and if it does, see if * the name can be matched by one of the patterns. */ if (excludelist) { if (CheckExclude(name)) { sprintf(conmsg,"Excluding %s\n", name); WriteConsole(conmsg); return 0; } } if (!(fnode = (T_FILE *) calloc(1,sizeof(T_FILE)))) { nomem: TypeAndSpeak("AddFile: Out of memory!\n"); return ERROR_NO_FREE_STORE; } if (!(fnode->filename = calloc(1, strlen(name)+1))) goto nomem; strcpy(fnode->filename, name); /* copy the filename */ if (!FIB) { blks = 1; fnode->is_dir = true; } else if (fnode->is_dir = (FIB->fib_DirEntryType >= 0) ) blks = 1; /* assume 1 block for directory */ else { blks = ((FIB->fib_Size/488)+2); /* compute file size in blocks */ blks += (blks/70); } fnode->blocks = blks; if (!list->first_file) { /* file list is empty? */ list->first_file = fnode; /* this is the new head */ } else { /* Find the insertion point for this file. */ for (tnode = list->first_file; tnode; tnode = tnode->next) { if (strcmp(fnode->filename, tnode->filename) <= 0) { fnode->next = tnode; /* insert it here */ if (tnode->previous) tnode->previous->next = fnode; fnode->previous = tnode->previous; tnode->previous = fnode; if (list->first_file == tnode) list->first_file = fnode; return 0; } } /* append to the end of the list */ fnode->previous = list->last_file; list->last_file->next = fnode; } list->last_file = fnode; return 0; } /* Main backup routine. */ Backup() { int compare_flag, status = 0; Speak("O K, I'm ready. Lets back up your disk!"); DateStamp(now); main_list.first_file = main_list.last_file = current_dir = NULL; if (do_listing) if ( OpenList(listpath) ) return; /* Get the file comparison date. Only files created on or after * this date are backed up. */ do { *since = *now; since->ds_Days -= 1; /* default interval is 1 day */ since->ds_Minute = 0; since->ds_Tick = 0; Speak("Enter the date since your last backup."); DateRequest(mywindow, "Backup files since:",since, since); if ( (compare_flag = CompareDS(since, now) ) >= 0) DisplayBeep(NULL); } while (compare_flag >= 0); BreakPath(homepath, srcvol, srcpath); BreakPath(backpath, destdrive, destpath); #ifdef DEBUG sprintf(debugmsg, "destdrive = %s, destpath = %s\n",destdrive,destpath); DebugWrite(debugmsg); sprintf(debugmsg, "srcvol = %s, srcpath = %s\n", srcvol,srcpath); DebugWrite(debugmsg); #endif if (*destpath) { TypeAndSpeak( "On a backup, the backup path must only be a device name"); status = ERR_ABORT; goto done; } strcat(destdrive,":"); if (*excludepath) if (GetExcludes()) goto done; level = 0; back = 0; size = 0; if (*srcpath) { /* starting path is a directory */ status = AddFile(srcpath, NULL, &main_list); } else /* starting path is a device */ status = CollectFiles(srcpath,&main_list); if (!status) { while (main_list.first_file) { /* while something in the list */ if (status = BackupFiles(&main_list)) break; if (status = BackupDirs(&main_list)) break; } } done: if (status == 0) { TypeAndSpeak("That was a piece of cake.\n"); } else { sprintf(conmsg,"Oops! Backup terminated with error %d.\n",status); TypeAndSpeak(conmsg); } } /* Process the next directory in the file list. * This routine is recursive. * Called with: * list: file list to be processed * Returns: * status code (0 => success) */ int BackupDirs(list) T_FILE_LIST *list; { T_FILE *dirnode; T_FILE *saved_current_dir; int status = 0; T_FILE_LIST sublist; /* subdirectory list header */ sublist.first_file = sublist.last_file = NULL; saved_current_dir = current_dir; /* remember current context */ /* There are a couple of things to note here. The first is that once * we have reached here, there should be NO simple file nodes in "list". * That currently is not handled as an error, but probably should be. * Second, since this scan modifies the list, a removal of a directory * node starts the scan at the beginning of the list since we shouldn't * reference the links in the node we're removing. Since we should be * removing the first node in the list anyway, who cares? */ for (dirnode = list->first_file; dirnode; ) { if (dirnode->is_dir) { /* found one */ current_dir = dirnode; /* set current directory */ RemFile(dirnode, list); if (status = NewDir(current_dir->filename)) break; if (status = CollectFiles(current_dir->filename,&sublist)) break; if (status = BackupFiles(&sublist)) break; if (status = BackupDirs(&sublist)) break; dirnode = list->first_file; } else /* should never get here !!! */ dirnode = dirnode->next; } current_dir = saved_current_dir; return status; } /* Backup all simple files in the current list. * Called with: * list: file list header * Returns: * status code (0 => success) */ int BackupFiles(list) T_FILE_LIST *list; { T_FILE *primary, *secondary; int status = 0; /* The following loop continually scans the file list (from the front), * looking for more file entries to process. If the primary choice * fails, an attempt is made to find a file which will fit in the * space remaining. If that attempt fails, then a new disk is formatted. */ while (primary = FindFile(list,false)) {/* find next file to process */ if (primary->blocks >= size) { /* file doesn't fit */ if (!(secondary = FindFile(list,true))) { /* At this point, we know that there's at least one * file to back up, but none that fit. Start a new * disk. */ if (status = NewDisk(true)) return status; continue; /* try that file again */ } primary = secondary; /* use second choice */ } if (status = DoFile(primary)) return status; RemFile(primary,list); /* delete the node */ } if (current_dir) { /* forget current directory */ FreeFile(current_dir); current_dir = NULL; } return status; } /* Check the file name about to be added against the exclude patterns. * Called with: * name: pathname to be checked * Returns: * 0 => no match * 1 => name was matched, ignore it. */ int CheckExclude(name) char *name; { int match = 0; T_PATTERN *p; for (p = excludelist; p; p = p->next_pattern) { if (match = wildcmp(p->pattern, name)) break; } return match; } /* Check the current number of disk blocks (size) available. If * less than 1, it's time to format a new disk. * Called with: * create_dir: true => OK to create continuation directory * Returns: * 0 => success * 1 => failure */ int CheckSize(create_dir) int create_dir; { if (size > 0) return 0; return NewDisk(create_dir); } /* Collect file names from a starting path. * Called with: * name: starting pathname * Note that name may be a null string when copying * the entire home device. * list: pointer to file list header * Returns: * status code (0 => success) * Notes: * CollectFiles attempts to collect all file and directory names * for a given level, starting with "name". If a simple filename * is given as a starting path, only that name will be collected. * If a directory name is given, then all pathnames contained * within that directory (only) will be collected. For each * directory name collected, CollectFiles will be called again to * collect files for that particular directory. This iterative * approach (vs. recursive) saves memory and allows us to maintain * order at each directory level. */ int CollectFiles(name, list) char *name; T_FILE_LIST *list; { int status = 0; struct FileInfoBlock *FIB = NULL; T_FILE *fnode; /* file descriptor node */ struct Lock *lock = NULL; char path[PATH_MAX+1]; USHORT top_level; #ifdef DEBUG sprintf(debugmsg,"Collecting files from %s\n",name); DebugWrite(debugmsg); #endif top_level = (*name == '\0'); /* empty name implies top level */ if (!(FIB = AllocMem((long)sizeof(struct FileInfoBlock), MEMF_CHIP|MEMF_CLEAR))) { TypeAndSpeak("CollectFiles: Can not allocate memory for FIB\n"); return ERROR_NO_FREE_STORE; } strcpy(path,srcvol); /* rebuild current home path */ strcat(path,":"); strcat(path, name); if (!(lock=Lock(path,SHARED_LOCK))) { status = IoErr(); sprintf(conmsg,"CollectFiles can not lock %s; error %d\n", path, status); TypeAndSpeak(conmsg); goto out2; } if ((Examine(lock,FIB))==0){ status = IoErr(); sprintf(conmsg,"CollectFiles can not examine %s; error: %d\n", name, status); TypeAndSpeak(conmsg); goto out2; } if (FIB->fib_DirEntryType > 0){ /* "name" is a directory */ while(!status && ExNext(lock,FIB)) { if (FIB->fib_DirEntryType < 0) { if (CompareDS(&FIB->fib_Date, since) < 0) { #ifdef DEBUG sprintf(debugmsg,"Skipping %s\n", &FIB->fib_FileName[0]); DebugWrite(debugmsg); #endif continue; } } if (top_level) *path = '\0'; else { strcpy(path,name); strcat(path,"/"); } strcat(path,&FIB->fib_FileName[0]); status = AddFile(path, FIB, list); } /* !!! Need check here for ERROR_NO_MORE_ENTRIES */ } else { if ( CompareDS(&FIB->fib_Date, since ) >= 0 ) status = AddFile(name, FIB, list); } out2: UnLock(lock); out1: FreeMem(FIB, (long)sizeof(struct FileInfoBlock) ); /* Don't give up if somebody else is using the file - just * ignore the error. The user can cancel us if there's a * problem. */ if (status == ERROR_OBJECT_IN_USE) status = 0; return status; } /* Process one file. * Called with: * fnode: node describing file * Returns: * 0 => success * 1 => failure */ int DoFile(fnode) T_FILE *fnode; { #define MAX_RETRY 3 int status = ERR_NONE; char destname[PATH_MAX+1]; int oldsize; USHORT retries = 0; char srcname[PATH_MAX+1]; oldsize = size; size -= fnode->blocks; /* deduct blocks for file */ if (CheckSize(true)) /* check disk space */ return ERR_ABORT; if (size > oldsize) /* we just formatted */ oldsize = size; /*#define NOCOPY*/ /* for fast debugging */ do { if (status = CheckStop()) /* does user want out? */ return status; sprintf(srcname,"%s:%s",srcvol,fnode->filename); sprintf(conmsg,"Blocks left: %5d ",oldsize); WriteConsole(conmsg); if ( !do_compress || IsCompressed(srcname) || fnode->blocks < 4){ sprintf(destname,"%s%s",destvol,fnode->filename); sprintf(conmsg,"Copying %s\n",srcname); WriteConsole(conmsg); #ifndef NOCOPY status = CopyFile(srcname,destname); #endif } else { sprintf(destname,"%s%s.Z",destvol,fnode->filename); sprintf(conmsg,"Compressing %s\n",srcname); WriteConsole(conmsg); #ifndef NOCOPY if (!(status = compress(srcname,destname))) status = CopyFileDate(srcname,destname); #endif } if (status) { /* status = GetErrOpt("I/O error during copy or compress", ERR_ABORT | ERR_IGNORE | ERR_RETRY_FILE | ERR_RESTART_VOLUME); */ sprintf(conmsg,"!!!I/O error %d on file %s",status,destname); ListLine(conmsg); unlink(destname); if (++retries <= MAX_RETRY) { strcat(conmsg," - retrying this file.\n"); if (retries == MAX_RETRY) size = 0; /* try new output disk */ status = ERR_RETRY_FILE; } else { strcat(conmsg," - aborting this file.\n"); /* Note that for now, we will proceed by brute force. A later revision * of this program will provide context-saving so we can restart the * series of files on this floppy. */ status = ERR_IGNORE; } WriteConsole(conmsg); } else ListLine(destname); } while (status == ERR_RETRY_FILE && (retries < MAX_RETRY)); if (status == ERR_IGNORE) status = ERR_NONE; #ifndef NOCOPY if (!status){ if ((size = DiskBlocks(destvol)) < 0) /* update blocks left */ ++status; } #endif return status; } /* Attempt to find a file node in the file list. If the check_size * parameter is true, only look at files which will fit in the space * remaining on the disk. * Called with: * list: pointer to file list to be searched * check_size: false => find first file in the list * true => test space available * Returns: * file node or NULL (not found) */ T_FILE *FindFile(list,check_size) T_FILE_LIST *list; int check_size; { T_FILE *fnode; for (fnode = list->first_file; fnode; fnode = fnode->next) { if (!fnode->is_dir) { /* don't consider directory nodes */ if (!check_size) break; /* take this one */ if (fnode->blocks < size) break; } } return fnode; } /* Free memory allocated to a file node. * Called with: * node: file node */ FreeFile(node) T_FILE *node; { free(node->filename); free(node); } /* An exclude file pathname has been specified. Get the patterns it * contains. * Returns: * status: 0 => success, failure otherwise */ int GetExcludes() { USHORT i, length, nonwild; T_PATTERN *p; int status = 0; char str[PATH_MAX+1]; FILE *xcld; if (! exclude_has_changed) return 0; if (!(xcld = fopen(excludepath, "r"))) { sprintf(conmsg, "I couldn't open the exclude pattern file, error %d.", errno); TypeAndSpeak(conmsg); return errno; } /* Release any previous exclude list. */ for (p = excludelist; p; p = p->next_pattern) { free(p->pattern); free(p); } excludelist = lastexclude = NULL; while (fgets(str, PATH_MAX, xcld)) { if (length = strlen(str)) { --length; str[length] = '\0'; } if (length && *str != '#') { /* ignore blank lines and comments */ nonwild = 0; for (i = 0; i < length; ++i) { if (str[i] != '*' && str[i] != '?' && str[i] != '/') ++nonwild; } if (! nonwild ) { sprintf(conmsg, "Very funny! %s will exclude everything! Ha ha ha!\n", str); TypeAndSpeak(conmsg); status = 1; goto done; } p = (T_PATTERN *) calloc(sizeof(T_PATTERN), 1); p->pattern = calloc(length+1, 1); strcpy(p->pattern, str); if ( ! excludelist ) excludelist = p; else lastexclude->next_pattern = p; lastexclude = p; } } done: fclose(xcld); if (! status ) exclude_has_changed = false; return status; } /* Format a new diskette. * Called with: * create_dir: true => create continuation directory, if necessary * Returns: * false => success * true => failure */ int NewDisk(create_dir) int create_dir; { char datestr[20]; int status = 0; if (back) /* not first disk? */ Delay(TICKS_PER_SECOND * 5L); /* let disk buffers flush */ ++back; /* Increment the volume ID */ Speak("Attention! I need a disk for formatting."); do { if (!RequestDisk(mywindow, destdrive)) return ERR_ABORT; /* Don't put the colon in the volume name - * FormatDisk will happily use it as part of * the name rather than as a delimiter. */ DS2Str(datestr, "%02m-%02d-%02y", now); sprintf(destvol,"Backup %s.%d", datestr, back); Inhibit(destdrive,1); if (status = FormatDisk(destdrive,destvol)) { TypeAndSpeak("I failed to format the disk. Sorry.\n"); } } while (status); strcat(destvol, ":"); /* add colon */ if ((size = DiskBlocks(destvol)) < 0) status = -size; else if (create_dir && (current_dir != NULL) ) status = NewDir(current_dir->filename); return status; } /* Remove a file node from the list. * Called with: * node: file node pointer * list: file list header * Returns: * nothing */ RemFile(node,list) T_FILE *node; T_FILE_LIST *list; { if (node->previous) node->previous->next = node->next; if (node->next) node->next->previous = node->previous; if (node == list->first_file) list->first_file = node->next; if (node == list->last_file) list->last_file = node->previous; if (!node->is_dir) FreeFile(node); }