/* MRBackup: Big File Handler * Date: 12/26/87 * Description: * * This package contains routines which support the backup and * restoration of files which are larger than the capacity of the output * media. * * Design philosophy: * * When a file is encountered whose total size exceeds the capacity * of the backup media, MRBackup will attempt to split the file into * pieces, spanning several diskettes, if necessary. Prior to doing this, * a special information file is written (name == BIGINFONAME). This file * contains the following information, written as newline-terminated ASCII * strings: * * file name: 1..255 characters * file size: 1..NNNNNNNN * sequence: 1..NNN * more: Y or N (Y => another piece follows this one) * * When files are restored, their names are checked agains the * "big file" name (if any). If the big file is encountered, RestoreBigFile * is called to restore it. RestoreBigFile will request each diskette, in * sequence, until the full file is restored or an error occurs. * * The user must have enabled the "Allow Big Files" and "Format * Destination" program options in order for multi-volume backups to occur. * * History: (most recent change first) * */ #include "MRBackup.h" /* * Copy a "big" file, preserving date and writing the big file information. * Depends upon file date routines in FileMisc.c */ #include #include #include #include #include #include ":src/lib/DiskMisc.h" #define BIGINFONAME "MRBackup.bigfile" #define MAXSTR 127 extern char fullBackPath[], fullHomePath[]; extern BOOL homeIsDevice; extern long Chk_Abort(); extern BOOL CopyFileDate(); extern BOOL GetFileDate(), SetFileDate(); extern long IoErr(); struct { char fileName[PATH_MAX+1]; ULONG size; /* file size in bytes */ USHORT seqNbr; /* sequence number (1..n) */ char more[4]; /* last piece of file? */ } bigFileInfo; /* Function: * BackupBigFile * * Description: * This routine splits a very large file across a sequence of diskettes. * On each of these disks, a special information file is written which * specifies the name of the file, its size and its sequence number. * * Called with: * fileName: file name (less volume) to be copied * * Returns: * status, where 0 => successful backup */ int BackupBigFile(fileName) char *fileName; { long count, startCount; BOOL done = false; char *errMsg = NULL; struct FileHandle *fin = NULL, *fout = NULL; char from[PATH_MAX+1], to[PATH_MAX+1]; struct InfoData *info; struct FileLock *lock = NULL; BOOL needNewDisk; long status = 0; /* First, check to see if this disk currently has a "big file" on it. * Since we can only record 1 of these per disk, we need to know this. */ status = GetBigFileInfo(destVol); if (status == 0) needNewDisk = false; else if (status == 1) needNewDisk = true; else if (status == -1) { errMsg = "Error while attempting to get big file information.\n"; status = ERR_ABORT; } setmem(&bigFileInfo, sizeof(bigFileInfo), 0); strcat(bigFileInfo.fileName, fileName); strcpy(from, srcVol); strcat(from, fileName); if (! (fin = Open(from, MODE_OLDFILE) ) ) { status = IoErr(); goto endit; } strcpy(bigFileInfo.fileName, fileName); startCount = 0; while (! (done | status) ) { if (needNewDisk) if (status = NewDisk(true)) break; /* Start new segment by incrementing sequence number. */ ++bigFileInfo.seqNbr; bigFileInfo.size = 0; /* Write dummy info file. This assures us that we will have a * block reserved for the big file information file. Since the * information kept is small, 1 block is all we will need. */ if (status = PutBigFileInfo(destVol)) continue; strcpy(to, destVol); strcat(to, fileName); if ( !(fout = Open(to, MODE_NEWFILE)) ) { status = IoErr(); sprintf(conmsg,"Failed to open %s for output; status = %ld\n", to, status); errMsg = conmsg; goto endit; } /* Position to the correct offset in the source file. */ if (Seek(fin, startCount, OFFSET_BEGINNING) == -1L) { status = IoErr(); errMsg = "BackupBigFile: Seek failed!\n"; goto endit; } /* Read blocks of data from the input file and write them to the * output file. One of the following events will terminate this * loop: * * 1. End of file on input (count < bufSize) * 2. Input error * 3. Disk full on output (status == ERROR_DISK_FULL) * 4. Other disk write error (non-recoverable). * */ while ( ! (status || done) ) { if (status = CheckStop()) break; count = Read( fin, buffer, bufSize ); if (count < bufSize) done = true; if ( count && (Write(fout, buffer, count) != count)) { status = IoErr(); if (status == ERROR_DISK_FULL) { status = 0; } break; } } if (status) break; Close(fout); fout = NULL; if (status = CopyFileDate(from, to)) { errMsg = "Failed to set file date!\n"; break; } if (! (lock = (struct FileLock *) Lock(to, ACCESS_READ))) { sprintf(conmsg,"Could not lock %s\n", to); errMsg = conmsg; break; } if (!Examine(lock, fibWork)) { status = IoErr(); sprintf(conmsg,"Could not examine %s\n", to); errMsg = conmsg; break; } UnLock(lock); lock = NULL; startCount += fibWork->fib_Size; bigFileInfo.size = fibWork->fib_Size; bigFileInfo.more[0] = (done ? 'N' : 'Y'); if (status = PutBigFileInfo(destVol)) break; needNewDisk = !done; /* Need another disk? */ } endit: if (lock) UnLock(lock); if (fin) Close(fin); if (fout) Close(fout); if (errMsg) TypeAndSpeak(errMsg); return (int) status; } ^L /* Function: * GetBigFileInfo * * Called with: * volume: name of volume to retrive info from * * Returns: * > 0 => sequence number of big file on this disk * 0 => no big file info on disk * < 0 => had trouble reading big file info * * Description: * See if the current diskette has "big file" information. */ BOOL GetBigFileInfo(volume) char *volume; { char bigFileInfoName[PATH_MAX+1]; char *errMsg = NULL; FILE *f; int status = 0; MakeBigInfoName(volume, bigFileInfoName); if (f = fopen(bigFileInfoName, "r")) { if (fgets(bigFileInfo.fileName, PATH_MAX+1, f) == NULL) { errMsg = "Couldn't get big file name!\n"; status = -errno; } else if (fscanf(f, "%d%ld\n", &bigFileInfo.seqNbr, &bigFileInfo.size) != 2) { errMsg = "Couldn't get big file size or sequence number!\n"; status = -errno; } else if (fgets(bigFileInfo.more, 3, f) == NULL) { errMsg = "Couldn't get big file continuation flag!\n"; status = -errno; } else { status = bigFileInfo.seqNbr; bigFileInfo.fileName[strlen(bigFileInfo.fileName)-1] = '\0'; } } if (f) fclose(f); if (errMsg) TypeAndSpeak(errMsg); return status; } ^L /* Function: * ThisIsBigFile * * Called with: * name: filename to test * * Returns: * true => filename is the name of a "big file" * false => this filename is not that of a "big file" * * Description: * The is compared against the current big file * information, if any. If matches the big file name, * true is returned, otherwise false is returned. This routine * provides a functional interface to the BigFiles package while * allowing us to hide low-level implementation details. */ BOOL ThisIsBigFile(name) char *name; { BOOL result = false; if (bigFileInfo.seqNbr && !strcmp(name, bigFileInfo.fileName)) result = true; return result; } /* Function: * MakeBigInfoName * * Called with: * volume: disk volume name where big file resides * name: result name (modified) * * Description: * This routine simply catenates the destination name with * the root name of the information file where data about the big * file is kept, returning it in . */ MakeBigInfoName(volume, name) char *volume, *name; { strcpy(name, volume); strcat(name, BIGINFONAME); } ^L /* Function: * PutBigFileInfo * * Called with: * volume: volume name of destination * * Returns: * status, where 0 => OK * * Description: * This routine writes a special information file to the destination * disk. This file describes * - the name of the file, * - the sequence number of the chunk that resides on the disk, * - the size of this chunk * - a flag (Y/N) indicating if more chunks exist */ int PutBigFileInfo(volume) char *volume; { FILE *f; char bigFileInfoName[PATH_MAX+1]; int status = 0; MakeBigInfoName(volume, bigFileInfoName); if (! (f = fopen(bigFileInfoName, "w")) ) { status = errno; sprintf(conmsg, "I couldn't create the big file information block: %s\n", bigFileInfoName); TypeAndSpeak(conmsg); } else { fprintf(f, "%s\n%d\n%ld\n%s\n", bigFileInfo.fileName, bigFileInfo.seqNbr, bigFileInfo.size, bigFileInfo.more ); fclose(f); } return status; } ^L /* Function: * RestoreBigFile * * Returns: * status, where 0 => OK * * Description: * RestoreBigFile is called by Restore() AFTER all other files on the * current diskette have been restored. This function will reassemble * a "big" file (size > backup media size) onto the home device, * prompting the user as necessary for subsequent disks. */ int RestoreBigFile() { int compare; /* date comparison result */ long count; char savedName[PATH_MAX+1]; BOOL done = false, eof; long fileByteCount; struct FileHandle *fin, *fout; USHORT seqNbr; int status = 0; strcpy(savedName, bigFileInfo.fileName); if ( (seqNbr = bigFileInfo.seqNbr) != 1) { TypeAndSpeak( "Oops! RestoreBigFile was called when it shouldn't have been.\n"); return ERR_ABORT; } strcpy(fullHomePath, homePath); if (!homeIsDevice) strcat(fullHomePath, "/"); strcat(fullHomePath, savedName); strcpy(fullBackPath, srcVol); strcat(fullBackPath, savedName); compare = CompareFileDates(fullHomePath, fullBackPath); if (compare >= 0) { sprintf(conmsg,"Skipping %s since home file is current.\n", savedName); WriteConsole(conmsg); return 0; } if (! (fout = Open(fullHomePath, MODE_NEWFILE))) { status = IoErr(); sprintf(conmsg,"Can't open %s for output!\n", fullHomePath); TypeAndSpeak(conmsg); return status; } done = false; while (! (done || status) ) { sprintf(conmsg, " Restoring chunk %d of %s\n ", bigFileInfo.seqNbr, savedName); WriteConsole(conmsg); if (! (fin = Open(fullBackPath, MODE_OLDFILE))) { sprintf(conmsg, "Can't open %s for input!\n", fullBackPath); TypeAndSpeak(conmsg); break; } eof = false; fileByteCount = 0; while (! (eof || status) ) { count = Read(fin, buffer, bufSize); if (count == -1L) { status = IoErr(); WriteConsole("--ERROR--\n"); } else { fileByteCount += count; /* The next bit of hackery is necessary since AmigaDOS apparently * doesn't do a good job of handling file size information on files * which experience DISK_FULL when being written. An Examine gives * the size as of the last successful Write, while reading to EOF * yields the exact number of bytes that were successfully written. * Thus, we must use the file size that we recorded in the big file * information file. If this situation gets corrected in KickStart * 1.3 or later, this code should still work correctly. */ if (fileByteCount > bigFileInfo.size) { /* Ignore "excess" data. */ count -= (fileByteCount - bigFileInfo.size); } WriteConsole("."); if (count < bufSize) { WriteConsole("--EOF--\n"); sprintf(conmsg," %ld bytes in chunk %d\n", fileByteCount, seqNbr); WriteConsole(conmsg); eof = true; Close(fin); fin = NULL; } if (count && ( Write(fout, buffer, count) != count) ) status = IoErr(); } } if (status) break; if (toupper(bigFileInfo.more[0]) == 'Y') { ++seqNbr; /* Request the next floppy disk. */ while (bigFileInfo.seqNbr != seqNbr) { sprintf(conmsg, "I need the disk for chunk %d of %s\n", seqNbr, savedName); TypeAndSpeak(conmsg); if (!RequestDisk( mainWindow, srcVol, "Insert next disk in ")) { status = ERR_ABORT; break; } else { if ((status = GetBigFileInfo(srcVol)) == seqNbr && ThisIsBigFile(savedName)) break; } } } else done = true; } if (fout) Close(fout); if (fin) Close(fin); if (!status) CopyFileDate(fullBackPath, fullHomePath); return status; }