/************************************************************************ * * * Copyright (c) 1984, Fred Fish * * All Rights Reserved * * * * This software and/or documentation is protected by U.S. * * Copyright Law (Title 17 United States Code). Unauthorized * * reproduction and/or sales may result in imprisonment of up * * to 1 year and fines of up to $10,000 (17 USC 506). * * Copyright infringers may also be subject to civil liability. * * * ************************************************************************ */ /* * FILE * * blocks.c routines to handle block buffers for bru * * SCCS * * @(#)blocks.c 9.11 5/11/88 * * DESCRIPTION * * Contains routines to manipulate the block buffers for * the bru utility. Provides facilities to read, write, * and seek on the archive file. Buffers blocks internally * and only does I/O in increments of the blocking factor * chosen. * * The following routines are provided: * * ar_open: open archive stream * ar_read: read next logical block * ar_write: write current logical block * ar_seek: seek to specified logical block * ar_tell: return current logical block number * ar_close: close archive stream * ar_next: locate next logical block * * NOTES * * Originally, the archive device was assumed to be seekable unless * the user explicitly requested no seeks via the -p option, * a brutab entry indicated that the device was NOT seekable, or * the archive device was a pipe. This turned out to cause lots * of confusion when devices are not specified in the brutab file * and were in fact not seekable (such as a raw mag tape unit * used over a network). So... the safest thing is to make the * default to be NOT seekable and only use seeks when device is * known to be seekable. Better slow than broken! * * BUGS * * Currently does not support mixed reading/writing * of archive file. * */ #include "autoconfig.h" #include #if unix || xenix # include # include # include /* for NBPC fall back */ # include # include # if BSD4_2 # include /* Plain vanilla 4.2 file is here */ # include /* 4.2 is woefully incomplete */ # else # include # include # endif #else # include "sys.h" /* Header file that fakes it for non-unix */ #endif #include "typedefs.h" #include "dbug.h" #include "manifest.h" #include "config.h" #include "errors.h" #include "blocks.h" #include "macros.h" #include "finfo.h" #include "devices.h" #include "flags.h" #include "bruinfo.h" #include "exeinfo.h" BOOLEAN need_swap (); /* Test for swap needed */ VOID ar_sync (); VOID do_swap (); /* Swap bytes/shorts */ static BOOLEAN do_open (); /* Open an archive file */ static VOID eject_media (); /* Test for and eject media if supported */ static VOID qfwrite (); /* Query for continuation on first write */ static VOID new_bufsize (); /* Force new buffer size */ static VOID bad_vol (); /* Found bad volume */ static VOID check_vol (); /* Check for volume not part of same set */ static VOID infer_end (); /* Process inferred end of volume */ static BOOLEAN recover (); /* Attempt to recover from I/O errors */ static VOID patch_buffer (); /* Add volume numbers and checksums */ static VOID clean_buffer (); /* Clean out any leftovers from previous */ static VOID harderror (); /* Handle non-recoverable read/write error */ extern VOID addz (); /* Add a ".Z" extension to filename */ extern VOID bru_error (); /* Report an error to user */ extern VOID fork_shell (); /* Fork a shell */ extern int execute (); extern VOID readsizes (); /* Read media/buffer sizes from blk header */ extern VOID read_info (); extern VOID sanity (); extern char response (); /* Prompt and get response */ extern BOOLEAN seekable (); extern BOOLEAN forcebuffer (); /* Nonzero if force small buffer */ extern VOID sig_push (); /* Push current signal state */ extern VOID sig_pop (); /* Pop previous signal state */ extern VOID sig_done (); /* Set signals to xfer to done */ static VOID open_std (); static VOID allocate (); VOID switch_media (); /* Switch media */ VOID phys_seek (); /* Do physical seek */ static VOID r_phys_seek (); /* Phys seek during error recovery */ static VOID rgrp (); /* Read new block group */ static VOID rfile (); /* Read new block group from file */ static VOID rpipe (); /* Read new block group from pipe */ static VOID wgrp (); /* Write block group out */ static VOID wfile (); /* Write block group to file */ static VOID wpipe (); /* Write block group to pipe */ static union blk *lba_seek (); /* Seek to specific logical block */ static void chkandfixaudio(int mt); /* * In cases of extreme bugs, we can define DUMPBLK, which enables * dumping of the contents of each archive block using the DBUG * macro mechanism. We don't normally want this, as it results * in overwhelming output... */ #ifdef DUMPBLK extern VOID dump_blk (); # define DUMP_BLK(a,b) dump_blk(a,b) /* call support routine */ #else # define DUMP_BLK(a,b) /* define the calls away */ #endif /* * External system interface routines. */ extern int s_dup (); /* Duplicate open file descriptor */ extern char *s_strcpy (); /* Copy strings */ extern char *s_ctime (); /* Convert time to string */ extern int s_open (); /* Invoke library file open function */ extern int s_read (); /* Invoke library file read function */ extern VOID s_sleep (); /* Sleep for given number of seconds */ extern int s_close (); /* Invoke library file close function */ extern int s_write (); /* Write data to a file */ extern VOID s_free (); /* Free allocated memory */ extern S32BIT s_lseek (); /* Seek to file location */ extern int s_eject (); /* Low level eject media support */ extern BOOLEAN s_format (); /* Format the media if possible */ /* * External bru functions. */ extern VOID file_chown (); /* Change file owner and group */ extern VOID swapbytes (); /* Swap bytes in a block */ extern VOID swapshorts (); /* Swap shorts in a block */ extern VOID done (); /* Clean up and exit with status */ extern VOID copyname (); /* Copy names around */ extern CHKSUM chksum (); /* Compute block checksum */ extern VOID tohex (); extern BOOLEAN dir_access (); /* Test dir for accessibility */ extern BOOLEAN file_access (); /* Test file accessibility */ extern VOID tty_newmedia (); /* Prompt for new media and device */ extern BOOLEAN raw_tape (); /* Test for raw magnetic tape drive */ extern BOOLEAN end_of_device (); /* Test for end of device */ extern BOOLEAN unformatted (); /* Test for unformatted media */ extern BOOLEAN write_protect(); /* Test for write protected media */ extern BOOLEAN chksum_ok (); /* Test for good block checksum */ extern BOOLEAN magic_ok (); /* Test for a recognized magic number */ extern VOID *get_memory (); /* Memory allocator */ extern struct device *get_ardp (); /* Initialize the ardp */ extern struct bru_info info; /* Information about bru invocation */ extern struct cmd_flags flags; /* Flags given on command line */ extern struct exe_info einfo; /* Execution information */ extern ULONG bufsize; /* Archive read/write buffer size */ extern ULONG msize; /* Size of archive media */ extern struct finfo afile; /* The archive file info struct */ extern time_t artime; /* Time of archive being read */ extern int release; /* Major release level */ extern int level; /* Minor release level */ extern int variant; /* Variant of bru */ extern struct device *ardp; /* Pointer to archive device info */ extern char mode; /* Current execution mode */ extern int errno; /* System error number */ extern FILE *logfp; /* Verbosity stream */ static BOOLEAN reading; /* Reading from archive */ static BOOLEAN bswap = FALSE; /* Swap bytes in buffer */ static BOOLEAN sswap = FALSE; /* Swap shorts in buffer */ union blk *blocks = NULL; /* Pointer to block buffer */ union blk *blocks_orig = NULL; static BOOLEAN seekflag = FALSE; /* Use seeks instead of reads */ int lbi = 0; /* Index into blocks[] */ static LBA gsba = 0; /* Group start block address */ static LBA vsba = 0; /* Volume start block address */ static LBA pba = 0; /* Physical block addr in media */ static LBA grp = 0; /* Current block group in buffer */ static int lvolume = 0; /* Current volume number (if not double buffering) */ int *volume = &lvolume; /* set to a shared memory area in dblib.c if using double buffering */ int lastvolume; /* highest volume number reached; for -d or -i after -c, etc. */ static int vol_estimate = 0; /* Estimation volume number */ static BOOLEAN data = FALSE; /* Flag for buffer has data */ static LBA lba_estimate = 0; /* Estimation logical block address */ static BOOLEAN dirty = FALSE; /* Buffer has been changed */ static BOOLEAN first = TRUE; /* First read/write of media */ static BOOLEAN checkswap = TRUE; /* Only swap check once per media */ static BOOLEAN pipe_io = FALSE; /* Doing I/O to/from a pipe */ static int tries; /* Current number of retries */ static int firsterrno; /* Original errno from original err */ static int recursions = 0; /* Error recovery recursion count */ #if HAVE_SHM extern int dbl_setup (); /* set up for buffering */ extern long dbl_read (); /* read some data from the buffers */ extern long dbl_write (); /* write data to the buffers */ extern int dbl_flush (); /* flush buffers */ extern VOID dbl_bufp (); /* set buffer sizes */ extern VOID dbl_errp (); /* set error routine pointers */ extern VOID dbl_iop (); /* set I/O routine pointers */ extern int dbl_rpcdown (); /* call procedure in child */ #define DBL_READING 0 /* reading from device */ #define DBL_WRITING 1 /* writing to device */ #define DBL_DOBUF 1 /* perform double buffering */ #define DBL_NOBUF 0 /* be transparent */ #define DBLF_FDES 0x1 /* update file descriptor */ static int trans_dbl; /* if in transparent mode */ static int wdblinit; /* set if write init done */ #endif /* HAVE_SHM */ #if HAVE_SHM /* * The following two routines are called by the double buffering module * when an input or output error occurs. */ static int inerr_recover (nb, err, des) int nb; int err; int *des; { int status = 1; DBUG_ENTER ("inerr_recover"); if (recover (nb, err, 1)) { *des = afile.fildes; status = 0; } DBUG_RETURN (status); } static int outerr_recover (nb, err, des) int nb; int err; int *des; { int status = 1; DBUG_ENTER ("outerr_recover"); if (recover (nb, err, 0)) { *des = afile.fildes; status = 0; } DBUG_RETURN (status); } static void dupdate (nvolume, des) long nvolume; int *des; { switch_media(nvolume); if (des != 0) { *des = afile.fildes; } } static setsize (nsize, dummy) ULONG nsize; int *dummy; { msize = nsize; } #endif /* HAVE_SHM */ /* * FUNCTION * * ar_open open the archive stream * * SYNOPSIS * * VOID ar_open (); * * DESCRIPTION * * Note that when the archive is opened for the first time, * space is automatically allocated for the block buffer. * */ VOID ar_open () { DBUG_ENTER ("ar_open"); pba = 0; gsba = 0; vsba = 0; grp = 0; *volume = 0; #if HAVE_SHM wdblinit = 0; #endif lbi = -1; /* CAUTION ! */ dirty = FALSE; data = FALSE; if (flags.Sflag) { switch_media (0); } else { if (!do_open ()) { switch_media (0); } } if (mode == 't' && !flags.bflag && seekable (afile.fname, BLKSIZE)) { DBUG_PRINT ("bufsize", ("bufsize auto-adjusted for min reads")); bufsize = BLKSIZE; flags.bflag = TRUE; } allocate (); if (reading) { read_info (); #if HAVE_SHM if (msize != 0) { if (trans_dbl == DBL_DOBUF) { if (dbl_rpcdown (setsize, msize)) { bru_error (ERR_DBLSUP); } } } #endif } else { qfwrite (); } sanity (); DBUG_VOID_RETURN; } /* * INTERNAL FUNCTION * * open_std open stdin/stdout as archive * * SYNOPSIS * * static VOID open_std () * * DESCRIPTION * * Opens either the standard input or standard output as * the archive, depending upon whether an archive is being * read or written respectively. * */ static VOID open_std () { DBUG_ENTER ("open_std"); pipe_io = TRUE; seekflag = FALSE; if (mode == 'c') { afile.fildes = s_dup (1); } else { afile.fildes = s_dup (0); } DBUG_VOID_RETURN; } /* * FUNCTION * * ar_seek seek to a logical block * * SYNOPSIS * * union blk *ar_seek (offset, whence) * LBA offset; * int whence; * * DESCRIPTION * * Repositions archive read/write pointer to the first byte * of the logical block specified by the combination of the * current read/write pointer (implicitly) and the offset and * whence values (explicitly). * * Sets the file pointer as follows: * * If whence is 0, the pointer is set to offset blocks. * * If whence is 1, the pointer is set to its current * block number plus offset blocks. * * Returns a pointer to the first byte of the logical block. * * Note that this simply provides a place to either read * data from (after a call to ar_read) or to write data * to (followed by a call to ar_write). There is no * meaningful I/O implied, although physical I/O may * take place if physical seeks are not allowed. * * */ union blk *ar_seek (offset, whence) LBA offset; int whence; { register union blk *blkp; register LBA nlba; DBUG_ENTER ("ar_seek"); if (whence == 0) { nlba = offset; } else { nlba = offset + gsba + lbi; } blkp = lba_seek (nlba); DBUG_RETURN (blkp); } static union blk *lba_seek (nlba) LBA nlba; { register LBA ngrp; register int nvolume; register LBA npba; register int bufblocks; register LBA mblocks; DBUG_ENTER("lba_seek"); DBUG_PRINT ("seek", ("logical seek to archive block %ld", nlba)); if (afile.fildes >= 0) { bufblocks = BLOCKS (bufsize); ngrp = nlba / bufblocks; DBUG_PRINT ("ngrp", ("new block in group %ld", ngrp)); if (ngrp != grp) { ar_sync (); data = FALSE; grp = ngrp; DBUG_PRINT ("grp", ("block group %ld", grp)); gsba = grp * bufblocks; DBUG_PRINT ("gsba", ("group start block addr %ld", gsba)); npba = gsba - vsba; DBUG_PRINT ("npba", ("new phys block addr %ld", npba)); if (msize != 0) { mblocks = BLOCKS (msize); nvolume = (int) (nlba / mblocks); if (nvolume != *volume) { #if HAVE_SHM if (trans_dbl == DBL_DOBUF) { if (!reading) { SIGTYPE prevINT; SIGTYPE prevQUIT; /* * In case user pops the interrupt while the * child is doing the next reel, we need to * recover correctly as well. */ sig_push (&prevINT, &prevQUIT); signal(SIGINT, SIG_DFL); if (dbl_rpcdown (dupdate, nvolume)) { bru_error (ERR_DBLSUP); } sig_pop (&prevINT, &prevQUIT); /* * After remote call completes, clean up by * doing some of the switch_media stuff that * needs to be done locally. */ *volume = nvolume; pba = 0; first = TRUE; checkswap = TRUE; sswap = FALSE; bswap = FALSE; } } else { switch_media (nvolume); } #else switch_media (nvolume); #endif vsba = *volume * mblocks; DBUG_PRINT ("vsba", ("new volume start block addr %ld", vsba)); npba = gsba - vsba; DBUG_PRINT ("npba", ("new phys block addr %ld", npba)); } } phys_seek (npba); } } lbi = (int) (nlba - gsba); if (lbi < 0 || lbi >= bufblocks) { bru_error (ERR_BUG, "lba_seek"); } DBUG_PRINT ("lba_seek", ("new logical block index %d", lbi)); DBUG_PRINT ("grp", ("grp %ld gsba %ld pba %ld", grp, gsba, pba)); DBUG_RETURN (&blocks[lbi]); } /* * Note that the seek is done even if the npba equals * the pba, since the pba may not be correct if error * recovery is being attempted. */ VOID phys_seek (npba) LBA npba; { auto S32BIT offset; int nblks = npba - pba; DBUG_ENTER ("phys_seek"); DBUG_PRINT ("pba", ("seek from %ld to %ld", pba, npba)); if (!seekflag) { /* use nblks instead of pba < npba to handle case of tape change in rfile() */ while (nblks > 0) { rgrp (); nblks -= BLOCKS(bufsize); } data = FALSE; if (nblks != 0) { DBUG_PRINT ("badpba", ("seek to %ld got %ld instead", pba, npba)); bru_error (ERR_ARPASS); done (); } } else { offset = npba * BLKSIZE; DBUG_PRINT ("ar_io", ("physical seek to block %ld", npba)); if (s_lseek (afile.fildes, offset, 0) != offset) { bru_error (ERR_ARSEEK); done (); } pba = npba; } DBUG_VOID_RETURN; } /* * FUNCTION * * ar_read read next logical block * * SYNOPSIS * * VOID ar_read (fip) * struct finfo *fip; * * DESCRIPTION * * Performs logical read of next logical block. * */ VOID ar_read (fip) struct finfo *fip; { register LBA bfound; DBUG_ENTER ("ar_read"); DBUG_PRINT ("flba", ("read file block %ld", fip -> flba)); if (!data) { rgrp (); } DUMP_BLK (&blocks[lbi], (LBA) (gsba + lbi)); if (fip == NULL) { bru_error (ERR_BUG, "ar_read"); } else { if (!chksum_ok (&blocks[lbi])) { fip -> fi_flags |= FI_CHKSUM; einfo.chkerr++; fip -> chkerrs++; fip -> flba++; } else { DBUG_PRINT ("alba", ("archive blk %ld", FROMHEX (blocks[lbi].HD.h_alba))); bfound = FROMHEX (blocks[lbi].HD.h_flba); DBUG_PRINT ("lba", ("file blk %ld", bfound)); if (fip -> flba != bfound) { DBUG_PRINT ("bseq", ("wrong file block (%ld) found", bfound)); fip -> flba = bfound + 1; fip -> fi_flags |= FI_BSEQ; } else { fip -> flba++; } } } DBUG_VOID_RETURN; } /* * FUNCTION * * ar_write logical write of current logical block * * SYNOPSIS * * VOID ar_write (fip, magic) * struct finfo *fip; * int magic; * * DESCRIPTION * * Performs logical write of current logical block. * This is also where several things in the header * get initialized. * * Note that in particular, the current volume number and * block checksum ARE NOT computed at this time. This operation * is delayed until just before the write to the archive, because * the buffer may have to be written to a different volume than * the current volume. When it comes time to write out the buffer, * all the blocks are patched with the volume number at the time * of the write (by patch_buffer()), and their checksums are computed * after the volume number patch, just before the write. * */ VOID ar_write (fip, magic) struct finfo *fip; int magic; { DBUG_ENTER ("ar_write"); data = TRUE; dirty = TRUE; copyname (blocks[lbi].HD.h_name, fip -> fname); if (IS_COMPRESSED (fip)) { addz (blocks[lbi].HD.h_name); } TOHEX (blocks[lbi].HD.h_alba, gsba + lbi); TOHEX (blocks[lbi].HD.h_flba, fip -> flba); TOHEX (blocks[lbi].HD.h_time, info.bru_time); TOHEX (blocks[lbi].HD.h_bufsize, bufsize); TOHEX (blocks[lbi].HD.h_msize, msize); TOHEX (blocks[lbi].HD.h_magic, magic); TOHEX (blocks[lbi].HD.h_release, release); TOHEX (blocks[lbi].HD.h_level, level); TOHEX (blocks[lbi].HD.h_variant, variant); DBUG_VOID_RETURN; } /* * FUNCTION * * ar_close close the virtual disk file * * SYNOPSIS * * VOID ar_close() * * DESCRIPTION * * If the virtual disk file is open, then closes it. * * If the device is unknown, assume that closing it "rewinds" it. * If the device is known and the D_NOREWIND flag is NOT set then * closing it rewinds it. Setting the pba to 0 is sufficient * (I believe) but initializing the rest of the buffer control * variables doesn't hurt. This code is starting to get really * twisted!! Better safe than sorry... * */ VOID ar_close () { DBUG_ENTER ("ar_close"); lastvolume = *volume; /* in case doing -d */ if (afile.fildes >= 0) { ar_sync(); #if HAVE_SHM dbl_flush (); eject_media (); if (trans_dbl != DBL_DOBUF) { if (s_close (afile.fildes) == SYS_ERROR) { bru_error (ERR_ARCLOSE); } } #else eject_media (); if (s_close (afile.fildes) == SYS_ERROR) { bru_error (ERR_ARCLOSE); } #endif afile.fildes = -1; } if ((ardp == NULL) || (!(ardp -> dv_flags & D_NOREWIND))) { pba = 0; gsba = 0; vsba = 0; grp = 0; *volume = 0; lbi = -1; /* CAUTION ! */ dirty = FALSE; data = FALSE; } DBUG_PRINT ("ar_io", ("virtual disk file closed")); DBUG_VOID_RETURN; } /* * FUNCTION * * rgrp fill blocks buffer from archive stream * * SYNOPSIS * * rgrp () * * DESCRIPTION * * Fills the blocks buffer from the archive stream at the * current read/write point. * * Also note that if the input is being read from a pipe * then there is no guarantee that the desired number of * bytes will be available in the pipe. Only the number * of bytes available will be returned. Thus we continue * to request additional bytes until the buffer is full or * a read error occurs. */ static VOID rgrp () { DBUG_ENTER ("rgrp"); if (pipe_io) { rpipe (); } else { rfile (); } DBUG_VOID_RETURN; } /* * FUNCTION * * rpipe file archive buffer from a pipe * * SYNOPSIS * * static VOID rpipe () * * DESCRIPTION * * Used to fill archive buffer from a pipe. Since pipe * I/O does not block until specified number of bytes are * ready, but rather returns the bytes immediately available, * we simply hang in a loop until the buffer is full or no * bytes are returned (signifying end of file). * * If the buffer only gets partially filled (such as when * the blocking factor of the writer is different than the * buffer size, no error occurs as long as the last block * placed in the buffer is the end of archive marker. This * is because rpipe will never be called again since the * upper level routines will find the end of the archive and * things will terminate normally. If however, the end of * archive is not found in the data read during a partial * buffer fill, the upper level routines will note that the * remaining (unfilled) data space contains bogus data, another * call to rpipe will eventually occur, it will get no data at * all, a read error error will be issued, and bru will terminate * via a direct call to done. * */ static VOID rpipe () { register char *destination; register int request; register int iobytes; DBUG_ENTER ("rpipe"); destination = blocks[0].bytes; request = bufsize; DBUG_PRINT ("rpipe", ("get %u bytes at pba %ld", request, pba)); do { iobytes = s_read (afile.fildes, destination, (UINT) request); request -= iobytes; destination += iobytes; DBUG_PRINT ("pipe_in", ("got %d bytes from pipe", iobytes)); } while (iobytes > 0 && request > 0); einfo.breads += BLOCKS (bufsize - request); pba += BLOCKS (bufsize - request); if (request == bufsize) { bru_error (ERR_ARREAD, pba); einfo.rhard++; done (); } data = TRUE; dirty = FALSE; if (bswap || sswap) { do_swap (); } if (first) { check_vol (); first = FALSE; } DBUG_VOID_RETURN; } /* * Note that we only check for the proper volume if the * read gets at least one block of actual data. * * One problem here with error recovery. When the retry limit * is reached, we attempt to continue by just ignoring the problem * and going for a new block. Since the file pointer still points * to the current bad spot, we want to be able to skip over it by * doing a phys_seek to the new location. This works fine with seekable * devices, like floppies, but for non-seekable devices phys_seek * attempts to reposition the file pointer by reading to the new * block, which won't work because that's how we got into this mess * in the first place. The bottom line is, if the device is not * seekable, we haven't gotten past the bad spot by retries, and * read/writes do not advance the media (D_ADVANCE in brutab), we * are probably stuck there forever and should just give up rather * than hanging in an infinite loop. */ static VOID rfile () { register int iobytes; DBUG_ENTER ("rfile"); tries = 0; iobytes = 0; while (iobytes != bufsize) { DBUG_PRINT ("rfile", ("read %u bytes at pba %ld", bufsize, pba)); #if HAVE_SHM iobytes = dbl_read (blocks[0].bytes, (long) bufsize); #else iobytes = s_read (afile.fildes, blocks[0].bytes, bufsize); #endif tries++; einfo.breads += BLOCKS (bufsize); if (iobytes == bufsize) { pba += BLOCKS (bufsize); } else { einfo.rsoft++; if (!recover (iobytes, errno, TRUE)) { break; } } } data = TRUE; dirty = FALSE; if (bswap || sswap) { do_swap (); } if (first) { check_vol (); first = FALSE; } DBUG_VOID_RETURN; } /* * FUNCTION * * wgrp write blocks buffer to archive stream * * SYNOPSIS * * static VOID wgrp () * * DESCRIPTION * * Writes the blocks buffer to the archive stream at the * current read/write point. * * If the output is going to a pipe, there is a limit on how * much data can be written in a single write command. Thus * there is a separate routine used for writing to pipes that * continues to write until the buffer is exhausted or a write * error occurs. * */ static VOID wgrp () { DBUG_ENTER ("wgrp"); if (pipe_io) { wpipe (); } else { wfile (); } DBUG_VOID_RETURN; } /* * FUNCTION * * wpipe write buffer to a pipe * * SYNOPSIS * * static VOID wpipe () * * DESCRIPTION * * Used to write the archive buffer out to a pipe. Since there * is a limit on how many bytes can be written to a pipe, we * write out the buffer one archive block at a time. * * Note that we specifically always write out a full buffer worth * of data, even when the last buffer written contains only a few * new blocks. Although we know the last block used (in lbi), we * write out the full buffer because this is the same behavior * that occurs when the output is stream is not a pipe. Thus * * bru -cf - >/usr/me/myfile * bru -cf - | dd of=/usr/me/myfile * bru -cf /usr/me/myfile * * will all produce output files of the same size (exact multiple * of bufsize). If this was not true, a subsequent read of the archive * file would have a problem with the last buffer since it would be * only a partial buffer. Naturally, this scheme results in some * left over blocks from the last write being used to pad out the * buffer to a full bufsize bytes. These normally cause no problem * except in the very rare case where the terminator block is somehow * missed during a read. * */ static VOID wpipe () { register int index; register int request; register int iobytes; register int bufblocks; auto char *source; DBUG_ENTER ("wpipe"); patch_buffer (); bufblocks = BLOCKS (bufsize); DBUG_PRINT ("wpipe", ("write out %d blocks from buffer", bufblocks)); for (index = 0; index < bufblocks; index++) { source = blocks[index].bytes; request = BLKSIZE; DBUG_PRINT ("pipe_out", ("write %u bytes from block %d", request, index)); iobytes = s_write (afile.fildes, source, (UINT) request); if (iobytes != request) { bru_error (ERR_ARWRITE, pba); einfo.whard++; done (); } pba++; einfo.bwrites++; } data = TRUE; dirty = FALSE; DBUG_VOID_RETURN; } /* * FUNCTION * * wfile write buffer to a non-pipe output file * * SYNOPSIS * * wfile (); * * DESCRIPTION * * Used to write output to a non-pipe archive. Writes are done * in BUFSIZE increments, with errors being retried up to the * specified limit. * * For complications due to error recovery algorthms, see comments * in description of analogous function rfile() for reads. * */ static VOID wfile () { register int iobytes; DBUG_ENTER ("wfile"); tries = 0; iobytes = 0; while (iobytes != bufsize) { DBUG_PRINT ("wfile", ("write %u bytes at pba %ld", bufsize, pba)); #if HAVE_SHM iobytes = dbl_write (blocks[0].bytes, bufsize); #else patch_buffer (); iobytes = s_write (afile.fildes, blocks[0].bytes, bufsize); #endif tries++; einfo.bwrites += BLOCKS (bufsize); if (iobytes == bufsize) { pba += BLOCKS (bufsize); dirty = FALSE; } else { #if HAVE_SHM if (trans_dbl == DBL_NOBUF) { einfo.wsoft++; if (!recover (iobytes, errno, FALSE)) { break; } } #else einfo.wsoft++; if (!recover (iobytes, errno, FALSE)) { break; } #endif } } first = FALSE; if (bswap || sswap) { do_swap (); } DBUG_VOID_RETURN; } /* * FUNCTION * * ar_sync write out sector buffer if "dirty" * * SYNOPSIS * * VOID ar_sync() * * DESCRIPTION * * Writes the contents of block buffer if necessary. * */ VOID ar_sync() { DBUG_ENTER("ar_sync"); if (afile.fildes >= 0 && dirty) { DBUG_PRINT ("ar_io", ("flushing dirty buffer")); wgrp (); } DBUG_VOID_RETURN; } /* * FUNCTION * * allocate allocate space for the block buffer * * SYNOPSIS * * static VOID allocate () * * DESCRIPTION * * This function is called when the archive file is first * opened, to allocate space for the block buffers. * * Any error is immediately fatal. * */ static VOID allocate () { int pgsize; DBUG_ENTER ("allocate"); if (blocks == NULL) { if(((pgsize=getpagesize())<=0) || (pgsize & (pgsize-1))) { bru_error (ERR_PAGESZ, pgsize); done (); } pgsize--; /* make it a mask */ blocks = (union blk *) get_memory (bufsize+pgsize, FALSE); if (blocks == NULL) { bru_error (ERR_BALLOC, bufsize/1024); done (); } /* align to page boundary for best performance */ blocks_orig = blocks; blocks = (union blk *)(((unsigned int)blocks+pgsize)&~pgsize); } DBUG_VOID_RETURN; } VOID deallocate () { DBUG_ENTER ("deallocate"); if (blocks_orig != NULL) { s_free ((char *) blocks_orig); blocks = blocks_orig = NULL; } DBUG_VOID_RETURN; } /* * FUNCTION * * ar_tell return current logical block address * * SYNOPSIS * * LBA ar_tell () * * DESCRIPTION * * Returns logical block address of current block in archive. * The current block is the block which was the last argument of * an lba_seek command or -1 by default. * * NOTES * * Does not use DBUG_ENTER, LEAVE, or DBUG_n macros because this * function is called in the middle of printing some verbose * output. * */ LBA ar_tell () { register LBA rtnval; if (mode == 'e') { rtnval = lba_estimate; } else { rtnval = gsba + lbi; } return (rtnval); } /* * FUNCTION * * ar_next locate next logical block in archive * * SYNOPSIS * * union *blk ar_next () * * DESCRIPTION * * Locates next logical block in archive and returns pointer * to usuable data buffer. Similar to lba_seek except that * the logical block number defaults to the current logical * block number + 1. * * Note that no I/O is implied by this operation, it simply * locates a buffer and sets up a new current logical block * number. The ar_read function must still be called if * the buffer is expected to contain valid data. * */ union blk *ar_next () { register union blk *new; DBUG_ENTER ("ar_next"); new = ar_seek (1L, 1); DBUG_RETURN (new); } /* * NOTES * * It is assumed that switching media will cause the * next read to start at physical block 0. This may * not always be true if the archive file is not closed. * One would expect it to be true for magnetic tapes but * not necessarily random access devices like removable * disks. Thus it may be necessary to use the D_REOPEN * flag in brutab for the specific device, to force this * condition. In some systems, such as the Convergent * Miniframe, removing a floppy without closing and * reopening the file (by D_REOPEN) will cause a read * error anyway. * * It is now the default to close the archive device file * and reopen it on a media switch. The reason for this is * that for some devices, a reopen is a must, otherwise we * will never be able to access the device again. If this * situation occurs for a device not in the brutab file, bru * will simply go into an infinite prompting loop, asking for * the next volume. Thus the D_REOPEN flag is now obsolete. * The D_NOREOPEN flag is used to suppress the close and reopen * for known devices that can continue without the reopen. * * If the user did not specify an explicit buffer size, and the * new media's entry in the brutab file does specify a default * buffer size that is not identical to the current buffer size, * a warning message is printed. The buffer size MUST remain * constant across all volumes of a multiple volume archive. * */ VOID switch_media (nvolume) int nvolume; { register BOOLEAN new; UINT newbufsize; BOOLEAN new_arfile (); DBUG_ENTER ("switch_media"); eject_media (); if (ardp == NULL || !(ardp -> dv_flags & D_NOREOPEN)) { if (afile.fildes >= 0) { DBUG_PRINT ("reopen", ("close archive prior to media switch")); if (s_close (afile.fildes) == SYS_ERROR) { bru_error (ERR_ARCLOSE); } afile.fildes = -1; } } DBUG_PRINT ("vol", ("new volume = %d", nvolume)); new = new_arfile (nvolume); if (new || ardp == NULL || !(ardp -> dv_flags & D_NOREOPEN)) { ardp = get_ardp (afile.fname); if (ardp != NULL && !flags.bflag) { newbufsize = BLOCKS (ardp -> dv_bsize) * BLKSIZE; if (newbufsize != bufsize && !forcebuffer ()) { bru_error (ERR_BSIZE, bufsize, newbufsize); } } if (afile.fildes >= 0) { if (s_close (afile.fildes) == SYS_ERROR) { bru_error (ERR_ARCLOSE); } afile.fildes = -1; } if (!do_open ()) { switch_media (nvolume); } } if (new) { qfwrite (); } *volume = nvolume; pba = 0; first = TRUE; checkswap = TRUE; sswap = FALSE; bswap = FALSE; DBUG_PRINT ("swap", ("checkswap = first = TRUE, sswap = bswap = FALSE")); if (reading) { data = FALSE; } DBUG_VOID_RETURN; } /* * FUNCTION * * r_phys_seek recovery phys_seek with recursion detection * * SYNOPSIS * * static VOID r_phys_seek (npba) * LBA npba; * * DESCRIPTION * * During error recovery, we may want to call phys_seek to seek * to a specific physical location on the media. For nonseekable * devices, this may fail because we may attempt to seek to a given * location by reading, which can itself get another error, thus * getting into a recursive situation. This routine simply * increments a recursion counter for each call to the real * phys_seek, and decrements it at each return. Calls to phys_seek * which never return cause the counter to increment and when * it reaches the preset limit, this volume is abandoned. * */ static VOID r_phys_seek (npba) LBA npba; { DBUG_ENTER ("r_phys_seek"); DBUG_PRINT ("recursions", ("recursions = %d", recursions)); if (recursions < D_RECUR) { recursions++; phys_seek (npba); recursions--; } else { recursions = 0; errno = firsterrno; bru_error (ERR_EOV, *volume + 1); infer_end (gsba-vsba); } DBUG_VOID_RETURN; } /* * FUNCTION * * recover recover from a read/write error * * SYNOPSIS * * static VOID recover (iobytes, ioerr, readflag) * int iobytes; * int ioerr; * BOOLEAN readflag; * * DESCRIPTION * * When a read or write error on the archive media is detected, * this routine gains control and attempts to set things up * so that the next read or write will be successful. * The recovery algorithms are complicated by the fact that * the behavour due to various errors (such as insertion of * unformatted media, end of device, media I/O errors, etc) * is very implementation and device dependent. * * The basic strategy is to switch to next media if the * error was definitely due to an attempt to do I/O past * the physical bounds, otherwise do at least one retry. * Then attempt to infer either the end of the device or * insertion of unformatted media. Finally, simply retry * the I/O up to the retry limit specified in the configuration * file. * * Because the error recovery algorithms can cause the original * errno, from the original error, to be destroyed, we save * errno for the first error, then restore it if all attempts * to recover fail and we need to report an error. This prevents * perror, or its equivalent, from reporting mysterious reasons for * failures. * */ static BOOLEAN recover (iobytes, ioerr, readflag) int iobytes; int ioerr; BOOLEAN readflag; { register int nvolume; register BOOLEAN unfmt; register BOOLEAN wprot; register BOOLEAN eod; register BOOLEAN rawtape; register LBA errorpba; register BOOLEAN recoverable; register BOOLEAN advanced; register BOOLEAN msgdone = FALSE; DBUG_ENTER ("recover"); DBUG_PRINT ("fault", ("io of %d bytes", iobytes)); DBUG_PRINT ("fault", ("ioerr %d", ioerr)); if (tries == 1) { firsterrno = ioerr; DBUG_PRINT ("errno", ("errno from original error = %d", firsterrno)); } errorpba = pba; advanced = (ardp != NULL && ardp -> dv_flags & D_ADVANCE); if (advanced) { pba += BLOCKS (bufsize); } DBUG_PRINT ("fault", ("error pba %ld, current pba %ld", errorpba, pba)); DBUG_PRINT ("fault", ("gsba %ld grp %ld", gsba, grp)); DBUG_PRINT ("fault", ("vsba %ld", vsba)); s_sleep ((UINT) 3); unfmt = unformatted (iobytes, ioerr, readflag); wprot = write_protect (iobytes, ioerr, readflag); #ifdef sgi /* regardless of what write_protect() returns, if errno is EROFS, then the media is write-protected! */ if(!readflag && wprot != TRUE) { if(ioerr == EROFS) wprot = TRUE; else if(sgiqic24(ioerr) == TRUE) { wprot = TRUE; msgdone = TRUE; } } #endif sgi eod = end_of_device (iobytes, ioerr, readflag); rawtape = raw_tape (); recoverable = TRUE; if (msize != 0 && errorpba >= BLOCKS (msize)) { DBUG_PRINT ("fault", ("found end of volume %d", *volume + 1)); nvolume = (int) (*volume + (errorpba / BLOCKS (msize))); switch_media (nvolume); vsba = nvolume * BLOCKS (msize); } else if (!first && eod && msize == 0) { DBUG_PRINT ("fault", ("end of known device of unknown size")); infer_end (errorpba); } else if (ardp != NULL && first && unfmt && !wprot) { DBUG_PRINT ("fault", ("known device unformatted")); errno = firsterrno; if (!(ardp -> dv_flags & D_FORMAT) || !(s_format (afile.fildes))) { bru_error (ERR_FORMAT); switch_media (*volume); } r_phys_seek (errorpba); } else if (ardp != NULL && first && !unfmt && wprot && !readflag) { DBUG_PRINT ("fault", ("known device write protected")); errno = firsterrno; if(msgdone == FALSE) bru_error (ERR_WPROT); switch_media (*volume); r_phys_seek (errorpba); } else if (ardp != NULL && first && iobytes > 0 && rawtape && readflag) { DBUG_PRINT ("fault", ("read of known raw tape with other blocking factor")); #if HAVE_SHM if (trans_dbl == DBL_NOBUF) { new_bufsize (iobytes); r_phys_seek (errorpba); } #else new_bufsize (iobytes); r_phys_seek (errorpba); #endif } else if (ardp != NULL && first && unfmt && wprot) { DBUG_PRINT ("fault", ("known device is unformatted or write protected")); errno = firsterrno; bru_error (ERR_FIRST); switch_media (*volume); r_phys_seek (errorpba); } else if (first && (iobytes == -1) && (ioerr == EIO)) { DBUG_PRINT ("fault", ("device had IO Error")); errno = firsterrno; if(chknomedia()) bru_error(ERR_NOMEDIA); else bru_error (ERR_IOERR); switch_media (*volume); r_phys_seek (errorpba); } else if (msize == 0 && (!seekflag && advanced)) { DBUG_PRINT ("fault", ("can't back up and media size unknown, infer end")); errno = firsterrno; bru_error (ERR_IEOV, *volume + 1); infer_end (errorpba); } else if (msize != 0 && (!seekflag && advanced)) { DBUG_PRINT ("fault", ("can't back up, but not end, so unrecoverable")); DBUG_PRINT ("fault", ("assume advanced over bad spot")); recoverable = FALSE; harderror (errorpba, readflag, iobytes); /* try twice as hard as most other cases, but not forever; * don't retry at all if eod is set */ if(eod || tries > (2*D_TRIES)) infer_end (errorpba); } else if (msize == 0 && tries == D_TRIES) { DBUG_PRINT ("fault", ("retry limit hit, media size unknown, infer end")); errno = firsterrno; bru_error (ERR_IEOV, *volume + 1); infer_end (errorpba); } else if (msize != 0 && tries >= D_TRIES) { recoverable = FALSE; harderror (errorpba, readflag, iobytes); if (seekflag) { DBUG_PRINT ("fault", ("seek past bad spot")); r_phys_seek ((LBA) (errorpba + BLOCKS (bufsize))); } else { recursions = 0; errno = firsterrno; bru_error (ERR_EOV, *volume + 1); infer_end (errorpba); } } else if ((ardp == NULL) && isrmt(afile.fildes) && first && !unfmt && wprot && !readflag) { DBUG_PRINT ("fault", ("remote device write protected")); errno = firsterrno; if(msgdone == FALSE) bru_error (ERR_WPROT); switch_media (*volume); r_phys_seek (errorpba); } else { DBUG_PRINT ("fault", ("retry %d at %ld", tries, errorpba)); r_phys_seek (errorpba); } DBUG_PRINT ("fault", ("volume %d vsba %ld", *volume, vsba)); DBUG_PRINT ("fault", ("gsba %ld pba %ld", gsba, pba)); DBUG_RETURN (recoverable); } static BOOLEAN do_open () { register int open_flag; register int open_mode; register BOOLEAN rtnval; register BOOLEAN accessible; register BOOLEAN exists; DBUG_ENTER ("do_open"); #if HAVE_SHM if (!flags.Dflag || pipe_io || seekflag) { trans_dbl = DBL_NOBUF; } else { trans_dbl = DBL_DOBUF; } dbl_errp (inerr_recover, outerr_recover); dbl_bufp (0, 0, bufsize); dbl_iop (s_read, s_write); #endif if (mode == 'c') { reading = FALSE; } else { reading = TRUE; } if (STRSAME (afile.fname, "-")) { open_std (); rtnval = TRUE; } else { rtnval = FALSE; exists = file_access (afile.fname, A_EXISTS, FALSE); if (mode == 'c') { open_flag = O_WRONLY | O_CREAT; open_mode = 0666; /* this code used to check using the file_access, but the * concept of access in bru is badly broken. For example, * if the the "device" is a symlink that isn't dangling, the * test would always fail. Since that's always the case with * /dev/tape in irix 6.4, and the open code does the right thing * always, I've just removed the totally bogus code, and always * set accessible to TRUE. Olson, 11/96 */ accessible = TRUE; } else { open_flag = O_RDONLY; open_mode = 0; /* see comments in block above. Olson */ accessible = TRUE; } if (!accessible) { rtnval = FALSE; bru_error (ERR_AROPEN, afile.fname); } else { afile.fildes = s_open (afile.fname, open_flag, open_mode); if (afile.fildes < 0) { rtnval = FALSE; eject_media (); bru_error (ERR_AROPEN, afile.fname); } else { chkandfixaudio(afile.fildes); rtnval = TRUE; if (flags.pflag) { seekflag = FALSE; } else { seekflag = seekable (afile.fname, BLKSIZE); } if (!exists) { file_chown (afile.fname, info.bru_uid, info.bru_gid); } #if HAVE_SHM if (!wdblinit) { if (!reading && trans_dbl == DBL_DOBUF) { wdblinit++; } if (mode == 'c') { if (dbl_setup (afile.fildes, trans_dbl, DBL_WRITING)) { bru_error (ERR_DBLSUP); done (); } } else { if (dbl_setup (afile.fildes, trans_dbl, DBL_READING)) { bru_error (ERR_DBLSUP); done (); } } if (trans_dbl == DBL_DOBUF && flags.vflag > 1) { s_fprintf (logfp, "using double buffering to %s\n", afile.fname); } } #endif } } } DBUG_PRINT ("arf", ("%s open on %o", afile.fname, afile.fildes)); DBUG_RETURN (rtnval); } VOID do_swap () { register int maxindex; register int index; DBUG_ENTER ("do_swap"); maxindex = BLOCKS (bufsize); if (bswap) { for (index = 0; index < maxindex; index++) { DBUG_PRINT ("swap", ("swap bytes for block %ld", (long) (gsba+index))); swapbytes (&blocks[index]); } } if (sswap) { for (index = 0; index < maxindex; index++) { DBUG_PRINT ("swap", ("swap shorts for block %ld", (long) (gsba+index))); swapshorts (&blocks[index]); } } DBUG_VOID_RETURN; } /* * Check to see if each buffer needs to have any byte or * short swapping done. The checkswap flag insures that this * is only done once per media, even when things are called * recursively. Otherwise it is possible to get confused over * whether or not swapping is necessary. */ BOOLEAN need_swap (blkp) register union blk *blkp; { DBUG_ENTER ("need_swap"); DBUG_PRINT ("swap", ("checkswap=%d", checkswap)); DBUG_PRINT ("swap", ("sswap=%d bswap=%d", sswap, bswap)); if (checkswap) { checkswap = FALSE; bswap = FALSE; sswap = FALSE; swapbytes (blkp); DUMP_BLK (blkp, 0L); if (chksum_ok (blkp) && magic_ok (blkp)) { DBUG_PRINT ("swap", ("swap bytes")); bswap = TRUE; } swapshorts (blkp); DUMP_BLK (blkp, 0L); if (chksum_ok (blkp) && magic_ok (blkp)) { DBUG_PRINT ("swap", ("swap bytes and shorts")); sswap = TRUE; } swapbytes (blkp); DUMP_BLK (blkp, 0L); if (chksum_ok (blkp) && magic_ok (blkp)) { DBUG_PRINT ("swap", ("swap shorts")); sswap = TRUE; bswap = FALSE; } swapshorts (blkp); DUMP_BLK (blkp, 0L); } DBUG_PRINT ("swap", ("checkswap=%d", checkswap)); DBUG_PRINT ("swap", ("sswap=%d bswap=%d", sswap, bswap)); DBUG_RETURN (sswap || bswap); } static VOID infer_end (errpba) LBA errpba; { DBUG_ENTER ("infer_end"); DBUG_PRINT ("fault", ("inferred end of volume %d", *volume + 1)); switch_media (*volume + 1); vsba += errpba; phys_seek (0L); tries = 0; recursions = 0; DBUG_VOID_RETURN; } /* * Note that we must check for a proper checksum because it * is not guaranteed at this point that the buffer data is * good (a bad read for example). We don't want to be * generating messages to load volume 67,892 if the first * block of a media is unreadable for some reason! * * Also note that since this is at a lower level than readinfo, * we also need to insure that the swap flags are up to date * by calling need_swap(). * */ static VOID check_vol () { register int vol; DBUG_ENTER ("check_vol"); if (need_swap (&blocks[0])) { do_swap (); } if (chksum_ok (&blocks[0])) { vol = (int) FROMHEX (blocks[0].HD.h_vol); if (vol != *volume) { if(flags.Tflag) { if(flags.gflag) { (VOID) s_fprintf(logfp, "E0 %d %d\n", vol+1, *volume+1); s_fflush(logfp); done(); } else { LBA oldpba; (VOID) s_fprintf(logfp, "E3 %d %d\n", vol+1, *volume+1); s_fflush(logfp); oldpba = pba; switch_media (*volume); phys_seek ((LBA) (oldpba - BLOCKS (bufsize))); rgrp (); } } else { bru_error (ERR_ARVOL, vol + 1, *volume + 1); bad_vol (vol); } } if (artime != (time_t)0 && artime != FROMHEX (blocks[0].HD.h_time)) { if(flags.Tflag) { if(flags.gflag) { (VOID) s_fprintf(logfp, "E1\n"); s_fflush(logfp); done(); } else { LBA oldpba; (VOID) s_fprintf(logfp, "E1\n"); s_fflush(logfp); oldpba = pba; switch_media (*volume); phys_seek ((LBA) (oldpba - BLOCKS (bufsize))); rgrp (); } } else { bru_error (ERR_ARTIME, s_ctime ((long *) &artime)); bad_vol (vol); } } } DBUG_VOID_RETURN; } /* * Note that for the reload case we must save the current pba, since * switch_volume() currently forces it back to zero. We need to be * able to seek to the same relative point in the new volume. */ static VOID bad_vol (vol) int vol; { register char key; register LBA mblocks; register LBA oldpba; DBUG_ENTER ("bad_vol"); key = response ("c => continue q => quit r => reload s => shell", 'q'); switch (key) { case 'q': case 'Q': done (); break; case 'r': case 'R': oldpba = pba; switch_media (*volume); phys_seek ((LBA) (oldpba - BLOCKS (bufsize))); rgrp (); break; case 'c': case 'C': readsizes (&blocks[0]); mblocks = BLOCKS (msize); gsba += (vol - *volume) * mblocks; vsba = vol * mblocks; grp = gsba / BLOCKS (bufsize); *volume = vol; DBUG_PRINT ("cont", ("grp %ld gsba %ld", grp, gsba)); DBUG_PRINT ("cont", ("volume %d vsba %ld", *volume, vsba)); break; case 'S': case 's': fork_shell (); bad_vol (vol); break; } DBUG_VOID_RETURN; } BOOLEAN ar_ispipe () { return (pipe_io); } /* * FUNCTION * * new_arfile issue prompt for new media and save file name * * SYNOPSIS * * BOOLEAN new_arfile (vol) * int vol; * * DESCRIPTION * * Issue prompt for new volume and remember name if specified. * Note that the new name, if any, overwrites the old name. * Returns TRUE if new name was given, false otherwise. * */ BOOLEAN new_arfile (vol) int vol; { register BOOLEAN rtnval; auto char newname[NAMESIZE]; SIGTYPE prevINT; SIGTYPE prevQUIT; DBUG_ENTER ("new_arfile"); DBUG_PRINT ("vol", ("new volume = %d", vol)); rtnval = FALSE; sig_push (&prevINT, &prevQUIT); sig_done (); #ifdef amiga FlushRaw (); MotorOff (); #endif if(flags.Tflag) { s_fprintf(logfp, "S %d\n", vol+1); s_fflush(logfp); fgets(newname, sizeof(newname), stdin); if(strncmp(newname, "C", 1) == 0) newname[0] = EOS; else { printf("Internal error: %s", newname); done(); } } else { tty_newmedia (vol + 1, afile.fname, newname, sizeof (newname)); } sig_pop (&prevINT, &prevQUIT); if ( (newname[0] != EOS) && isgraph(newname[0]) ) { rtnval = TRUE; copyname (afile.fname, newname); } DBUG_RETURN (rtnval); } /* * FUNCTION * * ar_vol return number of current volume * * SYNOPSIS * * int ar_vol () * * DESCRIPTION * * Returns the index of the current volume [0,1,...]. * Note that if current mode is media estimation mode * then the value returned is the estimated volume number * since there is not really an archive open. * * NOTES * * This function does not contain DBUG package macros because * it is called in the middle of printing normal output lines. * */ int ar_vol () { register int rtnval; if (mode == 'e') { rtnval = vol_estimate; } else { rtnval = *volume; } return (rtnval); } /* * Note that the new buffer size will be smaller than the * current buffer size, since we are reading from the raw * magtape file and got less data than requested. We * cannot deallocate the larger buffer and allocate a * smaller buffer at this level, since parent routines * have been passed a pointer to the current buffer, and * know nothing about the fact that we have had an "archive * buffer fault". Thus we will simply force the buffer size * to be smaller but continue to use the same space allocated * for the larger, previous buffer. * * Also note that if the old buffer size was bigger than the * entire archive (say a buffer size of 200K, when the archive * is 4 records of 20K each) some drivers may return all the * archive data, rather than just the data in the first physical * record. The Sun 3/50 (/dev/rst8, OS release 3.0) is one such * system. This may confuse us terribly... * * I have commented out the flags.bflag line because it * was suppressing checking of the recorded buffer size against * the actual buffer size in routine readsizes() in readinfo.c. * I am not sure why it was there to begin with... * Fred Fish, 20-Nov-86. */ static VOID new_bufsize (size) int size; { DBUG_ENTER ("new_bufsize"); /* flags.bflag = TRUE; */ bufsize = size; ar_close (); (VOID) do_open (); DBUG_VOID_RETURN; } /* * FUNCTION * * ar_estimate accumulate estimate blocks and compute volume * * SYNOPSIS * * VOID ar_estimate (size); * LBA size; * * DESCRIPTION * * Given number of logical blocks to accumulate, adds it to the * current total and computes the new volume number based on * the known media size. If the media size is unknown then * the volume estimate is forced to one (note that vol_estimate * is the volume index). * */ VOID ar_estimate (size) LBA size; { lba_estimate += size; if (msize > 0) { vol_estimate = (int) (KBYTES (lba_estimate) / B2KB (msize)); } else { vol_estimate = 0; } } /* * FUNCTION * * patch_buffer perform final operations on buffer * * SYNOPSIS * * static VOID patch_buffer () * * DESCRIPTION * * Because the volume number that a buffer is finally written * to may not correspond to the current volume number at the * time the buffer was filled, the buffer is patched with the * volume number just prior to the write operation. If the write * generates a fault and the media is switched to a new volume, * the buffer is automatically patched again with the updated * volume number just prior to the write retry. Since the checksums * cannot be computed until the rest of the data is stable, the * checksum computations are delayed until this time also. * */ static VOID patch_buffer () { register int index; DBUG_ENTER ("patch_buffer"); index = BLOCKS (bufsize); while (index--) { TOHEX (blocks[index].HD.h_vol, *volume); TOHEX (blocks[index].HD.h_chk, chksum (&blocks[index])); } DBUG_VOID_RETURN; } /* like patch_buffer, but gets a pointer to a single union blk */ patch_buffer_blk(blk) union blk *blk; { register int index; DBUG_ENTER ("patch_buffer"); index = BLOCKS (bufsize); while (index--) { TOHEX (blk->HD.h_vol, *volume); TOHEX (blk->HD.h_chk, chksum (blk)); blk++; } DBUG_VOID_RETURN; } /* * FUNCTION * * clean_buffer clear to end of io buffer * * SYNOPSIS * * static VOID clean_buffer (iobytes) * int iobytes; * * DESCRIPTION * * Cleans up rest of buffer after a partial read, so that * no junk from a previous read is left hanging around. * This way, other routines don't get fooled by leftover blocks * that have correct checksums and such. * */ static VOID clean_buffer (iobytes) int iobytes; { register char *zap; register int count; DBUG_ENTER ("clean_buffer"); count = bufsize - iobytes; zap = blocks[0].bytes + iobytes; #if HAVE_MEMSET (void) memset (zap, 0, count); #else while (count--) { *zap++ = 0; } #endif DBUG_VOID_RETURN; } /* * FUNCTION * * harderror report location of hard error * * SYNOPSIS * * static VOID harderror (errorpba, readflag, iobytes) * LBA errorpba; * BOOLEAN readflag; * int iobytes; * * DESCRIPTION * * Report location of hard error and adjust soft/hard error * counts appropriately. * * The original errno from the original error that triggered * recovery was stored in firsterrno, and is restored prior to * calling the routine to report the error. */ static VOID harderror (errorpba, readflag, iobytes) LBA errorpba; BOOLEAN readflag; int iobytes; { DBUG_ENTER ("harderror"); errno = firsterrno; if (iobytes < 0) { iobytes = 0; } if (readflag) { bru_error (ERR_ARREAD, errorpba + BLOCKS (iobytes)); einfo.rsoft -= tries; einfo.rhard++; clean_buffer (iobytes); } else { bru_error (ERR_ARWRITE, errorpba + BLOCKS (iobytes)); einfo.wsoft -= tries; einfo.whard++; } DBUG_VOID_RETURN; } static VOID qfwrite () { register char key; DBUG_ENTER ("qfwrite"); if (!reading && (ardp != NULL) && (ardp -> dv_flags & D_QFWRITE)) { bru_error (ERR_QFWRITE, afile.fname); key = response ( "c => continue with next q => quit r => reprompt for volume\n\ts => shell", 'r'); switch (key) { case 'q': case 'Q': done (); break; default: /* same as 'r', in case they just hit return */ switch_media (*volume); break; case 'c': case 'C': break; case 'S': case 's': fork_shell (); qfwrite (); break; } } DBUG_VOID_RETURN; } /* * Test for, and eject the current media, if supported. Under * A/UX, for example, an open for write on a write protected * floppy will fail, so at this point we have no file descriptor * on which to perform the ioctl. Thus if no open file descriptor * is available, attempt to open one for read, just to get a * handle for the ioctl call. * * Note that if the floppy is forced to be nonseekable, by hacking * brutab for example, and double buffering is in effect, then * the ejection fails for some currently unknown reason under A/UX. * This may be some sort of kernel bug, or some bug in the double * buffering scheme. Mysterious... * */ static VOID eject_media () { int fildes; DBUG_ENTER ("eject_media"); if (ardp != NULL && (ardp -> dv_flags & D_EJECT)) { if ((fildes = afile.fildes) < 0) { fildes = s_open (afile.fname, O_RDONLY, 0); } if (fildes >= 0) { DBUG_PRINT ("eject", ("eject media in '%s'", afile.fname)); if (s_eject (fildes) == SYS_ERROR) { bru_error (ERR_EJECT, afile.fname); } } } DBUG_VOID_RETURN; } #ifdef sgi #include #include #include "rmt.h" /* determine if we are trying to write on a QIC24 tape from a tape drive (like the VIPER150) that doesn't support it. Dave Olson, 11/88 */ sgiqic24(ioerr) int ioerr; { struct mtget mt_status; if(ioerr == EINVAL && s_ioctl(afile.fildes, MTIOCGET, (char *)&mt_status) == 0 && (mt_status.mt_dsreg & MT_QIC24)) { bru_error(ERR_NOQIC24); return TRUE; } return FALSE; } /* check to see if an i/o error was due to no media being present */ chknomedia() { struct mtget mt_status; if(s_ioctl(afile.fildes, MTIOCGET, (char *)&mt_status) == 0 && !(mt_status.mt_dposn & MT_ONL)) return TRUE; return FALSE; } /* check to see if it's a drive, and is in audio mode; if it is, then * try to fix it, and also notify them. Otherwise we can write in * audio mode, but they won't be able to get the data back */ static void chkandfixaudio(int mt) { static struct mtop mtop = {MTAUD, 0}; struct mtget mtget; unsigned status; if(ioctl(mt, MTIOCGET, &mtget)) return; status = mtget.mt_dsreg | ((unsigned)mtget.mt_erreg<<16); if(status & CT_AUDIO) { bru_error(ERR_AUDIO); if(ioctl(mt, MTIOCTOP, &mtop) < 0) bru_error(ERR_FIXAUDIO); } } #endif sgi