/* * Copyright 2000, International Business Machines Corporation and others. * All Rights Reserved. * * This software has been released under the terms of the IBM Public * License. For details, see the LICENSE file in the top-level source * directory or online at http://www.openafs.org/dl/license10.html */ #include #include #include #ifdef HAVE_SYS_WAIT_H #include #endif #include #include #include #include #include #include #include "error_macros.h" #include "butm_prototypes.h" #ifdef O_LARGEFILE typedef off64_t osi_lloff_t; #else /* O_LARGEFILE */ #ifdef AFS_HAVE_LLSEEK typedef offset_t osi_lloff_t; #else /* AFS_HAVE_LLSEEK */ typedef off_t osi_lloff_t; #endif /* AFS_HAVE_LLSEEK */ #endif /* O_LARGEFILE */ extern int isafile; #define FILE_MAGIC 1000000007 /* s/w file mark */ #define FILE_BEGIN 0 /* byte field in file mark */ #define FILE_FMEND 1 /* byte field in file mark */ #define FILE_EOD -1 /* byte field in file mark */ #define TAPE_MAGIC 1100000009 /* tape label block */ #define BLOCK_MAGIC 1100000005 /* file data block */ #ifdef AFS_PTHREAD_ENV #define POLL() #define SLEEP(s) sleep(s) #else #define POLL() IOMGR_Poll() #define SLEEP(s) IOMGR_Sleep(s) #endif /* Notes: (PA) * * 1) filemarks and block marks have the magic,bytes fields reversed. This * is undoubtedly a bug. Also note that the two structures have * inconsistent types, overlaying int and afs_int32. * 2) When a volume is dumped, if the volume is locked, the dump will produce * an anomalous tape format of the form: * s/w file begin mark * volume header * s/w file end mark * The design of the original butm code means that this cannot be * handled correctly. The code is modified so that ReadFileData * returns BUTM_ENDVOLUME if it encounters the s/w filemark. */ /* data organization on tape: * all writes are in blocks of BUTM_BLOCKSIZE (= sizeof(blockMark) + BUTM_BLKSIZE) * blockMark contains a magic number and counts of real data bytes * written out in the block. * * each file is preceeded by a fileMark, which acts as the file * delimiter. A file consists of contigous data blocks. TM does * understand or interrpet the data in data blocks. * * The tape begins with a tape label and ends with EOF file markers * in succession (2 or 4 of them ). */ struct fileMark { /* in network byte order */ afs_int32 magic; afs_uint32 nBytes; }; struct tapeLabel { afs_int32 magic; struct butm_tapeLabel label; }; struct progress { usd_handle_t fid; /* file id of simulated tape */ afs_int32 mountId; /* current mountId */ afs_int32 reading; /* read file operation in progress */ afs_int32 writing; /* write file operation in progress */ }; static struct configuration { char tapedir[64]; /* directory to create "tapes" */ afs_int32 mountId; /* for detecting simultaneous mounts */ afs_uint32 tapeSize; /* size of simulated tapes */ afs_uint32 fileMarkSize; /* size of file mark, bytes */ afs_int32 portOffset; /* port + portOffset is used by TC to listen */ } config; static char *whoami = "file_tm"; char tapeBlock[BUTM_BLOCKSIZE]; /* Tape buffer for reads and writes */ #define BLOCK_LABEL 0 /* read/write a tape label */ #define BLOCK_FMBEGIN 1 /* read/write a begin FileMark */ #define BLOCK_DATA 2 /* read/write a data block */ #define BLOCK_FMEND 3 /* read/write an end FileMark */ #define BLOCK_EOD 4 /* read/write an EOD FileMark */ #define BLOCK_EOF 5 /* tape block is a HW EOF mark (usually error) */ #define BLOCK_UNKNOWN 6 /* tape block is unknwon (error) */ #define WRITE_OP 1 #define READ_OP 0 #define READS (((struct progress *)(info->tmRock))->reading) #define WRITES (((struct progress *)(info->tmRock))->writing) /* ---------------------------------------------------------------------- * These routines use the usd library to perform tape operations. * ForkIoctl, ForkOpen, ForkClose, ForwardSpace, BackwardSpace, WriteEOF, * PrepareAccess(nt), ShutdownAccess(nt) * * Return Values: USD functions return 0 if successful or errno if failed. */ /* ------------------ USD Interface Functions Begin ------------------------ */ /* * On Unix, fork a child process to perform an IOCTL call. This avoids * blocking the entire process during a tape operation */ #ifndef AFS_NT40_ENV /* Unix version of function */ static int ForkIoctl(usd_handle_t fd, int op, int count) { int rc; /* return code from system calls */ int i; /* loop index */ int pid; /* process ID of child process */ int status; /* exit status of child process */ int ioctl_rc; /* return code from ioctl call */ int pipefd[2]; /* pipe for child return status */ int forkflag; /* flag set when ready to fork */ usd_tapeop_t tapeop; /* tape operation specification */ int unixfd; #ifdef AFS_PTHREAD_ENV forkflag = 0; /* No need to fork if using pthreads */ #else forkflag = 1; #endif /* initialize tape command structure */ tapeop.tp_op = op; tapeop.tp_count = count; /* create pipe for getting return code from child */ if (forkflag) { rc = pipe(pipefd); if (rc < 0) { printf("butm: Can't open pipe for IOCTL process. Error %d\n", errno); forkflag = 0; } } if (forkflag) { pid = fork(); if (pid < 0) { close(pipefd[0]); close(pipefd[1]); printf("butm: Can't fork IOCTL process. Error %d\n", errno); forkflag = 0; } } if (!forkflag) { /* problem starting child process */ /* do the ioctl anyway, it will probably work */ ioctl_rc = USD_IOCTL(fd, USD_IOCTL_TAPEOPERATION, (void *)&tapeop); } else if (pid == 0) { /* child process */ /* close all unneccessary file descriptors */ /* note: as painful as it is, we have to reach under the covers of * the usd package to implement this functionality. */ unixfd = (intptr_t)(fd->handle); for (i = 3; i < _POSIX_OPEN_MAX; i++) { if (i != unixfd && i != pipefd[1]) { close(i); } } /* do the ioctl call */ ioctl_rc = USD_IOCTL(fd, USD_IOCTL_TAPEOPERATION, (void *)&tapeop); /* * Send the return code back to the parent. * If this fails, there's nothing we can do, but we must test * it in order to avoid complier warnings on some platforms. */ if (write(pipefd[1], &ioctl_rc, sizeof(int)) < 0) { /* don't care */ } exit(0); } else { /* parent process */ close(pipefd[1]); POLL(); /* read the result from the child process */ rc = read(pipefd[0], &ioctl_rc, sizeof(int)); if (rc != sizeof(int)) { /* tape is now in unknown state */ printf("butm: Can't determine IOCTL child status. Error %d\n", errno); ioctl_rc = EFAULT; } close(pipefd[0]); /* get the completion status from the child process */ rc = waitpid(pid, &status, 0); while (rc < 0 && errno == EINTR) { rc = waitpid(pid, &status, 0); } if (rc < 0) { printf("butm: Can't determine IOCTL child status. Error %d\n", errno); } else if (status != 0) { printf ("butm: Unexpected IOCTL process status 0x%04x . Error %d\n", status, errno); } SLEEP(1); } return (ioctl_rc); } #else /* NT version of function */ static int ForkIoctl(usd_handle_t fd, int op, int count) { usd_tapeop_t tapeop; /* Issue requested tape control */ tapeop.tp_op = op; tapeop.tp_count = count; return (USD_IOCTL(fd, USD_IOCTL_TAPEOPERATION, (void *)&tapeop)); } #endif /* !AFS_NT40_ENV */ /* * On Unix, fork a child process to attempt to open the drive. We want to make * certain there is tape in the drive before trying to open the device * in the main process */ #ifndef AFS_NT40_ENV /* Unix version of function. */ static int ForkOpen(char *device) { int rc; /* return code from system calls */ int i; /* loop index */ int pid; /* process ID of child process */ int status; /* exit status of child process */ int open_rc; /* return code from open */ int pipefd[2]; /* pipe for child return status */ int forkflag; /* flag set when ready to fork */ usd_handle_t fd; /* handle returned from open */ #ifdef AFS_PTHREAD_ENV forkflag = 0; /* No need to fork if using pthreads */ #else forkflag = 1; #endif /* create pipe for getting return code from child */ if (forkflag) { rc = pipe(pipefd); if (rc < 0) { printf("butm: Cannot create pipe for OPEN process. Error %d\n", errno); forkflag = 0; } } if (forkflag) { pid = fork(); if (pid < 0) { close(pipefd[0]); close(pipefd[1]); printf("butm: Cannot create child process for OPEN. Error %d\n", errno); forkflag = 0; } } if (!forkflag) { /* problem starting child process */ /* *return success, the caller will discover any problems * when it opens the device. */ open_rc = 0; } else if (pid == 0) { /* child process */ /* close all unneccessary file descriptors */ for (i = 3; i < _POSIX_OPEN_MAX; i++) { if (i != pipefd[1]) { close(i); } } /* try the open */ open_rc = usd_Open(device, USD_OPEN_RDONLY, 0, &fd); if (open_rc == 0) { USD_CLOSE(fd); } /* * Send the return code back to the parent. * If this fails, there's nothing we can do, but we must test * it in order to avoid complier warnings on some platforms. */ if (write(pipefd[1], &open_rc, sizeof(open_rc)) < 0) { /* don't care */ } exit(0); } else { /* parent process */ close(pipefd[1]); POLL(); /* read the result from the child process */ rc = read(pipefd[0], &open_rc, sizeof(open_rc)); if (rc != sizeof(open_rc)) { /* this is not a problem since we will reopen the device anyway */ printf("butm: No response from OPEN process. Error %d\n", errno); open_rc = 0; } close(pipefd[0]); /* get the completion status from the child process */ rc = waitpid(pid, &status, 0); while (rc < 0 && errno == EINTR) { rc = waitpid(pid, &status, 0); } if (rc < 0) { printf("butm: Cannot get status of OPEN process. Error %d\n", errno); } else if (status != 0) { printf ("butm: Unexpected OPEN process exit status 0x%04x. Error %d \n", status, errno); } SLEEP(1); } return (open_rc); } #else /* NT version of function. */ static int ForkOpen(char *device) { return (0); } #endif /* AFS_NT40_ENV */ /* * On Unix, fork a child process to close the drive. If the drive rewinds * on close it could cause the process to block. */ #ifndef AFS_NT40_ENV /* Unix version of function */ static int ForkClose(usd_handle_t fd) { int rc; /* return code from system calls */ int i; /* loop index */ int pid; /* process ID of child process */ int status; /* exit status of child process */ int close_rc, parent_close_rc; /* return codes from close */ int pipefd[2]; /* pipe for child return status */ int ctlpipe[2]; /* pipe for message to child */ int forkflag; /* flag set when ready to fork */ int unixfd; #ifdef AFS_PTHREAD_ENV forkflag = 0; /* No need to fork if using pthreads */ #else forkflag = 1; #endif /* create pipe for getting return code from child */ if (forkflag) { rc = pipe(pipefd); if (rc < 0) { printf("butm: Cannot create pipe for CLOSE process. Error %d\n", errno); forkflag = 0; } } /* create pipe for notifying child when to close */ if (forkflag) { rc = pipe(ctlpipe); if (rc < 0) { close(pipefd[0]); close(pipefd[1]); printf("butm: Cannot create pipe for CLOSE process. Error %d\n", errno); forkflag = 0; } } if (forkflag) { pid = fork(); if (pid < 0) { close(pipefd[0]); close(pipefd[1]); close(ctlpipe[0]); close(ctlpipe[1]); printf("butm: Cannot create CLOSE child process. Error %d\n", errno); forkflag = 0; } } if (!forkflag) { /* problem starting child process */ close_rc = USD_CLOSE(fd); parent_close_rc = close_rc; } else if (pid == 0) { /* child process */ /* close all unneccessary file descriptors */ /* note: as painful as it is, we have to reach under the covers of * the usd package to implement this functionality. */ unixfd = (intptr_t)(fd->handle); for (i = 3; i < _POSIX_OPEN_MAX; i++) { if (i != unixfd && i != ctlpipe[0] && i != pipefd[1]) { close(i); } } /* * The parent writes the control pipe after it closes the device. * We don't actually care about the read; we're just using it to * block until the parent is ready. But we must do something * with the result, to avoid complier warnings on some platforms. */ if (read(ctlpipe[0], &close_rc, sizeof(int)) < 0) { /* don't care */ } close(ctlpipe[0]); /* do the close */ close_rc = USD_CLOSE(fd); /* * Send the return code back to the parent. * If this fails, there's nothing we can do, but we must test * it in order to avoid complier warnings on some platforms. */ if (write(pipefd[1], &close_rc, sizeof(int)) < 0) { /* don't care */ } exit(0); } else { /* parent process */ close(pipefd[1]); close(ctlpipe[0]); POLL(); /* * close the device, this should have no effect as long as the * child has not closed */ parent_close_rc = USD_CLOSE(fd); /* notify the child to do its close */ rc = write(ctlpipe[1], &close_rc, sizeof(int)); /* just send garbage */ if (rc != sizeof(int)) { printf("butm: Error communicating with CLOSE process. Error %d\n", errno); } close(ctlpipe[1]); /* read the result from the child process */ rc = read(pipefd[0], &close_rc, sizeof(int)); if (rc != sizeof(int)) { /* logging is enough, since we wrote a file mark the */ /* return code from the close doesn't really matter */ printf("butm: No response from CLOSE process. Error %d\n", errno); close_rc = 0; } close(pipefd[0]); /* get the completion status from the child process */ rc = waitpid(pid, &status, 0); while (rc < 0 && errno == EINTR) { rc = waitpid(pid, &status, 0); } if (rc < 0) { printf("butm: Cannot get status of CLOSE process. Error %d\n", errno); } else if (status != 0) { printf ("butm: Unexpected exit status 0x%04x from CLOSE process. Error %d\n", status, errno); } /* if either process received an error, then return an error */ if (parent_close_rc < 0) { close_rc = parent_close_rc; } SLEEP(1); } return (close_rc); } #else /* NT version of function */ static int ForkClose(usd_handle_t fd) { return (USD_CLOSE(fd)); } #endif /* AFS_NT40_ENV */ /* Forward space file */ static int ForwardSpace(usd_handle_t fid, int count) { POLL(); if (isafile) { return (0); } else { return (ForkIoctl(fid, USDTAPE_FSF, count)); } } /* Backward space file */ static int BackwardSpace(usd_handle_t fid, int count) { POLL(); if (isafile) { return (0); } else { return (ForkIoctl(fid, USDTAPE_BSF, count)); } } /* write end of file mark */ static int WriteEOF(usd_handle_t fid, int count) { POLL(); if (isafile) { return (0); } else { return (ForkIoctl(fid, USDTAPE_WEOF, count)); } } /* rewind tape */ static int Rewind(usd_handle_t fid) { if (isafile) { afs_int64 stopOff; return (USD_SEEK(fid, 0, SEEK_SET, &stopOff)); } else { return (ForkIoctl(fid, USDTAPE_REW, 0)); } } /* prepare tape drive for access */ static int PrepareAccess(usd_handle_t fid) { int code = 0; #ifdef AFS_NT40_ENV if (!isafile) { (void)ForkIoctl(fid, USDTAPE_PREPARE, 0); } /* NT won't rewind tape when it is opened */ code = Rewind(fid); #endif /* AFS_NT40_ENV */ return code; } /* decommission tape drive after all accesses complete */ static int ShutdownAccess(usd_handle_t fid) { #ifdef AFS_NT40_ENV if (!isafile) { (void)ForkIoctl(fid, USDTAPE_SHUTDOWN, 0); } #endif /* AFS_NT40_ENV */ return 0; } /* -------------------- USD Interface Functions End ----------------------- */ /* ===================================================================== * Support routines * ===================================================================== */ /* incSize * add the supplied no. of bytes to the byte count of information placed * on the tape. * entry: * dataSize - bytes used on the tape */ void incSize(struct butm_tapeInfo *info, afs_uint32 dataSize) { info->nBytes += dataSize; info->kBytes += (info->nBytes / 1024); info->nBytes %= 1024; } /* incPosition * IF YOU ADD/CHANGE THE ifdefs, BE SURE * TO ALSO MAKE THE SAME CHANGES IN bu_utils/fms.c. * * add the supplied no. of bytes to the byte count of data placed * on the tape. Also check for reaching 2GB limit and reset the * pointer if necessary. This allows us to use >2GB tapes. * entry: * fid - file id for the tape. * dataSize - bytes used on the tape */ void incPosition(struct butm_tapeInfo *info, usd_handle_t fid, afs_uint32 dataSize) { /* Add this to the amount of data written to the tape */ incSize(info, dataSize); info->posCount += dataSize; if (info->posCount >= 2147467264) { /* 2GB - 16K */ info->posCount = 0; #if (defined(AFS_SUN_ENV) || defined(AFS_LINUX24_ENV)) if (!isafile) { afs_int64 off; off = 0; USD_IOCTL(fid, USD_IOCTL_SETSIZE, &off); } #endif } } /* * This accounts for tape drives with a block size different from variable or 16K * blocks and only reads that block size. */ afs_int32 TapeBlockSize; afs_int32 readData(usd_handle_t fid, char *data, afs_uint32 totalSize, afs_int32 *errorP) { afs_int32 toread; /* Number of bytes to read */ afs_uint32 rSize; /* Total bytes read so far */ afs_uint32 tSize; /* Temporary size */ afs_int32 rc; /* return code */ toread = totalSize; /* First, try to read all the data */ rc = USD_READ(fid, &data[0], toread, &rSize); if (rc != 0) { *errorP = rc; return -1; } if (rSize == 0) /* reached EOF */ return rSize; if (rSize != TapeBlockSize) { /* Tape block size has changed */ TapeBlockSize = rSize; printf("Tape blocks read in %d Byte chunks.\n", TapeBlockSize); } /* Read the rest of the data in */ while (rSize < totalSize) { toread = ((totalSize - rSize) < TapeBlockSize ? (totalSize - rSize) : TapeBlockSize); rc = USD_READ(fid, &data[rSize], toread, &tSize); if (rc) *errorP = rc; else rSize += tSize; if (tSize != toread) break; } if (rSize > totalSize) printf("readData - Read > 16K data block - continuing.\n"); return (rSize); } afs_int32 SeekFile(struct butm_tapeInfo *info, int count) { afs_int32 code = 0; struct progress *p; afs_int32 error = 0; if (count == 0) ERROR_EXIT(0); p = (struct progress *)info->tmRock; if (isafile) { /* no reason for seeking through a file */ p->reading = p->writing = 0; return (0); } POLL(); if (count > 0) error = ForwardSpace(p->fid, count); else error = BackwardSpace(p->fid, -count); POLL(); if (error) { info->status |= BUTM_STATUS_SEEKERROR; ERROR_EXIT(BUTM_IOCTL); } info->position += count; incSize(info, (count * config.fileMarkSize)); p = (struct progress *)info->tmRock; p->reading = p->writing = 0; error_exit: if (error) info->error = error; return (code); } /* Step to the next filemark if we are not at one already */ afs_int32 NextFile(struct butm_tapeInfo *info) { afs_int32 code; if (!READS && !WRITES) return 0; code = SeekFile(info, 1); return (code); } static afs_int32 WriteTapeBlock(struct butm_tapeInfo *info, char *buffer, /* assumed to be 16384 bytes with data in it */ afs_int32 length, /* amount data in buffer */ afs_int32 blockType) { afs_int32 code = 0, rc = 0; afs_uint32 wsize; struct tapeLabel *label; struct fileMark *fmark; struct blockMark *bmark; struct progress *p; afs_int32 error = 0; p = (struct progress *)info->tmRock; if (blockType == BLOCK_DATA) { /* Data Block */ if (length == 0) ERROR_EXIT(0); bmark = (struct blockMark *)buffer; memset(bmark, 0, sizeof(struct blockMark)); bmark->magic = htonl(BLOCK_MAGIC); bmark->count = htonl(length); } else if (blockType == BLOCK_FMBEGIN) { /* Filemark - begin */ fmark = (struct fileMark *)buffer; fmark->magic = htonl(FILE_MAGIC); fmark->nBytes = htonl(FILE_BEGIN); } else if (blockType == BLOCK_FMEND) { /* Filemark - end */ fmark = (struct fileMark *)buffer; fmark->magic = htonl(FILE_MAGIC); fmark->nBytes = htonl(FILE_FMEND); } else if (blockType == BLOCK_LABEL) { /* Label */ label = (struct tapeLabel *)buffer; label->magic = htonl(TAPE_MAGIC); } else if (blockType == BLOCK_EOD) { /* Filemark - EOD mark */ fmark = (struct fileMark *)buffer; fmark->magic = htonl(FILE_MAGIC); fmark->nBytes = htonl(FILE_EOD); } /* Write the tape block */ /* -------------------- */ rc = USD_WRITE(p->fid, buffer, BUTM_BLOCKSIZE, &wsize); if ((rc == 0) && (wsize > 0)) { incPosition(info, p->fid, wsize); /* record whats written */ p->writing++; } if (wsize != BUTM_BLOCKSIZE) { info->status |= BUTM_STATUS_WRITEERROR; if (rc != 0) { error = rc; ERROR_EXIT(BUTM_IO); } else ERROR_EXIT(BUTM_EOT); } if (isafile) info->position++; /* Write trailing EOF marker for some block types */ /* ---------------------------------------------- */ if ((blockType == BLOCK_FMEND) || (blockType == BLOCK_LABEL) || (blockType == BLOCK_EOD)) { POLL(); error = WriteEOF(p->fid, 1); POLL(); if (error) { info->status |= BUTM_STATUS_WRITEERROR; ERROR_EXIT(BUTM_IOCTL); } incSize(info, config.fileMarkSize); if (!isafile) info->position++; p->writing = 0; } error_exit: if (error) info->error = error; return (code); } static afs_int32 ReadTapeBlock(struct butm_tapeInfo *info, char *buffer, /* assumed to be 16384 bytes */ afs_int32 *blockType) { afs_int32 code = 0; afs_int32 rsize, fmtype; struct tapeLabel *label; struct fileMark *fmark; struct blockMark *bmark; struct progress *p; *blockType = BLOCK_UNKNOWN; p = (struct progress *)info->tmRock; memset(buffer, 0, BUTM_BLOCKSIZE); label = (struct tapeLabel *)buffer; fmark = (struct fileMark *)buffer; bmark = (struct blockMark *)buffer; rsize = readData(p->fid, buffer, BUTM_BLOCKSIZE, &info->error); if (rsize > 0) { incPosition(info, p->fid, rsize); p->reading++; } if (rsize == 0) { /* Read a HW EOF Marker? OK */ *blockType = BLOCK_EOF; incSize(info, config.fileMarkSize); /* Size of filemark */ if (!isafile) info->position++; /* bump position */ p->reading = 0; /* No reads since EOF */ } else if (rsize != BUTM_BLOCKSIZE) { /* Didn't Read a full block */ info->status |= BUTM_STATUS_READERROR; ERROR_EXIT((rsize < 0) ? BUTM_IO : BUTM_EOT); } else if (ntohl(bmark->magic) == BLOCK_MAGIC) { /* Data block? */ *blockType = BLOCK_DATA; } else if (ntohl(fmark->magic) == FILE_MAGIC) { /* Read a filemark? */ fmtype = ntohl(fmark->nBytes); if (fmtype == FILE_BEGIN) { /* filemark begin */ *blockType = BLOCK_FMBEGIN; } else if (fmtype == FILE_FMEND) { /* filemark end */ *blockType = BLOCK_FMEND; code = SeekFile(info, 1); } else if (fmtype == FILE_EOD) { /* EOD mark */ *blockType = BLOCK_EOD; info->status |= BUTM_STATUS_EOD; code = SeekFile(info, 1); } } else if (ntohl(label->magic) == TAPE_MAGIC) { /* Read a tape label? */ *blockType = BLOCK_LABEL; code = SeekFile(info, 1); } if (isafile) info->position++; error_exit: return (code); } /* check * check version numbers and permissions in the info structure */ static afs_int32 check(struct butm_tapeInfo *info, int write) /* write operation requested */ { struct progress *p; if (!info) return (BUTM_BADARGUMENT); /* Check version number in info structure */ if (info->structVersion != BUTM_MAJORVERSION) return BUTM_OLDINTERFACE; /* Check if a tape is mounted */ if (((p = (struct progress *)info->tmRock) == 0) || (p->fid == 0)) return BUTM_NOMOUNT; /* If writing check if there is write access */ if (write && (info->flags & BUTM_FLAGS_READONLY)) return BUTM_READONLY; return 0; } static afs_int32 rewindFile(struct butm_tapeInfo *info) { struct progress *p; afs_int32 code = 0; afs_int32 error; p = (struct progress *)info->tmRock; POLL(); error = Rewind(p->fid); POLL(); if (error) { info->status |= BUTM_STATUS_SEEKERROR; ERROR_EXIT(BUTM_IOCTL); } info->position = (isafile ? 0 : 1); info->kBytes = info->nBytes = 0; info->nFiles = info->nRecords = 0; p->reading = p->writing = 0; error_exit: if (error) info->error = error; return (code); } /* ===================================================================== * butm routines * ===================================================================== */ static afs_int32 file_Mount(struct butm_tapeInfo *info, char *tape) { struct progress *p; char filename[64]; usd_handle_t fid; int xflags; afs_int32 code = 0, error = 0, rc = 0; if (!info || !tape) ERROR_EXIT(BUTM_BADARGUMENT); if (info->debug) printf("butm: Mount tape drive\n"); POLL(); info->error = 0; if (info->structVersion != BUTM_MAJORVERSION) ERROR_EXIT(BUTM_OLDINTERFACE); if (info->tmRock) ERROR_EXIT(BUTM_PARALLELMOUNTS); if (strlen(tape) >= sizeof(info->name)) ERROR_EXIT(BUTM_BADARGUMENT); strcpy(info->name, tape); strcpy(filename, config.tapedir); /* the name of the tape device */ info->position = (isafile ? 0 : 1); info->kBytes = info->nBytes = 0; info->nRecords = info->nFiles = 0; info->recordSize = 0; info->tapeSize = config.tapeSize; info->coefBytes = 1; info->coefRecords = 0; info->coefFiles = sizeof(struct fileMark); info->simultaneousTapes = 1; info->status = 0; info->error = 0; info->flags = BUTM_FLAGS_SEQUENTIAL; xflags = 0; if (isafile) { xflags |= USD_OPEN_CREATE; } else { /* * try to open in a child process first so nothing will * time out should the process block because the device * isn't ready. */ if (ForkOpen(filename)) { ERROR_EXIT(BUTM_MOUNTFAIL); } } /* Now go ahead and open the tape drive for real */ rc = usd_Open(filename, (USD_OPEN_RDWR | USD_OPEN_WLOCK | xflags), 0777, &fid); if (rc != 0) { /* try for lesser access */ rc = usd_Open(filename, (USD_OPEN_RDONLY | USD_OPEN_RLOCK), 0, &fid); if (rc) { error = rc; ERROR_EXIT(BUTM_MOUNTFAIL); } info->flags |= BUTM_FLAGS_READONLY; } (void)PrepareAccess(fid); /* for NT */ p = malloc(sizeof(*p)); info->tmRock = (char *)p; p->fid = fid; p->mountId = config.mountId = time(0); p->reading = p->writing = 0; TapeBlockSize = BUTM_BLOCKSIZE; /* Initialize */ error_exit: if (error) info->error = error; return (code); } static afs_int32 file_Dismount(struct butm_tapeInfo *info) { struct progress *p; afs_int32 code = 0, error = 0; if (info->debug) printf("butm: Unmount tape drive\n"); POLL(); info->error = 0; code = check(info, READ_OP); if (code) ERROR_EXIT(code); p = (struct progress *)info->tmRock; (void)ShutdownAccess(p->fid); /* for NT */ /* close the device */ if ((error = ForkClose(p->fid))) { printf("butm: Tape close failed. Error %d\n", errno); } POLL(); if (error) { code = BUTM_DISMOUNTFAIL; info->status |= BUTM_STATUS_TAPEERROR; } config.mountId = 0; info->tmRock = 0; /* mark it as closed - even if error on close */ if (p) free(p); error_exit: if (error) info->error = error; return (code); } /* file_WriteLabel * write the header on a tape * entry: * info - handle on tape unit * label - label information. This label is not copied onto the tape. * If supplied, various fields are copied from this label to * the actual tape label written on the tape. */ static afs_int32 file_WriteLabel(struct butm_tapeInfo *info, struct butm_tapeLabel *label, afs_int32 rewind) { afs_int32 code = 0; afs_int32 fcode; struct tapeLabel *tlabel; struct progress *p; afs_int64 off; /* offset */ if (info->debug) printf("butm: Write tape label\n"); POLL(); info->error = 0; code = check(info, WRITE_OP); if (code) ERROR_EXIT(code); if (!label) ERROR_EXIT(BUTM_BADARGUMENT); if (label->structVersion != CUR_TAPE_VERSION) ERROR_EXIT(BUTM_OLDINTERFACE); if (rewind) { /* Not appending, so rewind */ code = rewindFile(info); if (code) ERROR_EXIT(code); if (isafile) { p = (struct progress *)info->tmRock; off = 0; code = USD_IOCTL(p->fid, USD_IOCTL_SETSIZE, &off); if (code) ERROR_EXIT(BUTM_POSITION); } } else { if (READS || WRITES) ERROR_EXIT(BUTM_BADOP); } /* Copy the label into the tape block * ---------------------------------- */ memset(tapeBlock, 0, BUTM_BLOCKSIZE); if (!label->creationTime) label->creationTime = time(0); tlabel = (struct tapeLabel *)tapeBlock; memcpy(&tlabel->label, label, sizeof(struct butm_tapeLabel)); tlabel->label.structVersion = htonl(CUR_TAPE_VERSION); tlabel->label.creationTime = htonl(tlabel->label.creationTime); tlabel->label.expirationDate = htonl(tlabel->label.expirationDate); tlabel->label.size = htonl(tlabel->label.size); tlabel->label.useCount = htonl(tlabel->label.useCount); tlabel->label.dumpid = htonl(tlabel->label.dumpid); /* * write the tape label - For appends, the write may need to skip * over 1 or 2 EOF marks that were written when tape was closed after * the last dump. Plus, some AIX tape drives require we try forwarding * over the last EOF and take an error before we can write the new label. * ---------------------------------------------------------------------- */ code = WriteTapeBlock(info, tapeBlock, BUTM_BLOCKSIZE, BLOCK_LABEL); if (!isafile && !rewind && (code == BUTM_IO)) do { /* do if write failed */ fcode = SeekFile(info, 1); /* skip over the EOF */ if (fcode) break; /* leave if error */ code = WriteTapeBlock(info, tapeBlock, BUTM_BLOCKSIZE, BLOCK_LABEL); if (code != BUTM_IO) break; /* continue if write failed */ fcode = SeekFile(info, 1); /* skip over the EOF */ if (fcode) { /* retry 1 write if couldn't skip */ code = WriteTapeBlock(info, tapeBlock, BUTM_BLOCKSIZE, BLOCK_LABEL); break; } code = WriteTapeBlock(info, tapeBlock, BUTM_BLOCKSIZE, BLOCK_LABEL); if (code != BUTM_IO) break; /* continue if write failed */ fcode = SeekFile(info, 1); /* skip over the EOF */ if (fcode) { /* retry 1 write if couldn't skip */ code = WriteTapeBlock(info, tapeBlock, BUTM_BLOCKSIZE, BLOCK_LABEL); break; } break; } while (0); /* clear the write error status a failed WriteTapeBlock may have produced */ if (!code) info->status &= ~BUTM_STATUS_WRITEERROR; error_exit: return code; } static afs_int32 file_ReadLabel(struct butm_tapeInfo *info, struct butm_tapeLabel *label, afs_int32 rewind) { struct tapeLabel *tlabel; afs_int32 code = 0; afs_int32 blockType; if (info->debug) printf("butm: Read tape label\n"); POLL(); info->error = 0; code = check(info, READ_OP); if (code) ERROR_EXIT(code); if (READS || WRITES) ERROR_EXIT(BUTM_BADOP); if (rewind) { code = rewindFile(info); if (code) ERROR_EXIT(code); /* status is set so return */ } /* * When appended labels were written, either 1 or 2 EOF marks may * have had to be skipped. When reading a label, these EOF marks * must also be skipped. When an EOF is read, 0 bytes are returned * (refer to the write calls in file_WriteLabel routine). * ---------------------------------------------------------------- */ code = ReadTapeBlock(info, tapeBlock, &blockType); if (!isafile && !rewind && (blockType == BLOCK_EOF)) do { code = ReadTapeBlock(info, tapeBlock, &blockType); if (blockType != BLOCK_EOF) break; /* didn't read an EOF */ code = ReadTapeBlock(info, tapeBlock, &blockType); } while (0); if (blockType == BLOCK_UNKNOWN) ERROR_EXIT(BUTM_NOLABEL); if (code) ERROR_EXIT(code); if (blockType != BLOCK_LABEL) ERROR_EXIT(BUTM_BADBLOCK); /* Copy label out * -------------- */ if (label) { tlabel = (struct tapeLabel *)tapeBlock; memcpy(label, &tlabel->label, sizeof(struct butm_tapeLabel)); label->structVersion = ntohl(label->structVersion); label->creationTime = ntohl(label->creationTime); label->expirationDate = ntohl(label->expirationDate); label->size = ntohl(label->size); label->dumpid = ntohl(label->dumpid); label->useCount = ntohl(label->useCount); info->tapeSize = label->size; /* use size from label */ } error_exit: return (code); } static afs_int32 file_WriteFileBegin(struct butm_tapeInfo *info) { afs_int32 code = 0; if (info->debug) printf("butm: Write filemark begin\n"); POLL(); code = check(info, WRITE_OP); if (code) ERROR_EXIT(code); code = WriteTapeBlock(info, tapeBlock, BUTM_BLOCKSIZE, BLOCK_FMBEGIN); if (code) ERROR_EXIT(code); info->nFiles++; error_exit: return (code); } static afs_int32 file_ReadFileBegin(struct butm_tapeInfo *info) { afs_int32 code = 0; afs_int32 blockType; if (info->debug) printf("butm: Read filemark begin\n"); POLL(); info->error = 0; code = check(info, READ_OP); if (code) ERROR_EXIT(code); if (READS || WRITES) ERROR_EXIT(BUTM_BADOP); code = ReadTapeBlock(info, tapeBlock, &blockType); if (code) ERROR_EXIT(code); if (blockType != BLOCK_FMBEGIN) { if (blockType == BLOCK_EOD) ERROR_EXIT(BUTM_EOD); /* EODump label */ if (blockType == BLOCK_LABEL) ERROR_EXIT(BUTM_LABEL); /* Tape label */ ERROR_EXIT(BUTM_BADBLOCK); /* Other */ } error_exit: return (code); } /* Writes data out in block sizes of 16KB. Does destroy the data. * Assumes the data buffer has a space reserved at beginning for a blockMark. */ static afs_int32 file_WriteFileData(struct butm_tapeInfo *info, char *data, afs_int32 blocks, afs_int32 len) { afs_int32 code = 0; int length; afs_int32 b; char *bstart; /* Where block starts for a 16K block */ char *dstart; /* Where data starts for a 16K block */ if (info->debug) printf("butm: Write tape data - %u bytes\n", len); POLL(); info->error = 0; code = check(info, WRITE_OP); if (code) ERROR_EXIT(code); if (!data || (len < 0)) ERROR_EXIT(BUTM_BADARGUMENT); if (READS || !WRITES) ERROR_EXIT(BUTM_BADOP); b = 0; /* start at block 0 */ while (len > 0) { dstart = &data[b * BUTM_BLKSIZE]; bstart = dstart - sizeof(struct blockMark); if (len < BUTM_BLKSIZE) { memset(&dstart[len], 0, BUTM_BLKSIZE - len); length = len; } else { length = BUTM_BLKSIZE; } code = WriteTapeBlock(info, bstart, length, BLOCK_DATA); len -= length; /* If there are more blocks, step to next block */ /* Otherwise, copy the data to beginning of last block */ if (b < (blocks - 1)) b++; else if (len) memcpy(&dstart[0], &dstart[BUTM_BLKSIZE], len); } error_exit: return (code); } /* file_ReadFileData * Read a data block from tape. * entry: * info - tape info structure, c.f. fid * data - ptr to buffer for data * len - size of data buffer * exit: * nBytes - no. of data bytes read. */ static afs_int32 file_ReadFileData(struct butm_tapeInfo *info, char *data, int len, int *nBytes) { struct blockMark *bmark; afs_int32 code = 0; afs_int32 blockType; if (info->debug) printf("butm: Read tape data - %u bytes\n", len); POLL(); info->error = 0; if (!nBytes) ERROR_EXIT(BUTM_BADARGUMENT); *nBytes = 0; code = check(info, READ_OP); if (code) ERROR_EXIT(code); if (!data || (len < 0) || (len > BUTM_BLKSIZE)) ERROR_EXIT(BUTM_BADARGUMENT); if (!READS || WRITES) ERROR_EXIT(BUTM_BADOP); data -= sizeof(struct blockMark); code = ReadTapeBlock(info, data, &blockType); if (code) ERROR_EXIT(code); if (blockType != BLOCK_DATA) { if (blockType == BLOCK_EOF) ERROR_EXIT(BUTM_EOF); if (blockType == BLOCK_FMEND) ERROR_EXIT(BUTM_ENDVOLUME); ERROR_EXIT(BUTM_BADBLOCK); } bmark = (struct blockMark *)data; *nBytes = ntohl(bmark->count); /* Size of data in buf */ error_exit: return (code); } static afs_int32 file_WriteFileEnd(struct butm_tapeInfo *info) { afs_int32 code = 0; if (info->debug) printf("butm: Write filemark end\n"); POLL(); info->error = 0; code = check(info, WRITE_OP); if (code) ERROR_EXIT(code); if (READS || !WRITES) ERROR_EXIT(BUTM_BADOP); code = WriteTapeBlock(info, tapeBlock, BUTM_BLOCKSIZE, BLOCK_FMEND); error_exit: return (code); } /* Read a SW filemark, verify that it is a SW filemark, then skip to the next * HW filemark. If the read of the SW filemark shows it's an EOF, then * ignore that the SW filemark is not there and return 0 (found the SW filemark * missing with some 3.1 dumps). */ static afs_int32 file_ReadFileEnd(struct butm_tapeInfo *info) { afs_int32 code = 0; afs_int32 blockType; if (info->debug) printf("butm: Read filemark end\n"); POLL(); info->error = 0; code = check(info, READ_OP); if (code) ERROR_EXIT(code); if (!READS || WRITES) ERROR_EXIT(BUTM_BADOP); info->status &= ~BUTM_STATUS_EOF; code = ReadTapeBlock(info, tapeBlock, &blockType); if (code) ERROR_EXIT(code); if ((blockType != BLOCK_FMEND) && (blockType != BLOCK_EOF)) ERROR_EXIT(BUTM_BADBLOCK); error_exit: return code; } /* * Write the end-of-dump marker. */ static afs_int32 file_WriteEODump(struct butm_tapeInfo *info) { afs_int32 code = 0; if (info->debug) printf("butm: Write filemark EOD\n"); POLL(); info->error = 0; code = check(info, WRITE_OP); if (code) ERROR_EXIT(code); if (READS || WRITES) ERROR_EXIT(BUTM_BADOP); code = WriteTapeBlock(info, tapeBlock, BUTM_BLOCKSIZE, BLOCK_EOD); if (code) ERROR_EXIT(code); info->status |= BUTM_STATUS_EOD; error_exit: return (code); } static afs_int32 file_Seek(struct butm_tapeInfo *info, afs_int32 position) { afs_int32 code = 0; afs_int32 w; osi_lloff_t posit; struct progress *p; afs_int64 stopOff; /* for normal file(non-tape) seeks */ if (info->debug) printf("butm: Seek to the tape position %d\n", position); info->error = 0; code = check(info, READ_OP); if (code) ERROR_EXIT(code); if (isafile) { p = (struct progress *)info->tmRock; posit = (osi_lloff_t) position *(osi_lloff_t) BUTM_BLOCKSIZE; w = USD_SEEK(p->fid, posit, SEEK_SET, &stopOff); if (w) info->error = w; if (posit != stopOff) ERROR_EXIT(BUTM_POSITION); p->reading = p->writing = 0; info->position = position; } else { /* Don't position backwards if we are in-between FMs */ if ((READS || WRITES) && ((position - info->position) <= 0)) ERROR_EXIT(BUTM_BADOP); code = SeekFile(info, (position - info->position)); if (code) ERROR_EXIT(code); } error_exit: return (code); } /* * Seek to the EODump (end-of-dump) after the given position. This is * the position after the EOF filemark immediately after the EODump mark. * This is for tapes of version 4 or greater. */ static afs_int32 file_SeekEODump(struct butm_tapeInfo *info, afs_int32 position) { afs_int32 code = 0; afs_int32 blockType; afs_int32 w; struct progress *p; afs_int64 stopOff; /* file seek offsets */ if (info->debug) printf("butm: Seek to end-of-dump\n"); info->error = 0; code = check(info, READ_OP); if (code) ERROR_EXIT(code); if (READS || WRITES) ERROR_EXIT(BUTM_BADOP); if (isafile) { p = (struct progress *)info->tmRock; w = USD_SEEK(p->fid, 0, SEEK_END, &stopOff); if (w) { info->error = w; ERROR_EXIT(BUTM_POSITION); } if (stopOff % BUTM_BLOCKSIZE) ERROR_EXIT(BUTM_POSITION); info->position = (stopOff / BUTM_BLOCKSIZE); } else { /* Seek to the desired position */ code = SeekFile(info, (position - info->position) + 1); if (code) ERROR_EXIT(code); /* * Search until the filemark is an EODump filemark. * Skip over volumes only. */ while (1) { code = ReadTapeBlock(info, tapeBlock, &blockType); if (code) ERROR_EXIT(code); if (blockType == BLOCK_EOD) break; if (blockType != BLOCK_FMBEGIN) ERROR_EXIT(BUTM_BADBLOCK); code = SeekFile(info, 1); /* Step forward to next volume */ if (code) ERROR_EXIT(code); } code = 0; } error_exit: return (code); } static afs_int32 file_SetSize(struct butm_tapeInfo *info, afs_uint32 size) { if (info->debug) printf("butm: Set size of tape\n"); info->error = 0; if (size <= 0) info->tapeSize = config.tapeSize; else info->tapeSize = size; return 0; } static afs_int32 file_GetSize(struct butm_tapeInfo *info, afs_uint32 *size) { if (info->debug) printf("butm: Get size of tape\n"); info->error = 0; *size = info->tapeSize; return 0; } /* ===================================================================== * Startup/configuration routines. * ===================================================================== */ static afs_int32 file_Configure(struct tapeConfig *file) { if (!file) { afs_com_err(whoami, BUTM_BADCONFIG, "device not specified"); return BUTM_BADCONFIG; } config.tapeSize = file->capacity; config.fileMarkSize = file->fileMarkSize; config.portOffset = file->portOffset; strcpy(config.tapedir, file->device); /* Tape must be large enough to at least fit a label */ if (config.tapeSize <= 0) { afs_com_err(whoami, BUTM_BADCONFIG, "Tape size bogus: %d Kbytes", config.tapeSize); return BUTM_BADCONFIG; } if (strlen(config.tapedir) == 0) { afs_com_err(whoami, BUTM_BADCONFIG, "no tape device specified"); return BUTM_BADCONFIG; } config.mountId = 0; return 0; } /* This procedure instantiates a tape module of type file_tm. */ afs_int32 butm_file_Instantiate(struct butm_tapeInfo *info, struct tapeConfig *file) { extern int debugLevel; afs_int32 code = 0; if (debugLevel > 98) printf("butm: Instantiate butc\n"); if (!info) ERROR_EXIT(BUTM_BADARGUMENT); if (info->structVersion != BUTM_MAJORVERSION) ERROR_EXIT(BUTM_OLDINTERFACE); memset(info, 0, sizeof(struct butm_tapeInfo)); info->structVersion = BUTM_MAJORVERSION; info->ops.mount = file_Mount; info->ops.dismount = file_Dismount; info->ops.create = file_WriteLabel; info->ops.readLabel = file_ReadLabel; info->ops.seek = file_Seek; info->ops.seekEODump = file_SeekEODump; info->ops.readFileBegin = file_ReadFileBegin; info->ops.readFileData = file_ReadFileData; info->ops.readFileEnd = file_ReadFileEnd; info->ops.writeFileBegin = file_WriteFileBegin; info->ops.writeFileData = file_WriteFileData; info->ops.writeFileEnd = file_WriteFileEnd; info->ops.writeEOT = file_WriteEODump; info->ops.setSize = file_SetSize; info->ops.getSize = file_GetSize; info->debug = ((debugLevel > 98) ? 1 : 0); code = file_Configure(file); error_exit: return code; }