| /* |
| * Copyright (c) 2000-2001 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it would be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <sys/param.h> |
| #include <sys/ipc.h> |
| #include <sys/sem.h> |
| #include <sys/prctl.h> |
| #include <time.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <sys/sysmacros.h> |
| #include <malloc.h> |
| #include <sched.h> |
| #include <assert.h> |
| #include <string.h> |
| #include <uuid/uuid.h> |
| |
| #include "config.h" |
| |
| #include "types.h" |
| #include "qlock.h" |
| #include "cldmgr.h" |
| #include "mlog.h" |
| #include "dlog.h" |
| #include "global.h" |
| #include "drive.h" |
| #include "media.h" |
| #include "getopt.h" |
| #include "stream.h" |
| #include "ring.h" |
| #include "rec_hdr.h" |
| #include "arch_xlate.h" |
| #include "ts_mtio.h" |
| |
| |
| /* drive_minrmt.c - drive strategy for non-SGI rmt tape devices |
| * This strategy is derived from the scsitape strategy. It is designed |
| * so that tapes written using this strategy can be read with the |
| * scsitape strategy and vice versa. |
| */ |
| |
| /* structure definitions used locally ****************************************/ |
| |
| /* remote tape protocol debug |
| */ |
| #ifdef RMTDBG |
| #define open(p, f) dbgrmtopen(p, f) |
| #define close(fd) dbgrmtclose(fd) |
| #define ioctl(fd, op, arg) dbgrmtioctl(fd, op, arg) |
| #define read(fd, p, sz) dbgrmtread(fd, p, sz) |
| #define write(fd, p, sz) dbgrmtwrite(fd, p, sz) |
| #else /* RMTDBG */ |
| #define open rmtopen |
| #define close rmtclose |
| #define ioctl rmtioctl |
| #define read rmtread |
| #define write rmtwrite |
| #endif /* RMTDBG */ |
| |
| /* if the media file header structure changes, this number must be |
| * bumped, and STAPE_VERSION_1 must be defined and recognized. |
| */ |
| #define STAPE_VERSION 1 |
| |
| /* a bizarre number to help reduce the odds of mistaking random data |
| * for a media file or record header |
| */ |
| #define STAPE_MAGIC 0x13579bdf02468acell |
| |
| /* this much of each record is reserved for header info: the user |
| * data always begins at this offset from the beginning of each |
| * record. be sure global_hdr_t fits. |
| */ |
| #define STAPE_HDR_SZ PGSZ |
| |
| /* maximum tape record size. this is the max size of I/O buffers sent to drive. |
| * note that for variable block devices this determines the block size as well. |
| */ |
| #define STAPE_MAX_RECSZ 0x200000 /* 2M */ |
| |
| /* this is the smallest maximum block size for any tape device |
| * supported by xfsdump/xfsrestore. we use this when it is not possible |
| * to ask the driver for block size info. |
| */ |
| #define STAPE_MIN_MAX_BLKSZ 0x3c000 /* 240K, 245760 */ |
| |
| /* QIC tapes always use 512 byte blocks |
| */ |
| #define QIC_BLKSZ 512 |
| |
| /* number of record buffers in the I/O ring |
| */ |
| #define RINGLEN_MIN 1 |
| #define RINGLEN_MAX 10 |
| #define RINGLEN_DEFAULT 3 |
| |
| /* tape i/o request retry limit |
| */ |
| #define MTOP_TRIES_MAX 10 |
| |
| /* operational mode. can be reading or writing, but not both |
| */ |
| typedef enum { OM_NONE, OM_READ, OM_WRITE } om_t; |
| |
| /* drive_context - control state |
| * |
| * NOTE: ring used only if not singlethreaded |
| */ |
| struct drive_context { |
| om_t dc_mode; |
| /* current mode of operation (READ or WRITE) |
| */ |
| size_t dc_ringlen; |
| /* number of tape_recsz buffers in ring. only used |
| * for displaying ring info |
| */ |
| bool_t dc_ringpinnedpr; |
| /* are the ring buffers pinned down |
| */ |
| ring_t *dc_ringp; |
| /* handle to ring |
| */ |
| ring_msg_t *dc_msgp; |
| /* currently held ring message |
| */ |
| char *dc_bufp; |
| /* pre-allocated record buffer (only if ring not used) |
| */ |
| char *dc_recp; |
| /* pointer to current record buffer. once the current |
| * record is completely read or written by client, |
| * set to NULL. |
| */ |
| char *dc_recendp; |
| /* always set to point to just off the end of the |
| * current record buffer pointed to by dc_recp. valid |
| * only when dc_recp non-NULL. |
| */ |
| char *dc_dataendp; |
| /* same as dc_recendp, except for first and last |
| * records in media file. the first record is all |
| * pads after the header page. the last record may |
| * have been padded (as indicated by the rec_used |
| * field of the record header). in either case |
| * dc_dataendp points to first padding byte. |
| */ |
| char *dc_ownedp; |
| /* first byte in current buffer owned by caller. |
| * given to caller by do_read or do_get_write_buf |
| * set to null by do_return_read_buf or do_write. |
| */ |
| char *dc_nextp; |
| /* next byte available in current buffer to give |
| * to do_get_write_buf for writing or do_read for |
| * reading. |
| */ |
| off64_t dc_reccnt; |
| /* count of the number of records completely read or |
| * written by client, and therefore not represented |
| * by current dc_recp. valid initially and after |
| * each call to do_return_read_buf or do_write. |
| * NOT valid after a call to do_read or |
| * do_get_write_buf. always bumped regardless of |
| * read or write error status. |
| */ |
| off64_t dc_iocnt; |
| /* count of the number of records read or written |
| * to media without error. includes media file header |
| * record. this is incremented when the actual I/O is |
| * done. dc_reccnt is different, indicating what has |
| * been seen by client. slave may have read ahead / |
| * written behind. |
| */ |
| int dc_fd; |
| /* drive file descriptor. -1 when not open |
| */ |
| bool_t dc_isQICpr; |
| /* fixed 512 byte block size device. |
| */ |
| bool_t dc_canfsrpr; |
| /* can seek forward records at a time |
| */ |
| size_t dc_blksz; |
| /* actual tape blksize selected |
| */ |
| size_t dc_recsz; |
| /* actual tape record size selected |
| */ |
| off64_t dc_lostrecmax; |
| /* maximum number of records written without error |
| * which may be lost due to a near end-of-tape |
| * experience. a function of drive type and |
| * compression |
| */ |
| bool_t dc_singlethreadedpr; |
| /* single-threaded operation (no slave) |
| */ |
| bool_t dc_errorpr; |
| /* TRUE if error encountered during reading or writing. |
| * used to detect attempts to read or write after |
| * error reported. |
| */ |
| bool_t dc_recchksumpr; |
| /* TRUE if records should be checksumed |
| */ |
| bool_t dc_unloadokpr; |
| /* ok to issue unload command when do_eject invoked. |
| */ |
| bool_t dc_overwritepr; |
| /* overwrite tape without checking whats on it |
| */ |
| off64_t dc_filesz; |
| /* media file size given as argument |
| */ |
| }; |
| |
| typedef struct drive_context drive_context_t; |
| |
| /* macros for shortcut references to context. assumes a local variable named |
| * 'contextp'. |
| */ |
| #define tape_recsz (contextp->dc_recsz) |
| #define tape_blksz (contextp->dc_blksz) |
| |
| /* declarations of externally defined global variables ***********************/ |
| |
| extern void usage(void); |
| #ifdef DUMP |
| extern uint64_t hdr_mfilesz; |
| #endif /* DUMP */ |
| |
| /* remote tape protocol declarations (should be a system header file) |
| */ |
| extern int rmtopen(char *, int, ...); |
| extern int rmtclose(int); |
| extern int rmtfstat(int, struct stat *); |
| extern int rmtioctl(int, int, ...); |
| extern int rmtread(int, void*, uint); |
| extern int rmtwrite(int, const void *, uint); |
| |
| |
| /* forward declarations of locally defined static functions ******************/ |
| |
| /* strategy functions |
| */ |
| static int ds_match(int, char *[], drive_t *); |
| static int ds_instantiate(int, char *[], drive_t *); |
| |
| /* manager operations |
| */ |
| static bool_t do_init(drive_t *); |
| static bool_t do_sync(drive_t *); |
| static int do_begin_read(drive_t *); |
| static char *do_read(drive_t *, size_t, size_t *, int *); |
| static void do_return_read_buf(drive_t *, char *, size_t); |
| static void do_get_mark(drive_t *, drive_mark_t *); |
| static int do_seek_mark(drive_t *, drive_mark_t *); |
| static int do_next_mark(drive_t *); |
| static void do_get_mark(drive_t *, drive_mark_t *); |
| static void do_end_read(drive_t *); |
| static int do_begin_write(drive_t *); |
| static void do_set_mark(drive_t *, drive_mcbfp_t, void *, drive_markrec_t *); |
| static char *do_get_write_buf(drive_t *, size_t, size_t *); |
| static int do_write(drive_t *, char *, size_t); |
| static size_t do_get_align_cnt(drive_t *); |
| static int do_end_write(drive_t *, off64_t *); |
| static int do_fsf(drive_t *, int, int *); |
| static int do_bsf(drive_t *, int, int *); |
| static int do_rewind(drive_t *); |
| static int do_erase(drive_t *); |
| static int do_eject_media(drive_t *); |
| static int do_get_device_class(drive_t *); |
| static void do_display_metrics(drive_t *drivep); |
| static void do_quit(drive_t *); |
| |
| /* misc. local utility funcs |
| */ |
| static int mt_op(int, int, int); |
| static int determine_write_error(int, int); |
| static int read_label(drive_t *); |
| static bool_t tape_rec_checksum_check(drive_context_t *, char *); |
| static void set_recommended_sizes(drive_t *); |
| static void display_access_failed_message(drive_t *); |
| static bool_t get_tpcaps(drive_t *); |
| static int prepare_drive(drive_t *drivep); |
| static bool_t Open(drive_t *drivep); |
| static void Close(drive_t *drivep); |
| static int Read(drive_t *drivep, |
| char *bufp, |
| size_t cnt, |
| int *errnop); |
| static int Write(drive_t *drivep, |
| char *bufp, |
| size_t cnt, |
| int *saved_errnop); |
| static int record_hdr_validate(drive_t *drivep, |
| char *bufp, |
| bool_t chkoffpr); |
| static int ring_read(void *clientctxp, char *bufp); |
| static int ring_write(void *clientctxp, char *bufp); |
| static double percent64(off64_t num, off64_t denom); |
| static int getrec(drive_t *drivep); |
| static int write_record(drive_t *drivep, char *bufp, bool_t chksumpr, |
| bool_t xlatepr); |
| static ring_msg_t *Ring_get(ring_t *ringp); |
| static void Ring_reset(ring_t *ringp, ring_msg_t *msgp); |
| static void Ring_put(ring_t *ringp, ring_msg_t *msgp); |
| static int validate_media_file_hdr(drive_t *drivep); |
| static void calc_max_lost(drive_t *drivep); |
| static void display_ring_metrics(drive_t *drivep, int mlog_flags); |
| #ifdef CLRMTAUD |
| static uint32_t rewind_and_verify(drive_t *drivep); |
| static uint32_t erase_and_verify(drive_t *drivep); |
| static uint32_t bsf_and_verify(drive_t *drivep); |
| static uint32_t fsf_and_verify(drive_t *drivep); |
| #else /* CLRMTAUD */ |
| static short rewind_and_verify(drive_t *drivep); |
| static short erase_and_verify(drive_t *drivep); |
| static short bsf_and_verify(drive_t *drivep); |
| static short fsf_and_verify(drive_t *drivep); |
| #endif /* CLRMTAUD */ |
| static bool_t set_best_blk_and_rec_sz(drive_t *drivep); |
| static bool_t isefsdump(drive_t *drivep); |
| static bool_t isxfsdumperasetape(drive_t *drivep); |
| |
| /* RMT trace stubs |
| */ |
| #ifdef RMTDBG |
| static int dbgrmtopen(char *, int); |
| static int dbgrmtclose(int); |
| static int dbgrmtioctl(int, int, void *); |
| static int dbgrmtread(int, void *, uint); |
| static int dbgrmtwrite(int, void *, uint); |
| #endif /* RMTDBG */ |
| |
| #define ERASE_MAGIC "$^*@++! This tape was quick erased by SGI xfsdump $^*@++!" |
| |
| /* definition of locally defined global variables ****************************/ |
| |
| /* rmt drive strategy. referenced by drive.c |
| */ |
| drive_strategy_t drive_strategy_rmt = { |
| DRIVE_STRATEGY_RMT, /* ds_id */ |
| "minimum scsi tape (drive_minrmt)", /* ds_description */ |
| ds_match, /* ds_match */ |
| ds_instantiate, /* ds_instantiate */ |
| 0x1000000ll, /* ds_recmarksep 16 MB */ |
| OFF64MAX, /* ds_recmfilesz */ |
| }; |
| |
| |
| /* definition of locally defined static variables *****************************/ |
| |
| /* drive operators |
| */ |
| static drive_ops_t drive_ops = { |
| do_init, /* do_init */ |
| do_sync, /* do_sync */ |
| do_begin_read, /* do_begin_read */ |
| do_read, /* do_read */ |
| do_return_read_buf, /* do_return_read_buf */ |
| do_get_mark, /* do_get_mark */ |
| do_seek_mark, /* do_seek_mark */ |
| do_next_mark, /* do_next_mark */ |
| do_end_read, /* do_end_read */ |
| do_begin_write, /* do_begin_write */ |
| do_set_mark, /* do_set_mark */ |
| do_get_write_buf, /* do_get_write_buf */ |
| do_write, /* do_write */ |
| do_get_align_cnt, /* do_get_align_cnt */ |
| do_end_write, /* do_end_write */ |
| do_fsf, /* do_fsf */ |
| do_bsf, /* do_bsf */ |
| do_rewind, /* do_rewind */ |
| do_erase, /* do_erase */ |
| do_eject_media, /* do_eject_media */ |
| do_get_device_class, /* do_get_device_class */ |
| do_display_metrics, /* do_display_metrics */ |
| do_quit, /* do_quit */ |
| }; |
| |
| static uint32_t cmdlineblksize = 0; |
| |
| /* definition of locally defined global functions ****************************/ |
| |
| |
| /* definition of locally defined static functions ****************************/ |
| |
| /* strategy match - determines if this is the right strategy |
| */ |
| /* ARGSUSED */ |
| static int |
| ds_match(int argc, char *argv[], drive_t *drivep) |
| { |
| int fd; |
| int c; |
| bool_t minrmt = BOOL_FALSE; |
| |
| /* heuristics to determine if this is a drive. |
| */ |
| |
| if (!strcmp(drivep->d_pathname, "stdio")) { |
| return -10; |
| } |
| |
| /* Check if the min rmt flag and block size have |
| * been specified. |
| * If so, this is a non-SGI drive and this is the right |
| * strategy. |
| */ |
| { |
| optind = 1; |
| opterr = 0; |
| while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) { |
| switch (c) { |
| case GETOPT_BLOCKSIZE: |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("-%c argument missing\n"), |
| c); |
| return -10; |
| } |
| cmdlineblksize = (uint32_t)atoi(optarg); |
| errno = 0; |
| fd = open(drivep->d_pathname, O_RDONLY); |
| if (fd < 0) |
| return -10; |
| close(fd); |
| break; |
| case GETOPT_MINRMT: |
| minrmt = BOOL_TRUE; |
| break; |
| } |
| } |
| |
| if (minrmt == BOOL_TRUE) { |
| if (cmdlineblksize != 0) |
| return 20; |
| else |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("Minimal rmt cannot be used without specifying blocksize. Use -%c\n"), |
| GETOPT_BLOCKSIZE); |
| } |
| } |
| /* catch all */ |
| return -10; |
| } |
| |
| /* strategy instantiate - initializes the pre-allocated drive descriptor |
| */ |
| /*ARGSUSED*/ |
| static bool_t |
| ds_instantiate(int argc, char *argv[], drive_t *drivep) |
| { |
| drive_context_t *contextp; |
| int c; |
| |
| /* opportunity for sanity checking |
| */ |
| assert(sizeof(global_hdr_t) <= STAPE_HDR_SZ); |
| assert(sizeof(rec_hdr_t) |
| == |
| sizeofmember(drive_hdr_t, dh_specific)); |
| assert(!(STAPE_MAX_RECSZ % PGSZ)); |
| |
| /* hook up the drive ops |
| */ |
| drivep->d_opsp = &drive_ops; |
| |
| /* allocate context for the drive manager |
| */ |
| contextp = (drive_context_t *)calloc(1, sizeof(drive_context_t)); |
| assert(contextp); |
| memset((void *)contextp, 0, sizeof(*contextp)); |
| |
| /* do not enable a separate I/O thread, |
| * more testing to be done first... |
| */ |
| contextp->dc_singlethreadedpr = BOOL_TRUE; |
| |
| /* scan the command line for the I/O buffer ring length |
| * and record checksum request |
| */ |
| contextp->dc_ringlen = RINGLEN_DEFAULT; |
| contextp->dc_ringpinnedpr = BOOL_FALSE; |
| contextp->dc_recchksumpr = BOOL_FALSE; |
| contextp->dc_unloadokpr = BOOL_FALSE; |
| contextp->dc_filesz = 0; |
| contextp->dc_isQICpr = BOOL_FALSE; |
| optind = 1; |
| opterr = 0; |
| while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) { |
| switch (c) { |
| case GETOPT_RINGLEN: |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("-%c argument missing\n"), |
| c); |
| return BOOL_FALSE; |
| } |
| contextp->dc_ringlen = (size_t)atoi(optarg); |
| if (contextp->dc_ringlen < RINGLEN_MIN |
| || |
| contextp->dc_ringlen > RINGLEN_MAX) { |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, |
| _("-%c argument must be " |
| "between %u and %u: ignoring %u\n"), |
| c, |
| RINGLEN_MIN, |
| RINGLEN_MAX, |
| contextp->dc_ringlen); |
| return BOOL_FALSE; |
| } |
| break; |
| case GETOPT_RINGPIN: |
| contextp->dc_ringpinnedpr = BOOL_TRUE; |
| break; |
| case GETOPT_RECCHKSUM: |
| contextp->dc_recchksumpr = BOOL_TRUE; |
| break; |
| case GETOPT_UNLOAD: |
| contextp->dc_unloadokpr = BOOL_TRUE; |
| break; |
| case GETOPT_QIC: |
| contextp->dc_isQICpr = BOOL_TRUE; |
| break; |
| #ifdef DUMP |
| case GETOPT_OVERWRITE: |
| contextp->dc_overwritepr = BOOL_TRUE; |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| _("Overwrite command line option\n")); |
| break; |
| case GETOPT_FILESZ: |
| if (!optarg || optarg [0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("-%c argument missing\n"), |
| c); |
| return BOOL_FALSE; |
| } |
| contextp->dc_filesz = (off64_t)atoi(optarg) * 1024 * 1024; |
| if (contextp->dc_filesz <= 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("-%c argument must be a " |
| "positive number (MB): ignoring %u\n"), |
| c, |
| contextp->dc_filesz); |
| return BOOL_FALSE; |
| } |
| break; |
| #endif |
| } |
| } |
| |
| /* set drive file descriptor to null value |
| */ |
| contextp->dc_fd = -1; |
| |
| /* record location of context descriptor in drive descriptor |
| */ |
| drivep->d_contextp = (void *)contextp; |
| |
| /* indicate neither capacity nor rate estimates available |
| */ |
| drivep->d_cap_est = -1; |
| drivep->d_rate_est = -1; |
| |
| /* if threads not allowed, allocate a record buffer. otherwise |
| * create a ring, from which buffers will be taken. |
| */ |
| if (contextp->dc_singlethreadedpr) { |
| contextp->dc_bufp = (char *)memalign(PGSZ, STAPE_MAX_RECSZ); |
| assert(contextp->dc_bufp); |
| } else { |
| int rval; |
| mlog((MLOG_NITTY + 1) | MLOG_DRIVE, |
| "ring op: create: ringlen == %u\n", |
| contextp->dc_ringlen); |
| contextp->dc_ringp = ring_create(contextp->dc_ringlen, |
| STAPE_MAX_RECSZ, |
| contextp->dc_ringpinnedpr, |
| drivep->d_index, |
| ring_read, |
| ring_write, |
| (void *)drivep, |
| &rval); |
| if (!contextp->dc_ringp) { |
| if (rval == ENOMEM) { |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, |
| _("unable to allocate memory " |
| "for I/O buffer ring\n")); |
| } else if (rval == E2BIG) { |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, |
| _("not enough physical memory " |
| "to pin down I/O buffer ring\n")); |
| } else if (rval == EPERM) { |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, |
| _("not allowed " |
| "to pin down I/O buffer ring\n")); |
| } else { |
| assert(0); |
| } |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* several of contextp predicates cannot yet be determined. |
| * mark them as unknown for now. however, if this is an RMT |
| * access, we know immediately some capabilities are missing. |
| */ |
| |
| /* specify that we are currently neither reading nor writing |
| */ |
| contextp->dc_mode = OM_NONE; |
| |
| /* set the capabilities flags advertised in the drive_t d_capabilities |
| * field that we know a priori to be true. later additional flags |
| * may be set |
| */ |
| drivep->d_capabilities = 0 |
| | |
| DRIVE_CAP_BSF |
| | |
| DRIVE_CAP_FSF |
| | |
| DRIVE_CAP_REWIND |
| | |
| DRIVE_CAP_FILES |
| | |
| DRIVE_CAP_NEXTMARK |
| | |
| DRIVE_CAP_READ |
| | |
| DRIVE_CAP_REMOVABLE |
| | |
| DRIVE_CAP_ERASE |
| | |
| DRIVE_CAP_EJECT |
| ; |
| |
| return BOOL_TRUE; |
| } |
| |
| /* drive op init - do more time-consuming init/checking here. read and |
| * write headers are available now. |
| * |
| * NOTE: |
| * When using a RMT device, the MTIOCGETBLKINFO, MTCAPABILITY and |
| * MTSPECOP ioctl calls are not supported. This means that we have |
| * to assume that the drive does not support the MTCAN_APPEND capability. |
| */ |
| /* ARGSUSED */ |
| static bool_t |
| do_init(drive_t *drivep) |
| { |
| #ifdef DUMP |
| drive_hdr_t *dwhdrp = drivep->d_writehdrp; |
| media_hdr_t *mwhdrp = (media_hdr_t *)dwhdrp->dh_upper; |
| #endif /* DUMP */ |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: init\n"); |
| |
| #ifdef DUMP |
| /* fill in media strategy id: artifact of first version of xfsdump |
| */ |
| mwhdrp->mh_strategyid = MEDIA_STRATEGY_RMVTAPE; |
| #endif /* DUMP */ |
| |
| return BOOL_TRUE; |
| } |
| |
| /* wait here for slave to complete initialization. |
| * set drive capabilities flags. NOTE: currently don't make use of this |
| * feature: drive initialization done whenever block/record sizes unknown. |
| */ |
| /* ARGSUSED */ |
| static bool_t |
| do_sync(drive_t *drivep) |
| { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: sync\n"); |
| |
| return BOOL_TRUE; |
| } |
| |
| /* begin_read |
| * Set up the tape device and read the media file header. |
| * if allowed, begin read-ahead. |
| * |
| * RETURNS: |
| * 0 on success |
| * DRIVE_ERROR_* on failure |
| * |
| */ |
| static int |
| do_begin_read(drive_t *drivep) |
| { |
| drive_context_t *contextp; |
| int rval; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: begin read\n"); |
| |
| /* get drive context |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| /* verify protocol being followed |
| */ |
| assert(drivep->d_capabilities & DRIVE_CAP_READ); |
| assert(contextp->dc_mode == OM_NONE); |
| assert(!contextp->dc_recp); |
| |
| /* get a record buffer to use during initialization. |
| */ |
| if (contextp->dc_singlethreadedpr) { |
| contextp->dc_recp = contextp->dc_bufp; |
| } else { |
| assert(contextp->dc_ringp); |
| contextp->dc_msgp = Ring_get(contextp->dc_ringp); |
| assert(contextp->dc_msgp->rm_stat == RING_STAT_INIT); |
| contextp->dc_recp = contextp->dc_msgp->rm_bufp; |
| } |
| |
| /* if the tape is not open, open, determine the record size, and |
| * read the first record. otherwise read a record using the record |
| * size previously determined. |
| */ |
| contextp->dc_iocnt = 0; |
| if (contextp->dc_fd < 0) { |
| assert(contextp->dc_fd == -1); |
| rval = prepare_drive(drivep); |
| if (rval) { |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_reset(contextp->dc_ringp, contextp->dc_msgp); |
| } |
| contextp->dc_msgp = 0; |
| contextp->dc_recp = 0; |
| return rval; |
| } |
| } else { |
| rval = read_label(drivep); |
| if (rval) { |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_reset(contextp->dc_ringp, contextp->dc_msgp); |
| } |
| contextp->dc_msgp = 0; |
| contextp->dc_recp = 0; |
| return rval; |
| } |
| } |
| assert(contextp->dc_iocnt == 1); |
| /* set by prepare_drive or read_label */ |
| |
| /* all is well. adjust context. don't kick off read-aheads just yet; |
| * the client may not want this media file. |
| */ |
| if (!contextp->dc_singlethreadedpr) { |
| contextp->dc_msgp->rm_op = RING_OP_NOP; |
| contextp->dc_msgp->rm_user = 0; /* do diff. use in do_seek */ |
| Ring_put(contextp->dc_ringp, contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| contextp->dc_recp = 0; |
| contextp->dc_recendp = 0; |
| contextp->dc_dataendp = 0; |
| contextp->dc_ownedp = 0; |
| contextp->dc_nextp = 0; |
| contextp->dc_reccnt = 1; |
| |
| /* used to detect attempt to read after an error was reported |
| */ |
| contextp->dc_errorpr = BOOL_FALSE; |
| |
| /* successfully entered read mode. must do end_read to get out. |
| */ |
| contextp->dc_mode = OM_READ; |
| |
| return 0; |
| } |
| |
| /* do_read |
| * Supply the caller with all or a portion of the current buffer, |
| * filled with data from a record. |
| * |
| * RETURNS: |
| * a pointer to a buffer containing "*actual_bufszp" bytes of data |
| * or 0 on failure with "*rvalp" containing the error (DRIVE_ERROR_...) |
| * |
| */ |
| static char * |
| do_read(drive_t *drivep, |
| size_t wantedcnt, |
| size_t *actualcntp, |
| int *rvalp) |
| { |
| drive_context_t *contextp; |
| size_t availcnt; |
| size_t actualcnt; |
| int rval; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: read: wanted %u (0x%x)\n", |
| wantedcnt, |
| wantedcnt); |
| |
| /* get context ptrs |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| /* assert protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_READ); |
| assert(!contextp->dc_errorpr); |
| assert(!contextp->dc_ownedp); |
| assert(wantedcnt > 0); |
| |
| /* clear the return status field |
| */ |
| *rvalp = 0; |
| |
| /* read a new record if necessary |
| */ |
| rval = getrec(drivep); |
| if (rval) { |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "rmt drive op read returning error rval=%d\n", |
| rval); |
| *rvalp = rval; |
| return 0; |
| } |
| |
| /* figure how much data is available, and how much should be supplied |
| */ |
| availcnt = (size_t)(contextp->dc_dataendp - contextp->dc_nextp); |
| actualcnt = min(wantedcnt, availcnt); |
| |
| /* adjust the context |
| */ |
| contextp->dc_ownedp = contextp->dc_nextp; |
| contextp->dc_nextp += actualcnt; |
| assert(contextp->dc_nextp <= contextp->dc_dataendp); |
| |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "rmt drive op read actual == %d (0x%x)\n", |
| actualcnt, |
| actualcnt); |
| |
| *actualcntp = actualcnt; |
| return contextp->dc_ownedp; |
| } |
| |
| /* do_return_read_buf - |
| * Lets the caller give back the buffer portion obtained from the preceding |
| * call to do_read(). |
| * |
| * RETURNS: |
| * void |
| */ |
| /* ARGSUSED */ |
| static void |
| do_return_read_buf(drive_t *drivep, char *bufp, size_t retcnt) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| /* REFERENCED */ |
| size_t ownedcnt; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: return read buf: sz %d (0x%x)\n", |
| retcnt, |
| retcnt); |
| |
| /* assert protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_READ); |
| assert(!contextp->dc_errorpr); |
| assert(contextp->dc_ownedp); |
| assert(bufp == contextp->dc_ownedp); |
| |
| /* calculate how much the caller owns |
| */ |
| assert(contextp->dc_nextp >= contextp->dc_ownedp); |
| ownedcnt = (size_t)(contextp->dc_nextp - contextp->dc_ownedp); |
| assert(ownedcnt == retcnt); |
| |
| /* take possession of buffer portion |
| */ |
| contextp->dc_ownedp = 0; |
| |
| /* if caller is done with this record, take the buffer back |
| * and (if ring in use) give buffer to ring for read-ahead. |
| */ |
| if (contextp->dc_nextp >= contextp->dc_dataendp) { |
| assert(contextp->dc_nextp == contextp->dc_dataendp); |
| if (!contextp->dc_singlethreadedpr) { |
| contextp->dc_msgp->rm_op = RING_OP_READ; |
| Ring_put(contextp->dc_ringp, contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| contextp->dc_recp = 0; |
| contextp->dc_recendp = 0; |
| contextp->dc_dataendp = 0; |
| contextp->dc_nextp = 0; |
| contextp->dc_reccnt++; |
| } |
| } |
| |
| /* do_get_mark |
| * Get the current read tape location. |
| * |
| * RETURNS: |
| * void |
| */ |
| static void |
| do_get_mark(drive_t *drivep, drive_mark_t *markp) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| off64_t offset; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: get mark\n"); |
| |
| /* assert protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_READ); |
| assert(!contextp->dc_errorpr); |
| assert(!contextp->dc_ownedp); |
| |
| /* the mark is simply the offset into the media file of the |
| * next byte to be read. |
| */ |
| offset = contextp->dc_reccnt * (off64_t)tape_recsz; |
| if (contextp->dc_recp) { |
| offset += (off64_t)(contextp->dc_nextp - contextp->dc_recp); |
| } |
| |
| *markp = (drive_mark_t)offset; |
| |
| return; |
| } |
| |
| typedef enum { SEEKMODE_BUF, SEEKMODE_RAW } seekmode_t; |
| |
| /* do_seek_mark |
| * Advance the tape to the given mark. does dummy reads to |
| * advance tape, as well as FSR if supported. |
| * |
| * RETURNS: |
| * 0 on success |
| * DRIVE_ERROR_* on failure |
| * |
| */ |
| static int |
| do_seek_mark(drive_t *drivep, drive_mark_t *markp) |
| { |
| drive_context_t *contextp; |
| off64_t wantedoffset; |
| off64_t currentoffset; |
| |
| /* get the drive context |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| /* assert protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_READ); |
| assert(!contextp->dc_errorpr); |
| assert(!contextp->dc_ownedp); |
| |
| |
| /* the desired mark is passed by reference, and is really just an |
| * offset into the raw (incl rec hdrs) read stream |
| */ |
| wantedoffset = *(off64_t *)markp; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "drive op: seek mark: %lld (0x%llx)\n", |
| wantedoffset, |
| wantedoffset); |
| |
| /* determine the current offset. assert that the wanted offset is |
| * not less than the current offset. |
| */ |
| currentoffset = contextp->dc_reccnt * (off64_t)tape_recsz; |
| if (contextp->dc_recp) { |
| uint32_t recoff; |
| #ifdef DEBUG |
| rec_hdr_t *rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| #endif |
| |
| assert(contextp->dc_nextp >= contextp->dc_recp); |
| recoff = (uint32_t)(contextp->dc_nextp |
| - |
| contextp->dc_recp); |
| assert(recoff <= tape_recsz); |
| assert(rechdrp->rec_used <= tape_recsz); |
| assert(recoff >= STAPE_HDR_SZ); |
| assert(rechdrp->rec_used >= STAPE_HDR_SZ); |
| assert(recoff <= rechdrp->rec_used); |
| currentoffset += (off64_t)recoff; |
| } |
| assert(wantedoffset >= currentoffset); |
| |
| /* if we are currently holding a record and the desired offset |
| * is not within the current record, eat the current record. |
| */ |
| if (contextp->dc_recp) { |
| off64_t nextrecoffset; |
| rec_hdr_t *rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| |
| nextrecoffset = contextp->dc_reccnt * (off64_t)tape_recsz |
| + |
| (off64_t)rechdrp->rec_used; |
| if (wantedoffset >= nextrecoffset) { |
| uint32_t recoff; |
| size_t wantedcnt; |
| char *dummybufp; |
| size_t actualcnt; |
| int rval; |
| |
| /* if this is the last record, the wanted offset |
| * must be just after it. |
| */ |
| if (rechdrp->rec_used < tape_recsz) { |
| assert(wantedoffset == nextrecoffset); |
| } |
| |
| /* figure how much to ask for |
| */ |
| assert(contextp->dc_nextp >= contextp->dc_recp); |
| recoff = (uint32_t)(contextp->dc_nextp |
| - |
| contextp->dc_recp); |
| wantedcnt = (size_t)(rechdrp->rec_used |
| - |
| recoff); |
| |
| /* eat that much tape |
| */ |
| rval = 0; |
| dummybufp = do_read(drivep, |
| wantedcnt, |
| &actualcnt, |
| &rval); |
| if (rval) { |
| return rval; |
| } |
| assert(actualcnt == wantedcnt); |
| do_return_read_buf(drivep, dummybufp, actualcnt); |
| currentoffset += (off64_t)actualcnt; |
| assert(currentoffset == nextrecoffset); |
| assert(wantedoffset >= currentoffset); |
| assert(!contextp->dc_recp); |
| assert(currentoffset |
| == |
| contextp->dc_reccnt * (off64_t)tape_recsz); |
| } |
| } |
| |
| /* if FSR is supported, while the desired offset is more than a record |
| * away, eat records. this is tricky. if read-ahead has already read |
| * to the desired point, no need to FSR: fall through to next code block |
| * where we get there by eating excess records. if read-ahead has not |
| * made it there, suspend read-ahead, eat those readahead records, |
| * FSR the remaining, and resume readahead. |
| */ |
| if (contextp->dc_canfsrpr |
| && |
| wantedoffset - currentoffset >= (off64_t)tape_recsz) { |
| off64_t wantedreccnt; |
| seekmode_t seekmode; |
| |
| assert(!contextp->dc_recp); |
| wantedreccnt = wantedoffset / (off64_t)tape_recsz; |
| if (contextp->dc_singlethreadedpr) { |
| seekmode = SEEKMODE_RAW; |
| } else { |
| seekmode = SEEKMODE_BUF; |
| } |
| assert(wantedreccnt != 0); /* so NOP below can be |
| * distinguished from use |
| * in do_begin_read |
| */ |
| while (contextp->dc_reccnt < wantedreccnt) { |
| off64_t recskipcnt64; |
| off64_t recskipcnt64remaining; |
| |
| if (seekmode == SEEKMODE_BUF) { |
| ring_stat_t rs; |
| assert(!contextp->dc_msgp); |
| contextp->dc_msgp = |
| Ring_get(contextp->dc_ringp); |
| rs = contextp->dc_msgp->rm_stat; |
| if (rs == RING_STAT_ERROR) { |
| contextp->dc_errorpr = BOOL_TRUE; |
| return contextp->dc_msgp->rm_rval; |
| } |
| if (rs != RING_STAT_OK |
| && |
| rs != RING_STAT_INIT |
| && |
| rs != RING_STAT_NOPACK) { |
| assert(0); |
| contextp->dc_errorpr = BOOL_TRUE; |
| return DRIVE_ERROR_CORE; |
| } |
| if (rs == RING_STAT_OK) { |
| contextp->dc_reccnt++; |
| } |
| if (rs == RING_STAT_NOPACK |
| && |
| contextp->dc_msgp->rm_user |
| == |
| wantedreccnt) { |
| seekmode = SEEKMODE_RAW; |
| } |
| contextp->dc_msgp->rm_op = RING_OP_NOP; |
| contextp->dc_msgp->rm_user = wantedreccnt; |
| Ring_put(contextp->dc_ringp, |
| contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| continue; |
| } |
| |
| assert(contextp->dc_reccnt == contextp->dc_iocnt); |
| assert(wantedreccnt > contextp->dc_reccnt); |
| recskipcnt64 = wantedreccnt - contextp->dc_reccnt; |
| recskipcnt64remaining = recskipcnt64; |
| while (recskipcnt64remaining) { |
| int recskipcnt; |
| int saved_errno; |
| int rval; |
| |
| assert(recskipcnt64remaining > 0); |
| if (recskipcnt64remaining > INTGENMAX) { |
| recskipcnt = INTGENMAX; |
| } else { |
| recskipcnt = (int) |
| recskipcnt64remaining; |
| } |
| assert(recskipcnt > 0); |
| rval = mt_op(contextp->dc_fd, |
| MTFSR, |
| recskipcnt); |
| saved_errno = errno; |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("could not forward space %d " |
| "tape blocks: " |
| "rval == %d, errno == %d (%s)\n"), |
| rval, |
| saved_errno, |
| strerror(saved_errno)); |
| return DRIVE_ERROR_MEDIA; |
| } |
| recskipcnt64remaining -= (off64_t)recskipcnt; |
| } |
| contextp->dc_reccnt += recskipcnt64; |
| contextp->dc_iocnt += recskipcnt64; |
| currentoffset = contextp->dc_reccnt |
| * |
| (off64_t)tape_recsz; |
| assert(wantedoffset >= currentoffset); |
| assert(wantedoffset - currentoffset |
| < |
| (off64_t)tape_recsz); |
| } |
| } |
| |
| /* remove excess records by eating them. won't be any if |
| * FSR supported |
| */ |
| while (wantedoffset - currentoffset >= (off64_t)tape_recsz) { |
| size_t wantedcnt; |
| char *dummybufp; |
| size_t actualcnt; |
| int rval; |
| |
| assert(!contextp->dc_recp); |
| |
| /* figure how much to ask for. to eat an entire record, |
| * ask for a record sans the header. do_read will eat |
| * the header, we eat the rest. |
| */ |
| wantedcnt = (size_t)(tape_recsz - STAPE_HDR_SZ); |
| |
| /* eat that much tape |
| */ |
| rval = 0; |
| dummybufp = do_read(drivep, wantedcnt, &actualcnt, &rval); |
| if (rval) { |
| return rval; |
| } |
| assert(actualcnt == wantedcnt); |
| do_return_read_buf(drivep, dummybufp, actualcnt); |
| assert(!contextp->dc_recp); |
| currentoffset += (off64_t)tape_recsz; |
| assert(currentoffset |
| == |
| contextp->dc_reccnt * (off64_t)tape_recsz); |
| } |
| |
| /* eat that portion of the next record leading up to the |
| * desired offset. |
| */ |
| if (wantedoffset != currentoffset) { |
| size_t wantedcnt; |
| char *dummybufp; |
| size_t actualcnt; |
| |
| assert(wantedoffset > currentoffset); |
| assert(wantedoffset - currentoffset < (off64_t)tape_recsz); |
| wantedcnt = (size_t)(wantedoffset - currentoffset); |
| if (contextp->dc_recp) { |
| uint32_t recoff; |
| #ifdef DEBUG |
| rec_hdr_t *rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| #endif |
| recoff = (uint32_t)(contextp->dc_nextp |
| - |
| contextp->dc_recp); |
| assert(recoff <= tape_recsz); |
| assert(rechdrp->rec_used <= tape_recsz); |
| assert(recoff >= STAPE_HDR_SZ); |
| assert(rechdrp->rec_used >= STAPE_HDR_SZ); |
| assert(recoff <= rechdrp->rec_used); |
| assert(recoff + wantedcnt <= rechdrp->rec_used); |
| } else { |
| assert(wantedcnt >= STAPE_HDR_SZ); |
| wantedcnt -= STAPE_HDR_SZ; |
| } |
| |
| /* eat that much tape |
| */ |
| if (wantedcnt > 0) { |
| int rval; |
| rval = 0; |
| dummybufp = do_read(drivep, wantedcnt, &actualcnt, &rval); |
| if (rval) { |
| return rval; |
| } |
| assert(actualcnt == wantedcnt); |
| do_return_read_buf(drivep, dummybufp, actualcnt); |
| } |
| } |
| |
| /* as a sanity check, refigure the current offset and make sure |
| * it is equal to the wanted offset |
| */ |
| currentoffset = contextp->dc_reccnt * (off64_t)tape_recsz; |
| if (contextp->dc_recp) { |
| uint32_t recoff; |
| #ifdef DEBUG |
| rec_hdr_t *rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| #endif |
| |
| assert(contextp->dc_nextp >= contextp->dc_recp); |
| recoff = (uint32_t)(contextp->dc_nextp |
| - |
| contextp->dc_recp); |
| assert(recoff <= tape_recsz); |
| assert(rechdrp->rec_used <= tape_recsz); |
| assert(recoff >= STAPE_HDR_SZ); |
| assert(rechdrp->rec_used >= STAPE_HDR_SZ); |
| assert(recoff <= rechdrp->rec_used); |
| currentoffset += (off64_t)recoff; |
| } |
| assert(wantedoffset == currentoffset); |
| |
| return 0; |
| } |
| |
| /* do_next_mark |
| * Advance the tape position to the next valid mark. if in |
| * error mode, first attempt to move past error by re-reading. if |
| * that fails, try to FSR. also deals with QIC possibility of |
| * reading a block not at a record boundary. |
| * |
| * RETURNS: |
| * 0 on success |
| * DRIVE_ERROR_* on failure |
| */ |
| static int |
| do_next_mark(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| rec_hdr_t *rechdrp; |
| char *p; |
| ix_t trycnt; |
| const ix_t maxtrycnt = 5; |
| int nread; |
| off64_t markoff; |
| int saved_errno; |
| size_t tailsz; |
| int rval; |
| |
| /* assert protocol being followed. |
| */ |
| assert(contextp->dc_mode == OM_READ); |
| assert(!contextp->dc_errorpr); |
| assert(!contextp->dc_ownedp); |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: next mark\n"); |
| |
| trycnt = 0; |
| |
| if (contextp->dc_errorpr) { |
| goto resetring; |
| } else { |
| goto noerrorsearch; |
| } |
| |
| noerrorsearch: |
| for (;;) { |
| rval = getrec(drivep); |
| if (rval == DRIVE_ERROR_CORRUPTION) { |
| goto resetring; |
| } else if (rval) { |
| return rval; |
| } |
| rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| |
| assert(rechdrp->first_mark_offset != 0); |
| if (rechdrp->first_mark_offset > 0) { |
| off64_t markoff = rechdrp->first_mark_offset |
| - |
| rechdrp->file_offset; |
| off64_t curoff = (off64_t)(contextp->dc_nextp |
| - |
| contextp->dc_recp); |
| assert(markoff > 0); |
| assert(curoff > 0); |
| if (markoff >= curoff) { |
| break; |
| } |
| } |
| |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_put(contextp->dc_ringp, |
| contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| contextp->dc_recp = 0; |
| contextp->dc_reccnt++; |
| } |
| |
| assert(rechdrp->first_mark_offset - rechdrp->file_offset |
| <= |
| (off64_t)tape_recsz); |
| contextp->dc_nextp = contextp->dc_recp |
| + |
| (size_t)(rechdrp->first_mark_offset |
| - |
| rechdrp->file_offset); |
| assert(contextp->dc_nextp <= contextp->dc_dataendp); |
| assert(contextp->dc_nextp >= contextp->dc_recp + STAPE_HDR_SZ); |
| if (contextp->dc_nextp == contextp->dc_dataendp) { |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_put(contextp->dc_ringp, |
| contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| contextp->dc_recp = 0; |
| contextp->dc_reccnt++; |
| } |
| |
| return 0; |
| |
| resetring: |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_reset(contextp->dc_ringp, contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| contextp->dc_recp = 0; |
| |
| /* get a record buffer and cast a record header pointer |
| */ |
| if (contextp->dc_singlethreadedpr) { |
| contextp->dc_recp = contextp->dc_bufp; |
| } else { |
| contextp->dc_msgp = Ring_get(contextp->dc_ringp); |
| assert(contextp->dc_msgp->rm_stat == RING_STAT_INIT); |
| contextp->dc_recp = contextp->dc_msgp->rm_bufp; |
| } |
| rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| goto readrecord; |
| |
| readrecord: |
| trycnt++; |
| if (trycnt > maxtrycnt) { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("unable to locate next mark in media file\n")); |
| return DRIVE_ERROR_MEDIA; |
| } |
| |
| nread = Read(drivep, contextp->dc_recp, tape_recsz, &saved_errno); |
| goto validateread; |
| |
| validateread: |
| if (nread == (int)tape_recsz) { |
| goto validatehdr; |
| } |
| |
| if (nread >= 0) { |
| assert((size_t)nread <= tape_recsz); |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "short read (nread == %d, record size == %d)\n", |
| nread, |
| tape_recsz); |
| goto getbeyonderror; |
| } |
| |
| /* some other error |
| */ |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, |
| _("unexpected error attempting to read record: " |
| "read returns %d, errno %s (%s)\n"), |
| nread, |
| errno, |
| strerror(errno)); |
| |
| goto getbeyonderror; |
| |
| validatehdr: |
| rval = record_hdr_validate(drivep, contextp->dc_recp, BOOL_FALSE); |
| |
| if (rval |
| && |
| (contextp->dc_isQICpr == BOOL_TRUE |
| || |
| contextp->dc_isQICpr == BOOL_UNKNOWN)) { |
| goto huntQIC; |
| } |
| |
| if (rval) { |
| goto readrecord; |
| } |
| |
| contextp->dc_reccnt = rechdrp->file_offset / (off64_t)tape_recsz; |
| contextp->dc_iocnt = contextp->dc_reccnt + 1; |
| if (rechdrp->first_mark_offset < 0) { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("valid record %lld but no mark\n"), |
| contextp->dc_iocnt - 1); |
| goto readrecord; |
| } |
| |
| assert(!(rechdrp->file_offset % (off64_t)tape_recsz)); |
| markoff = rechdrp->first_mark_offset - rechdrp->file_offset; |
| assert(markoff >= (off64_t)STAPE_HDR_SZ); |
| assert(markoff < (off64_t)tape_recsz); |
| assert(rechdrp->rec_used > STAPE_HDR_SZ); |
| assert(rechdrp->rec_used < tape_recsz); |
| |
| goto alliswell; |
| |
| alliswell: |
| contextp->dc_nextp = contextp->dc_recp + (size_t)markoff; |
| assert(!(rechdrp->file_offset % (off64_t)tape_recsz)); |
| contextp->dc_reccnt = rechdrp->file_offset / (off64_t)tape_recsz; |
| contextp->dc_iocnt = contextp->dc_reccnt + 1; |
| contextp->dc_recendp = contextp->dc_recp + tape_recsz; |
| contextp->dc_dataendp = contextp->dc_recp + rechdrp->rec_used; |
| assert(contextp->dc_dataendp <= contextp->dc_recendp); |
| assert(contextp->dc_nextp < contextp->dc_dataendp); |
| contextp->dc_errorpr = BOOL_FALSE; |
| |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("resynchronized at record %lld " |
| "offset %u\n"), |
| contextp->dc_iocnt - 1, |
| contextp->dc_nextp |
| - |
| contextp->dc_recp); |
| return 0; |
| |
| getbeyonderror: |
| rval = mt_op(contextp->dc_fd, MTFSR, 1); |
| saved_errno = errno; |
| |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("could not forward space one tape block beyond " |
| "read error: rval == %d, errno == %d (%s)\n"), |
| rval, |
| saved_errno, |
| strerror(saved_errno)); |
| return DRIVE_ERROR_MEDIA; |
| } |
| |
| goto readrecord; |
| |
| huntQIC: |
| /* we have a full tape_recsz record. look for the magic number at the |
| * beginning of each 512 byte block. If we find one, shift that and |
| * the following blocks to the head of the record buffer, and try |
| * to read the remaining blocks in the record. |
| */ |
| for (p = contextp->dc_recp + QIC_BLKSZ |
| ; |
| p < contextp->dc_recendp |
| ; |
| p += QIC_BLKSZ) { |
| if (*(uint64_t *)p == STAPE_MAGIC) { |
| goto adjustQIC; |
| } |
| } |
| |
| goto readrecord; |
| |
| adjustQIC: |
| tailsz = (size_t)(contextp->dc_recendp - p); |
| memcpy((void *)contextp->dc_recp, |
| (void *)p, |
| tailsz); |
| nread = Read(drivep, |
| contextp->dc_recp + tailsz, |
| tape_recsz - tailsz, |
| &saved_errno); |
| |
| goto validateread; |
| } |
| |
| /* do_end_read |
| * Discard any buffered reads. |
| * Tell the reader/writer process to wait. |
| * |
| * RETURNS: |
| * void |
| */ |
| static void |
| do_end_read(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: end read\n"); |
| |
| /* assert protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_READ); |
| assert(!contextp->dc_ownedp); |
| |
| /* In the scsi version, read_label() does a status command to the |
| * drive to then decide if doing a 'fsf' is appropriate. For minrmt, |
| * we don't have the status command so we need to space forward at |
| * the moment we know we need to go forward... which is here. |
| */ |
| (void)fsf_and_verify(drivep); |
| |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_reset(contextp->dc_ringp, contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| |
| contextp->dc_recp = 0; |
| contextp->dc_mode = OM_NONE; |
| } |
| |
| /* do_begin_write |
| * prepare drive for writing. set up drive context. write a header record. |
| * |
| * RETURNS: |
| * 0 on success |
| * DRIVE_ERROR_... on failure |
| */ |
| static int |
| do_begin_write(drive_t *drivep) |
| { |
| drive_context_t *contextp; |
| drive_hdr_t *dwhdrp; |
| global_hdr_t *gwhdrp; |
| rec_hdr_t *tpwhdrp; |
| rec_hdr_t *rechdrp; |
| int rval; |
| media_hdr_t *mwhdrp; |
| content_hdr_t *ch; |
| content_inode_hdr_t *cih; |
| |
| global_hdr_t *tmpgh; |
| drive_hdr_t *tmpdh; |
| media_hdr_t *tmpmh; |
| rec_hdr_t *tmprh; |
| content_hdr_t *tmpch; |
| content_inode_hdr_t *tmpcih; |
| |
| /* get drive context |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: begin write\n"); |
| |
| /* verify protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_NONE); |
| assert(!drivep->d_markrecheadp); |
| assert(!contextp->dc_recp); |
| |
| /* get pointers into global write header |
| */ |
| gwhdrp = drivep->d_gwritehdrp; |
| dwhdrp = drivep->d_writehdrp; |
| tpwhdrp = (rec_hdr_t *)dwhdrp->dh_specific; |
| |
| /* must already be open. The only way to open is to do a begin_read. |
| * so all interaction with tape requires reading first. |
| */ |
| assert(contextp->dc_fd != -1); |
| |
| /* fill in write header's drive specific info |
| */ |
| tpwhdrp->magic = STAPE_MAGIC; |
| tpwhdrp->version = STAPE_VERSION; |
| tpwhdrp->blksize = (int32_t)tape_blksz; |
| tpwhdrp->recsize = (int32_t)tape_recsz; |
| tpwhdrp->rec_used = 0; |
| tpwhdrp->file_offset = 0; |
| tpwhdrp->first_mark_offset= 0; |
| tpwhdrp->capability = drivep->d_capabilities; |
| |
| /* get a record buffer. will be used for the media file header, |
| * and is needed to "prime the pump" for first call to do_write. |
| */ |
| assert(!contextp->dc_recp); |
| if (contextp->dc_singlethreadedpr) { |
| assert(contextp->dc_bufp); |
| contextp->dc_recp = contextp->dc_bufp; |
| } else { |
| assert(contextp->dc_ringp); |
| assert(!contextp->dc_msgp); |
| contextp->dc_msgp = Ring_get(contextp->dc_ringp); |
| assert(contextp->dc_msgp->rm_stat == RING_STAT_INIT); |
| contextp->dc_recp = contextp->dc_msgp->rm_bufp; |
| } |
| |
| /* write the record. be sure to prevent a record checksum from |
| * being produced! |
| */ |
| contextp->dc_iocnt = 0; |
| memset((void *)contextp->dc_recp, 0, tape_recsz); |
| |
| tmpgh = (global_hdr_t *)contextp->dc_recp; |
| tmpdh = (drive_hdr_t *)tmpgh->gh_upper; |
| tmpmh = (media_hdr_t *)tmpdh->dh_upper; |
| tmprh = (rec_hdr_t *)tmpdh->dh_specific; |
| tmpch = (content_hdr_t *)tmpmh->mh_upper; |
| tmpcih = (content_inode_hdr_t *)tmpch->ch_specific; |
| |
| mwhdrp = (media_hdr_t *)dwhdrp->dh_upper; |
| ch = (content_hdr_t *)mwhdrp->mh_upper; |
| cih = (content_inode_hdr_t *)ch->ch_specific; |
| |
| xlate_global_hdr(gwhdrp, tmpgh, 1); |
| xlate_drive_hdr(dwhdrp, tmpdh, 1); |
| xlate_media_hdr(mwhdrp, tmpmh, 1); |
| xlate_content_hdr(ch, tmpch, 1); |
| xlate_content_inode_hdr(cih, tmpcih, 1); |
| xlate_rec_hdr(tpwhdrp, tmprh, 1); |
| |
| /* checksum the global header |
| */ |
| global_hdr_checksum_set(tmpgh); |
| |
| rval = write_record(drivep, contextp->dc_recp, BOOL_TRUE, BOOL_FALSE); |
| if (rval) { |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_reset(contextp->dc_ringp, contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| contextp->dc_recp = 0; |
| return rval; |
| } |
| |
| /* prepare the drive context. must have a record buffer ready to |
| * go, header initialized. |
| */ |
| assert(!contextp->dc_ownedp); |
| contextp->dc_reccnt = 1; /* count the header record */ |
| contextp->dc_recendp = contextp->dc_recp + tape_recsz; |
| contextp->dc_nextp = contextp->dc_recp + STAPE_HDR_SZ; |
| |
| /* intialize header in new record |
| */ |
| rechdrp = (rec_hdr_t*)contextp->dc_recp; |
| rechdrp->magic = STAPE_MAGIC; |
| rechdrp->version = STAPE_VERSION; |
| rechdrp->file_offset = contextp->dc_reccnt * (off64_t)tape_recsz; |
| rechdrp->blksize = (int32_t)tape_blksz; |
| rechdrp->recsize = (int32_t)tape_recsz; |
| rechdrp->capability = drivep->d_capabilities; |
| rechdrp->first_mark_offset = -1LL; |
| uuid_copy(rechdrp->dump_uuid, gwhdrp->gh_dumpid); |
| |
| /* set mode now so operators will work |
| */ |
| contextp->dc_mode = OM_WRITE; |
| |
| contextp->dc_errorpr = BOOL_FALSE; |
| |
| return 0; |
| } |
| |
| /* do_set_mark - queue a mark request. if first mark set in record, record |
| * in record. |
| */ |
| static void |
| do_set_mark(drive_t *drivep, |
| drive_mcbfp_t cbfuncp, |
| void *cbcontextp, |
| drive_markrec_t *markrecp) |
| { |
| drive_context_t *contextp; |
| off64_t nextoff; |
| rec_hdr_t *rechdrp; |
| |
| /* get drive context |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| /* verify protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_WRITE); |
| assert(!contextp->dc_errorpr); |
| assert(!contextp->dc_ownedp); |
| assert(contextp->dc_recp); |
| assert(contextp->dc_nextp); |
| |
| /* calculate and fill in the mark record offset |
| */ |
| assert(contextp->dc_recp); |
| nextoff = contextp->dc_reccnt * (off64_t)tape_recsz |
| + |
| (off64_t)(contextp->dc_nextp - contextp->dc_recp); |
| markrecp->dm_log = (drive_mark_t)nextoff; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: set mark: %lld (0x%llx)\n", |
| nextoff, |
| nextoff); |
| |
| /* note the location of the first mark in this tape record. |
| */ |
| rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| if (rechdrp->first_mark_offset == -1LL) { |
| assert(nextoff != -1LL); |
| rechdrp->first_mark_offset = nextoff; |
| } |
| |
| /* put the mark on the tail of the queue. |
| */ |
| markrecp->dm_cbfuncp = cbfuncp; |
| markrecp->dm_cbcontextp = cbcontextp; |
| markrecp->dm_nextp = 0; |
| if (drivep->d_markrecheadp == 0) { |
| drivep->d_markrecheadp = markrecp; |
| drivep->d_markrectailp = markrecp; |
| } else { |
| assert(drivep->d_markrectailp); |
| drivep->d_markrectailp->dm_nextp = markrecp; |
| drivep->d_markrectailp = markrecp; |
| } |
| } |
| |
| /* do_get_write_buf - supply the caller with some or all of the current record |
| * buffer. the supplied buffer must be fully returned (via a single call to |
| * do_write) prior to the next call to do_get_write_buf. |
| * |
| * RETURNS: |
| * the address of a buffer |
| * "actual_bufszp" points to the size of the buffer |
| */ |
| static char * |
| do_get_write_buf(drive_t *drivep, size_t wantedcnt, size_t *actualcntp) |
| { |
| drive_context_t *contextp; |
| size_t remainingcnt; |
| size_t actualcnt; |
| |
| /* get drive context |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| /* verify protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_WRITE); |
| assert(!contextp->dc_errorpr); |
| assert(!contextp->dc_ownedp); |
| assert(contextp->dc_recp); |
| assert(contextp->dc_nextp); |
| assert(contextp->dc_nextp < contextp->dc_recendp); |
| |
| /* figure how much is available; supply the min of what is |
| * available and what is wanted. |
| */ |
| remainingcnt = (size_t)(contextp->dc_recendp - contextp->dc_nextp); |
| actualcnt = min(remainingcnt, wantedcnt); |
| *actualcntp = actualcnt; |
| contextp->dc_ownedp = contextp->dc_nextp; |
| contextp->dc_nextp += actualcnt; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: get write buf: wanted %u (0x%x) actual %u (0x%x)\n", |
| wantedcnt, |
| wantedcnt, |
| actualcnt, |
| actualcnt); |
| |
| return contextp->dc_ownedp; |
| } |
| |
| /* do_write - accept ownership of the portion of the current record buffer |
| * being returned by the caller. if returned portion includes end of record |
| * buffer, write the buffer and get and prepare a new one in anticipation of |
| * the next call to do_get_write_buf. also, process any queued marks which |
| * are guaranteed to be committed to media. NOTE: the caller must return |
| * everything obtained with the preceeding call to do_get_write_buf. |
| * |
| * RETURNS: |
| * 0 on success |
| * non 0 on error |
| */ |
| /* ARGSUSED */ |
| static int |
| do_write(drive_t *drivep, char *bufp, size_t retcnt) |
| { |
| drive_context_t *contextp; |
| rec_hdr_t *rechdrp; |
| global_hdr_t *gwhdrp; |
| size_t heldcnt; |
| off64_t last_rec_wrtn_wo_err; /* zero-based index */ |
| int rval; |
| |
| /* get drive context and pointer to global write hdr |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| gwhdrp = drivep->d_gwritehdrp; |
| |
| /* calculate how many bytes we believe caller is holding |
| */ |
| heldcnt = (size_t)(contextp->dc_nextp - contextp->dc_ownedp); |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: write: retcnt %u (0x%x) heldcnt %u (0x%x)\n", |
| retcnt, |
| retcnt, |
| heldcnt, |
| heldcnt); |
| |
| /* verify protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_WRITE); |
| assert(!contextp->dc_errorpr); |
| assert(contextp->dc_ownedp); |
| assert(contextp->dc_recp); |
| assert(contextp->dc_nextp); |
| assert(contextp->dc_nextp <= contextp->dc_recendp); |
| |
| /* verify the caller is returning exactly what is held |
| */ |
| assert(bufp == contextp->dc_ownedp); |
| assert(retcnt == heldcnt); |
| |
| /* take it back |
| */ |
| contextp->dc_ownedp = 0; |
| |
| /* if some portion of the record buffer has not yet been |
| * held by the client, just return. |
| */ |
| if (contextp->dc_nextp < contextp->dc_recendp) { |
| return 0; |
| } |
| |
| /* record in record header that entire record is used |
| */ |
| rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| rechdrp->rec_used = tape_recsz; |
| |
| /* write out the record buffer and get a new one. |
| */ |
| if (contextp->dc_singlethreadedpr) { |
| rval = write_record(drivep, contextp->dc_recp, BOOL_TRUE, BOOL_TRUE); |
| last_rec_wrtn_wo_err = contextp->dc_reccnt; /* conv cnt to ix */ |
| } else { |
| contextp->dc_msgp->rm_op = RING_OP_WRITE; |
| contextp->dc_msgp->rm_user = contextp->dc_reccnt; |
| Ring_put(contextp->dc_ringp, |
| contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| contextp->dc_msgp = Ring_get(contextp->dc_ringp); |
| contextp->dc_recp = contextp->dc_msgp->rm_bufp; |
| last_rec_wrtn_wo_err = contextp->dc_msgp->rm_user; |
| switch(contextp->dc_msgp->rm_stat) { |
| case RING_STAT_OK: |
| case RING_STAT_INIT: |
| rval = 0; |
| break; |
| case RING_STAT_ERROR: |
| rval = contextp->dc_msgp->rm_rval; |
| break; |
| default: |
| assert(0); |
| return DRIVE_ERROR_CORE; |
| } |
| } |
| |
| /* check for errors. if none, commit all marks before a safety margin |
| * before the no error offset. |
| */ |
| if (rval) { |
| contextp->dc_errorpr = BOOL_TRUE; |
| } else { |
| off64_t recs_wrtn_wo_err; |
| off64_t recs_committed; |
| off64_t bytes_committed; |
| recs_wrtn_wo_err = last_rec_wrtn_wo_err + 1; |
| recs_committed = recs_wrtn_wo_err - contextp->dc_lostrecmax; |
| bytes_committed = recs_committed * (off64_t)tape_recsz; |
| drive_mark_commit(drivep, bytes_committed); |
| } |
| |
| /* adjust context |
| */ |
| contextp->dc_reccnt++; |
| contextp->dc_recendp = contextp->dc_recp + tape_recsz; |
| contextp->dc_nextp = contextp->dc_recp |
| + |
| STAPE_HDR_SZ; |
| |
| /* intialize header in new record |
| */ |
| rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| rechdrp->magic = STAPE_MAGIC; |
| rechdrp->version = STAPE_VERSION; |
| rechdrp->file_offset = contextp->dc_reccnt * (off64_t)tape_recsz; |
| rechdrp->blksize = (int32_t)tape_blksz; |
| rechdrp->recsize = (int32_t)tape_recsz; |
| rechdrp->capability = drivep->d_capabilities; |
| rechdrp->first_mark_offset = -1LL; |
| uuid_copy(rechdrp->dump_uuid, gwhdrp->gh_dumpid); |
| |
| return rval; |
| } |
| |
| /* do_get_align_cnt - |
| * Returns the number of bytes which must be written to |
| * cause the next call to get_write_buf() to be page-aligned. |
| * |
| * RETURNS: |
| * the number of bytes to next alignment |
| */ |
| static size_t |
| do_get_align_cnt(drive_t *drivep) |
| { |
| char *next_alignment_point; |
| intptr_t next_alignment_off; |
| drive_context_t *contextp; |
| |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: get align cnt\n"); |
| |
| /* verify protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_WRITE); |
| assert(!contextp->dc_errorpr); |
| assert(!contextp->dc_ownedp); |
| assert(contextp->dc_recp); |
| assert(contextp->dc_nextp); |
| assert(contextp->dc_nextp < contextp->dc_recendp); |
| |
| /* calculate the next alignment point at or beyond the current nextp. |
| * the following algorithm works because all buffers are page-aligned |
| * and a multiple of PGSZ. |
| */ |
| next_alignment_off = (intptr_t)contextp->dc_nextp; |
| next_alignment_off += PGMASK; |
| next_alignment_off &= ~PGMASK; |
| next_alignment_point = (char *)next_alignment_off; |
| assert(next_alignment_point <= contextp->dc_recendp); |
| |
| /* return the number of bytes to the next alignment offset |
| */ |
| assert(next_alignment_point >= contextp->dc_nextp); |
| return (size_t)(next_alignment_point - contextp->dc_nextp); |
| } |
| |
| /* do_end_write - pad and write pending record if any client data in it. |
| * flush all pending writes. write a file mark. figure how many records are |
| * guaranteed to be on media, and commit/discard marks accordingly. |
| * RETURNS: |
| * 0 on success |
| * DRIVE_ERROR_* on failure |
| */ |
| static int |
| do_end_write(drive_t *drivep, off64_t *ncommittedp) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| off64_t first_rec_w_err; /* zero-based index */ |
| off64_t recs_wtn_wo_err; |
| off64_t recs_guaranteed; |
| off64_t bytes_committed; |
| |
| int rval; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: end write\n"); |
| |
| /* verify protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_WRITE); |
| assert(!contextp->dc_ownedp); |
| assert(contextp->dc_recp); |
| assert(contextp->dc_nextp); |
| assert(contextp->dc_nextp >= contextp->dc_recp + STAPE_HDR_SZ); |
| assert(contextp->dc_nextp < contextp->dc_recendp); |
| |
| /* pre-initialize return of count of bytes committed to media |
| */ |
| *ncommittedp = 0; |
| |
| /* if in error mode, a write error occured earlier. don't bother |
| * to do anymore writes, just cleanup and return 0. don't need to |
| * do commits, already done when error occured. |
| */ |
| if (contextp->dc_errorpr) { |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_reset(contextp->dc_ringp, contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| contextp->dc_mode = OM_NONE; |
| drive_mark_discard(drivep); |
| *ncommittedp = (contextp->dc_iocnt - contextp->dc_lostrecmax) |
| * |
| (off64_t)tape_recsz; |
| contextp->dc_recp = 0; |
| return 0; |
| } |
| |
| /* if any user data in current record buffer, send it out. |
| */ |
| if (contextp->dc_nextp > contextp->dc_recp + STAPE_HDR_SZ) { |
| rec_hdr_t *rechdrp; |
| size_t bufusedcnt; |
| |
| rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| bufusedcnt = (size_t)(contextp->dc_nextp |
| - |
| contextp->dc_recp); |
| rechdrp->rec_used = bufusedcnt; |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "writing padded last record\n"); |
| if (contextp->dc_singlethreadedpr) { |
| rval = write_record(drivep, |
| contextp->dc_recp, |
| BOOL_TRUE, BOOL_TRUE); |
| } else { |
| assert(contextp->dc_msgp); |
| contextp->dc_msgp->rm_op = RING_OP_WRITE; |
| contextp->dc_msgp->rm_user = contextp->dc_reccnt; |
| Ring_put(contextp->dc_ringp, |
| contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| contextp->dc_msgp = Ring_get(contextp->dc_ringp); |
| switch(contextp->dc_msgp->rm_stat) { |
| case RING_STAT_OK: |
| case RING_STAT_INIT: |
| rval = 0; |
| break; |
| case RING_STAT_ERROR: |
| rval = contextp->dc_msgp->rm_rval; |
| break; |
| default: |
| assert(0); |
| contextp->dc_recp = 0; |
| return DRIVE_ERROR_CORE; |
| } |
| } |
| contextp->dc_reccnt++; |
| } else { |
| rval = 0; |
| } |
| |
| /* now flush the ring until error or tracer bullet seen. |
| * note the record index in the first msg received with |
| * an error indication. this will be used to calculate |
| * the number of records guaranteed to have made it onto |
| * media, and that will be used to select which marks |
| * to commit and which to discard. |
| */ |
| if (rval) { |
| first_rec_w_err = contextp->dc_iocnt; |
| /* because dc_iocnt bumped by write_record |
| * only if no error |
| */ |
| } else { |
| first_rec_w_err = -1L; |
| } |
| if (!contextp->dc_singlethreadedpr) { |
| while (!rval) { |
| assert(contextp->dc_msgp); |
| contextp->dc_msgp->rm_op = RING_OP_TRACE; |
| Ring_put(contextp->dc_ringp, |
| contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| contextp->dc_msgp = Ring_get(contextp->dc_ringp); |
| if (contextp->dc_msgp->rm_op == RING_OP_TRACE) { |
| break; |
| } |
| switch(contextp->dc_msgp->rm_stat) { |
| case RING_STAT_OK: |
| case RING_STAT_INIT: |
| assert(rval == 0); |
| break; |
| case RING_STAT_ERROR: |
| rval = contextp->dc_msgp->rm_rval; |
| first_rec_w_err = contextp->dc_msgp->rm_user; |
| break; |
| default: |
| assert(0); |
| contextp->dc_recp = 0; |
| return DRIVE_ERROR_CORE; |
| } |
| } |
| } |
| |
| /* the ring is now flushed. reset |
| */ |
| if (!contextp->dc_singlethreadedpr) { |
| Ring_reset(contextp->dc_ringp, contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| } |
| contextp->dc_recp = 0; |
| |
| /* if no error so far, write a file mark. this will have the |
| * side-effect of flushing the driver/drive of pending writes, |
| * exposing any write errors. |
| */ |
| if (!rval) { |
| int weofrval; |
| |
| weofrval = mt_op(contextp->dc_fd, MTWEOF, 1); |
| if (weofrval) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "MTWEOF returned %d: errno == %d (%s)\n", |
| weofrval, |
| errno, |
| strerror(errno)); |
| rval = DRIVE_ERROR_EOM; |
| } |
| } |
| |
| /* if an error occured, first_rec_w_err now contains |
| * the count of records written without error, all of which |
| * were full records. subtract from this dc_lostrecmax, |
| * and we have the number of records guaranteed to have made |
| * it to media. |
| * |
| * if no errors have occured, all I/O has been committed. |
| * we can use dc_iocnt, which is the count of records actually |
| * written without error. |
| * |
| * commit marks contained in committed records, discard the rest, |
| * and return rval. return by reference the number of bytes committed |
| * to tape. |
| */ |
| if (rval) { |
| assert(first_rec_w_err >= 0); |
| recs_wtn_wo_err = first_rec_w_err; |
| recs_guaranteed = recs_wtn_wo_err - contextp->dc_lostrecmax; |
| } else { |
| assert(first_rec_w_err == -1); |
| recs_wtn_wo_err = contextp->dc_iocnt; |
| recs_guaranteed = recs_wtn_wo_err; |
| } |
| bytes_committed = recs_guaranteed * (off64_t)tape_recsz; |
| drive_mark_commit(drivep, bytes_committed); |
| drive_mark_discard(drivep); |
| contextp->dc_mode = OM_NONE; |
| *ncommittedp = bytes_committed; |
| return rval; |
| } |
| |
| /* do_fsf |
| * Advance the tape by count files. |
| * |
| * RETURNS: |
| * number of media files skipped |
| * *statp set to zero or DRIVE_ERROR_... |
| */ |
| static int |
| do_fsf(drive_t *drivep, int count, int *statp) |
| { |
| int i, done, op_failed, opcount; |
| drive_context_t *contextp; |
| |
| /* get drive context |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| /* verify protocol being followed |
| */ |
| assert(contextp->dc_mode == OM_NONE); |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: fsf: count %d\n", |
| count); |
| |
| assert(count); |
| assert(contextp->dc_mode == OM_NONE); |
| |
| for (i = 0; i < count; i++) { |
| done = 0; |
| opcount = 2; |
| |
| /* the tape may encounter errors will trying to |
| * reach the next file. |
| */ |
| while (!done) { |
| |
| /* advance the tape to the next file mark |
| * NOTE: |
| * ignore return code |
| */ |
| mlog(MLOG_VERBOSE | MLOG_DRIVE, |
| _("advancing tape to next media file\n")); |
| |
| op_failed = 0; |
| assert(contextp->dc_fd >= 0); |
| if (mt_op(contextp->dc_fd, MTFSF, 1)) { |
| op_failed = 1; |
| } |
| |
| |
| /* Check for a file mark to |
| * determine if the fsf command worked. |
| */ |
| if (!op_failed) { |
| done = 1; |
| } |
| |
| /* If the FSF command has been issued multiple |
| * times, and a file mark has not been reached, |
| * return an error. |
| */ |
| if (--opcount < 0) { |
| mlog(MLOG_VERBOSE | MLOG_DRIVE, |
| _("FSF tape command failed\n")); |
| |
| *statp = DRIVE_ERROR_DEVICE; |
| return i; |
| } |
| } |
| } |
| |
| return count; |
| } |
| |
| /* do_bsf |
| * Backup the tape by count files. zero means just back up to the beginning |
| * of the last media file read or written. |
| * |
| * RETURNS: |
| * number of media files skipped |
| * *statp set to zero or DRIVE_ERROR_... |
| */ |
| static int |
| do_bsf(drive_t *drivep, int count, int *statp) |
| { |
| #ifdef DEBUG |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| #endif |
| int skipped; |
| int rval; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: bsf: count %d\n", |
| count); |
| |
| assert(contextp->dc_mode == OM_NONE); |
| |
| *statp = 0; |
| |
| /* back space - places us to left of previous file mark |
| * if we hit BOT, return |
| */ |
| assert(drivep->d_capabilities & DRIVE_CAP_BSF); |
| rval = bsf_and_verify(drivep); |
| if (rval) { |
| if (errno == ENOSPC/*IRIX*/ || errno == EIO/*Linux*/) { |
| if (count) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: bsf reached BOT " |
| "unexpectedly (%d files to go)\n", |
| count); |
| /* set statp - BOT is unexpected */ |
| } else { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: bsf reached BOT\n"); |
| /* don't set statp, BOT is fine */ |
| return 0; |
| } |
| } |
| *statp = DRIVE_ERROR_DEVICE; |
| return 0; |
| } |
| |
| /* now loop, skipping media files |
| */ |
| for (skipped = 0; skipped < count; skipped++) { |
| |
| /* move to the left of the next file mark on the left. |
| * check for BOT. |
| */ |
| rval = bsf_and_verify(drivep); |
| if (rval) { |
| if (errno == ENOSPC/*IRIX*/ || errno == EIO/*Linux*/) { |
| if (count - skipped - 1) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: bsf reached BOT " |
| "unexpectedly (%d files to go)\n", |
| count - skipped - 1); |
| /* set statp - BOT is unexpected */ |
| *statp = DRIVE_ERROR_DEVICE; |
| return skipped + 1; |
| } else { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: bsf reached BOT\n"); |
| /* don't set statp - BOT is fine */ |
| return skipped + 1; |
| } |
| *statp = DRIVE_ERROR_DEVICE; |
| return skipped; |
| } |
| } |
| } |
| |
| /* we're not at BOT, so we need to move to the right side of |
| * the file mark |
| */ |
| (void)fsf_and_verify(drivep); |
| |
| /* indicate the number of media files skipped |
| */ |
| return skipped; |
| } |
| |
| /* do_rewind |
| * Position the tape at the beginning of the recorded media. |
| * |
| * RETURNS: |
| * 0 on sucess |
| * DRIVE_ERROR_* on failure |
| */ |
| static int |
| do_rewind(drive_t *drivep) |
| { |
| #ifdef DEBUG |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| #endif |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: rewind\n"); |
| |
| assert(contextp->dc_mode == OM_NONE); |
| assert(contextp->dc_fd >= 0); |
| |
| /* use validating tape rewind util func |
| */ |
| (void)rewind_and_verify(drivep); |
| return 0; |
| } |
| |
| /* do_erase |
| * erase media from beginning |
| * |
| * RETURNS: |
| * 0 on sucess |
| * DRIVE_ERROR_* on failure |
| */ |
| static int |
| do_erase(drive_t *drivep) |
| { |
| #ifdef DEBUG |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| #endif |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: erase\n"); |
| |
| assert(contextp->dc_mode == OM_NONE); |
| assert(contextp->dc_fd >= 0); |
| |
| /* use validating tape rewind util func |
| */ |
| (void)rewind_and_verify(drivep); |
| |
| /* use validating tape erase util func |
| */ |
| (void)erase_and_verify(drivep); |
| |
| /* rewind again |
| */ |
| (void)rewind_and_verify(drivep); |
| |
| /* close the drive so we start from scratch |
| */ |
| Close(drivep); |
| return 0; |
| } |
| |
| /* do_eject |
| * pop the tape out - may be a nop on some drives |
| * |
| * RETURNS: |
| * 0 on sucess |
| * DRIVE_ERROR_DEVICE on failure |
| */ |
| static int |
| do_eject_media(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: eject media\n"); |
| |
| /* drive must be open |
| */ |
| assert(contextp->dc_fd >= 0); |
| assert(contextp->dc_mode == OM_NONE); |
| |
| /* issue tape unload |
| */ |
| if (contextp->dc_unloadokpr) { |
| (void)mt_op(contextp->dc_fd, MTUNLOAD, 0); |
| } |
| |
| /* close the device driver |
| */ |
| Close(drivep); |
| |
| return 0; |
| } |
| |
| /* do_get_device_class |
| * Return the device class |
| * |
| * RETURNS: |
| * always returns DEVICE_TAPE_REMOVABLE |
| */ |
| /* ARGSUSED */ |
| static int |
| do_get_device_class(drive_t *drivep) |
| { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: get device class\n"); |
| |
| return DEVICE_TAPE_REMOVABLE; |
| } |
| |
| /* do_display_metrics - print ring stats if using I/O ring |
| */ |
| static void |
| do_display_metrics(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| ring_t *ringp = contextp->dc_ringp; |
| |
| if (ringp) { |
| if (drivecnt > 1) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_DRIVE, |
| _("drive %u "), |
| drivep->d_index); |
| } |
| display_ring_metrics(drivep, |
| MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK); |
| } |
| } |
| |
| /* do_quit |
| */ |
| static void |
| do_quit(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| ring_t *ringp = contextp->dc_ringp; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op: quit\n"); |
| |
| /* print the ring metrics and kill the ring |
| */ |
| if (ringp) { |
| display_ring_metrics(drivep, MLOG_VERBOSE); |
| |
| /* tell slave to die |
| */ |
| mlog((MLOG_NITTY + 1) | MLOG_DRIVE, |
| "ring op: destroy\n"); |
| ring_destroy(ringp); |
| } |
| |
| Close(drivep); |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt drive op quit complete\n"); |
| } |
| |
| static double |
| percent64(off64_t num, off64_t denom) |
| { |
| return (double)(num * 100) / (double)denom; |
| } |
| |
| |
| /* read_label |
| * responsible for reading and validating the first record from a |
| * media file. can assume that prepare_drive has already been run |
| * on this tape. if read fails due to an encounter with a file mark, |
| * end of media, or end of data, position the media to allow an |
| * append. |
| * |
| * RETURNS: |
| * 0 on success |
| * DRIVE_ERROR_* on failure |
| */ |
| static int |
| read_label(drive_t *drivep) |
| { |
| drive_context_t *contextp; |
| int nread; |
| int saved_errno; |
| int rval; |
| |
| /* initialize context ptr |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| /* read the first record of the media file directly |
| */ |
| nread = Read(drivep, |
| contextp->dc_recp, |
| tape_recsz, |
| &saved_errno); |
| |
| /* if a read error, get status |
| */ |
| if (nread != (int)tape_recsz) { |
| assert(nread < (int)tape_recsz); |
| } |
| |
| /* check for an unexpected errno |
| */ |
| if (nread < 0 && saved_errno != ENOSPC) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("could not read from drive: %s (%d)\n"), |
| strerror(errno), |
| errno); |
| return DRIVE_ERROR_DEVICE; |
| } |
| |
| /* check for a blank tape/EOD. |
| */ |
| if ((nread == 0) /* takes care of sun */ |
| || /* now handle SGI */ |
| (nread < 0 && saved_errno == ENOSPC)) { |
| #ifdef DUMP |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("encountered EOD : assuming blank media\n")); |
| #endif |
| #ifdef RESTORE |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("encountered EOD : end of data\n")); |
| #endif |
| (void)rewind_and_verify(drivep); |
| #ifdef DUMP |
| return DRIVE_ERROR_BLANK; |
| #endif |
| #ifdef RESTORE |
| return DRIVE_ERROR_EOD; |
| #endif |
| } |
| |
| |
| /* dc_iocnt is count of number of records read without error |
| */ |
| contextp->dc_iocnt = 1; |
| |
| rval = validate_media_file_hdr(drivep); |
| |
| return rval; |
| } |
| |
| static int |
| validate_media_file_hdr(drive_t *drivep) |
| { |
| global_hdr_t *grhdrp = drivep->d_greadhdrp; |
| drive_hdr_t *drhdrp = drivep->d_readhdrp; |
| rec_hdr_t *tprhdrp = (rec_hdr_t *)drhdrp->dh_specific; |
| media_hdr_t *mrhdrp = (media_hdr_t *)drhdrp->dh_upper; |
| content_hdr_t *ch = (content_hdr_t *)mrhdrp->mh_upper; |
| content_inode_hdr_t *cih = (content_inode_hdr_t *)ch->ch_specific; |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| char tmpbuf[GLOBAL_HDR_SZ]; |
| global_hdr_t *tmpgh = (global_hdr_t *)&tmpbuf[0]; |
| drive_hdr_t *tmpdh = (drive_hdr_t *)tmpgh->gh_upper; |
| media_hdr_t *tmpmh = (media_hdr_t *)tmpdh->dh_upper; |
| rec_hdr_t *tmprh = (rec_hdr_t *)tmpdh->dh_specific; |
| content_hdr_t *tmpch = (content_hdr_t *)tmpmh->mh_upper; |
| content_inode_hdr_t *tmpcih = (content_inode_hdr_t *)tmpch->ch_specific; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "validating media file header\n"); |
| |
| memcpy(tmpbuf, contextp->dc_recp, GLOBAL_HDR_SZ); |
| |
| /* check the checksum |
| */ |
| if (!global_hdr_checksum_check(tmpgh)) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "bad media file header checksum\n"); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| if (!tape_rec_checksum_check(contextp, contextp->dc_recp)) { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("tape record checksum error\n")); |
| return DRIVE_ERROR_CORRUPTION; |
| |
| } |
| |
| xlate_global_hdr(tmpgh, grhdrp, 1); |
| xlate_drive_hdr(tmpdh, drhdrp, 1); |
| xlate_media_hdr(tmpmh, mrhdrp, 1); |
| xlate_content_hdr(tmpch, ch, 1); |
| xlate_content_inode_hdr(tmpcih, cih, 1); |
| xlate_rec_hdr(tmprh, tprhdrp, 1); |
| |
| memcpy(contextp->dc_recp, grhdrp, GLOBAL_HDR_SZ); |
| |
| /* check the magic number |
| */ |
| if (strncmp(grhdrp->gh_magic, GLOBAL_HDR_MAGIC, GLOBAL_HDR_MAGIC_SZ)) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "missing magic number in tape label\n"); |
| return DRIVE_ERROR_FORMAT; |
| } |
| |
| /* check the version |
| */ |
| if (global_version_check(grhdrp->gh_version) != BOOL_TRUE) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "invalid version number (%d) in tape label\n", |
| grhdrp->gh_version); |
| return DRIVE_ERROR_VERSION; |
| } |
| |
| /* check the strategy id |
| */ |
| if (drhdrp->dh_strategyid != drivep->d_strategyp->ds_id) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "unrecognized drive strategy ID (%d)\n", |
| drivep->d_readhdrp->dh_strategyid); |
| return DRIVE_ERROR_FORMAT; |
| } |
| |
| /* check the record magic number |
| */ |
| if (tprhdrp->magic != STAPE_MAGIC) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "invalid record magic number in tape label\n"); |
| return DRIVE_ERROR_FORMAT; |
| } |
| |
| /* check the record version number |
| */ |
| if (tprhdrp->version != STAPE_VERSION) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "invalid record version number in tape label\n"); |
| return DRIVE_ERROR_VERSION; |
| } |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "media file header valid: " |
| "media file ix %d\n", |
| mrhdrp->mh_mediafileix); |
| |
| return 0; |
| } |
| |
| |
| /* get_tpcaps |
| * Get the specific tape drive capabilities. Set the |
| * d_capabilities field of the driver structure. |
| * set the blksz limits and tape type in the context structure. |
| * |
| * RETURNS: |
| * TRUE on success |
| * FALSE on error |
| */ |
| static bool_t |
| get_tpcaps(drive_t *drivep) |
| { |
| #ifdef DEBUG |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| assert(contextp->dc_fd >= 0); |
| #endif |
| |
| /* can't ask about blksz, can't set blksz, can't ask about |
| * drive types/caps. assume a drive which can overwrite. |
| * assume NOT QIC, since fixed blksz devices not supported |
| * via RMT. |
| */ |
| drivep->d_capabilities |= DRIVE_CAP_OVERWRITE; |
| drivep->d_capabilities |= DRIVE_CAP_BSF; |
| |
| set_recommended_sizes(drivep); |
| |
| return BOOL_TRUE; |
| } |
| |
| /* set_recommended_sizes |
| * Determine the recommended tape file size and mark separation |
| * based on tape device type. |
| * |
| * RETURNS: |
| * void |
| */ |
| static void |
| set_recommended_sizes(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| off64_t fsize = drive_strategy_rmt.ds_recmfilesz; |
| off64_t marksep = drive_strategy_rmt.ds_recmarksep; |
| |
| if (contextp->dc_filesz > 0) { |
| fsize = contextp->dc_filesz; |
| #ifdef DUMP |
| if (hdr_mfilesz > fsize) { |
| mlog(MLOG_WARNING, _( |
| "recommended media file size of %llu Mb less than " |
| "estimated file header size %llu Mb for %s\n"), |
| fsize / (1024 * 1024), |
| hdr_mfilesz / (1024 * 1024), |
| drivep->d_pathname); |
| } |
| #endif /* DUMP */ |
| } |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "recommended tape media file size set to 0x%llx bytes\n", |
| fsize); |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "recommended tape media mark separation set to 0x%llx bytes\n", |
| marksep); |
| |
| drivep->d_recmfilesz = fsize; |
| drivep->d_recmarksep = marksep; |
| |
| return; |
| } |
| |
| |
| |
| /* mt_op |
| * Issue MTIOCTOP ioctl operation to the tape device. |
| * |
| * RETURNS: |
| * 0 on sucess |
| * -1 on failure |
| */ |
| static int |
| mt_op(int fd, int sub_op, int param) |
| { |
| struct mtop mop; |
| char *printstr; |
| int rval; |
| |
| mop.mt_op = (short)sub_op; |
| mop.mt_count = param; |
| |
| assert(fd >= 0); |
| |
| switch (sub_op) { |
| case MTSEEK: |
| printstr = "seek"; |
| break; |
| case MTBSF: /* 2 */ |
| printstr = "back space file"; |
| break; |
| case MTWEOF: /* 0 */ |
| printstr = "write file mark"; |
| break; |
| case MTFSF: /* 1 */ |
| printstr = "forward space file"; |
| break; |
| case MTREW: /* 5 */ |
| printstr = "rewind"; |
| break; |
| case MTUNLOAD: |
| printstr = "unload"; |
| break; |
| case MTEOM: |
| printstr = "advance to EOD"; |
| break; |
| case MTFSR: /* 3 */ |
| printstr = "forward space block"; |
| break; |
| case MTERASE: |
| printstr = "erase"; |
| break; |
| #ifdef CLRMTAUD |
| #ifdef MTAUD |
| case MTAUD: |
| printstr = _("audio"); |
| break; |
| #endif /* MTAUD */ |
| #endif /* CLRMTAUD */ |
| default: |
| printstr = "???"; |
| break; |
| } |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "tape op: %s %d\n", |
| printstr, |
| param); |
| |
| rval = ioctl(fd, MTIOCTOP, &mop); |
| if (rval < 0) { |
| /* failure |
| */ |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "tape op %s %d returns %d: errno == %d (%s)\n", |
| printstr, |
| param, |
| rval, |
| errno, |
| strerror(errno)); |
| return -1; |
| } |
| |
| /* success |
| */ |
| return 0; |
| } |
| |
| /* determine_write_error() |
| * Using the errno and the tape status information, determine the |
| * type of tape write error that has occured. |
| * |
| * RETURNS: |
| * DRIVE_ERROR_* |
| */ |
| static int |
| determine_write_error(int nwritten, int saved_errno) |
| { |
| int ret = 0; |
| |
| if (saved_errno == EACCES) { |
| mlog(MLOG_NORMAL, |
| _("tape is write protected\n")); |
| |
| ret = DRIVE_ERROR_DEVICE; |
| } else if ( |
| (saved_errno == ENOSPC) |
| || |
| (saved_errno == EIO) |
| || |
| ((saved_errno == 0) && (nwritten >= 0)) /* short |
| write indicates EOM */ |
| ) { |
| mlog(MLOG_NORMAL, |
| _("tape media error on write operation\n")); |
| |
| mlog(MLOG_NORMAL, |
| _("no more data can be written to this tape\n")); |
| |
| ret = DRIVE_ERROR_EOM; |
| } else if (saved_errno != 0) { |
| ret = DRIVE_ERROR_CORE; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "tape unknown error on write operation: " |
| "%d, %d\n", |
| nwritten, saved_errno); |
| } |
| |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "tape write operation nwritten %d, errno %d\n", |
| nwritten, |
| saved_errno); |
| |
| return (ret); |
| |
| } |
| |
| static void |
| tape_rec_checksum_set(drive_context_t *contextp, char *bufp) |
| { |
| rec_hdr_t *rechdrp = (rec_hdr_t *)bufp; |
| uint32_t *beginp = (uint32_t *)bufp; |
| uint32_t *endp = (uint32_t *)(bufp + tape_recsz); |
| uint32_t *p; |
| uint32_t accum; |
| |
| if (!contextp->dc_recchksumpr) { |
| return; |
| } |
| |
| INT_SET(rechdrp->ischecksum, ARCH_CONVERT, 1); |
| rechdrp->checksum = 0; |
| accum = 0; |
| for (p = beginp; p < endp; p++) { |
| accum += INT_GET(*p, ARCH_CONVERT); |
| } |
| INT_SET(rechdrp->checksum, ARCH_CONVERT, (int32_t)(~accum + 1)); |
| } |
| |
| static bool_t |
| tape_rec_checksum_check(drive_context_t *contextp, char *bufp) |
| { |
| rec_hdr_t *rechdrp = (rec_hdr_t *)bufp; |
| uint32_t *beginp = (uint32_t *)bufp; |
| uint32_t *endp = (uint32_t *)(bufp + tape_recsz); |
| uint32_t *p; |
| uint32_t accum; |
| |
| if (contextp->dc_recchksumpr && INT_GET(rechdrp->ischecksum, ARCH_CONVERT)) { |
| accum = 0; |
| for (p = beginp; p < endp; p++) { |
| accum += INT_GET(*p, ARCH_CONVERT); |
| } |
| return accum == 0 ? BOOL_TRUE : BOOL_FALSE; |
| } else { |
| return BOOL_TRUE; |
| } |
| } |
| |
| /* to trace rmt operations |
| */ |
| #ifdef RMTDBG |
| static int |
| dbgrmtopen(char *path, int flags) |
| { |
| int rval; |
| rval = rmtopen(path, flags); |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("RMTOPEN( %s, %d ) returns %d: errno=%d (%s)\n"), |
| path, |
| flags, |
| rval, |
| errno, |
| strerror(errno)); |
| return rval; |
| } |
| static int |
| dbgrmtclose(int fd) |
| { |
| int rval; |
| rval = rmtclose(fd); |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("RMTCLOSE( %d ) returns %d: errno=%d (%s)\n"), |
| fd, |
| rval, |
| errno, |
| strerror(errno)); |
| return rval; |
| } |
| static int |
| dbgrmtioctl(int fd, int op, void *arg) |
| { |
| int rval; |
| rval = rmtioctl(fd, op, arg); |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("RMTIOCTL( %d, %d, 0x%x ) returns %d: errno=%d (%s)\n"), |
| fd, |
| op, |
| arg, |
| rval, |
| errno, |
| strerror(errno)); |
| return rval; |
| } |
| static int |
| dbgrmtread(int fd, void *p, uint sz) |
| { |
| int rval; |
| rval = rmtread(fd, p, sz); |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("RMTREAD( %d, 0x%x, %u ) returns %d: errno=%d (%s)\n"), |
| fd, |
| p, |
| sz, |
| rval, |
| errno, |
| strerror(errno)); |
| return rval; |
| } |
| static int |
| dbgrmtwrite(int fd, void *p, uint sz) |
| { |
| int rval; |
| rval = rmtwrite(fd, p, sz); |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("RMTWRITE( %d, 0x%x, %u ) returns %d: errno=%d (%s)\n"), |
| fd, |
| p, |
| sz, |
| rval, |
| errno, |
| strerror(errno)); |
| return rval; |
| } |
| #endif /* RMTDBG */ |
| |
| /* display_access_failed_message() |
| * Print tape device open/access failed message. |
| * |
| * RETURNS: |
| * void |
| */ |
| static void |
| display_access_failed_message(drive_t *drivep) |
| { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, _( |
| "attempt to access/open remote " |
| "tape drive %s failed: %d (%s)\n"), |
| drivep->d_pathname, |
| errno, |
| strerror(errno)); |
| return; |
| } |
| |
| /* prepare_drive - called by begin_read if drive device not open. |
| * determines record size and sets block size if fixed block device. |
| * determines other drive attributes. determines if any previous |
| * xfsdumps on media. |
| */ |
| static int |
| prepare_drive(drive_t *drivep) |
| { |
| drive_context_t *contextp; |
| bool_t ok; |
| ix_t try; |
| ix_t maxtries; |
| bool_t changedblkszpr; |
| int rval; |
| |
| /* get pointer to drive context |
| */ |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| /* retry: */ |
| if (cldmgr_stop_requested()) { |
| return DRIVE_ERROR_STOP; |
| } |
| |
| /* shouldn't be here if drive is open |
| */ |
| assert(contextp->dc_fd == -1); |
| |
| mlog(MLOG_VERBOSE | MLOG_DRIVE, |
| _("preparing drive\n")); |
| |
| /* determine if tape is present or write protected. try several times. |
| * if not present or write-protected during dump, return. |
| */ |
| maxtries = 15; |
| for (try = 1 ; ; sleep(10), try++) { |
| if (cldmgr_stop_requested()) { |
| return DRIVE_ERROR_STOP; |
| } |
| |
| /* open the drive |
| */ |
| ok = Open(drivep); |
| if (!ok) { |
| if (errno != EBUSY) { |
| display_access_failed_message(drivep); |
| return DRIVE_ERROR_DEVICE; |
| } else { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "open drive returns EBUSY\n"); |
| if (try >= maxtries) { |
| mlog(MLOG_TRACE | MLOG_DRIVE, |
| "giving up waiting for drive " |
| "to indicate online\n"); |
| return DRIVE_ERROR_MEDIA; |
| } |
| Close(drivep); |
| continue; |
| } |
| } else |
| break; |
| } |
| |
| /* determine tape capabilities. this will set the drivep->d_capabilities |
| * and contextp->dc_{...}blksz and dc_isQICpr, as well as recommended |
| * mark separation and media file size. |
| */ |
| ok = get_tpcaps(drivep); |
| if (!ok) { |
| return DRIVE_ERROR_DEVICE; |
| } |
| |
| |
| /* establish the initial block and record sizes we will try. |
| * if unable to ask drive about fixed block sizes, we will |
| * guess. |
| */ |
| tape_blksz = cmdlineblksize; |
| if (tape_blksz > STAPE_MAX_RECSZ) { |
| tape_blksz = STAPE_MAX_RECSZ; |
| } |
| if (contextp->dc_isQICpr == BOOL_TRUE) |
| tape_recsz = STAPE_MIN_MAX_BLKSZ; |
| else |
| tape_recsz = tape_blksz; |
| |
| /* if the overwrite option was specified, return. |
| */ |
| if (contextp->dc_overwritepr) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "Overwrite option specified.\n"); |
| return DRIVE_ERROR_OVERWRITE; |
| } |
| |
| /* loop trying to read a record. begin with the record size |
| * calculated above. if it appears we have selected the wrong |
| * block size and if we are able to alter the fixed block size, |
| * and the record size we tried initially was not less than |
| * the minmax block size, change the block size to minmax and |
| * try to read a one block record again. |
| */ |
| maxtries = 5; |
| changedblkszpr = BOOL_FALSE; |
| |
| for (try = 1 ; ; try++) { |
| int nread; |
| int saved_errno; |
| |
| if (cldmgr_stop_requested()) { |
| return DRIVE_ERROR_STOP; |
| } |
| |
| /* bail out if we've tried too many times |
| */ |
| if (try > maxtries) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("giving up attempt to determining " |
| "tape record size\n")); |
| return DRIVE_ERROR_MEDIA; |
| } |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "determining tape record size: trying %d (0x%x) bytes\n", |
| tape_recsz, |
| tape_recsz); |
| |
| /* read a record. use the first ring buffer |
| */ |
| saved_errno = 0; |
| nread = Read(drivep, |
| contextp->dc_recp, |
| tape_recsz, |
| &saved_errno); |
| assert(saved_errno == 0 || nread < 0); |
| |
| /* RMT can require a retry |
| */ |
| if (saved_errno == EAGAIN) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "read returned EAGAIN: retrying\n"); |
| continue; |
| } |
| |
| /* block size is bigger than buffer; should never happen |
| */ |
| if (saved_errno == EINVAL) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "read returned EINVAL: " |
| "trying new record size\n"); |
| goto largersize; |
| } |
| |
| /* block size is bigger than buffer; should never happen |
| */ |
| if (saved_errno == ENOMEM) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "read returned ENOMEM: " |
| "trying new record size\n"); |
| goto largersize; |
| } |
| |
| |
| /* I/O error |
| */ |
| if (saved_errno == EIO) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "read returned EIO: will reopen, " |
| "and try again\n"); |
| Close(drivep); |
| ok = Open(drivep); |
| if (!ok) { |
| display_access_failed_message(drivep); |
| return DRIVE_ERROR_DEVICE; |
| } |
| #ifdef RESTORE |
| continue; |
| #endif |
| #ifdef DUMP |
| if (3 == try) |
| return DRIVE_ERROR_BLANK; /* sun rmt returns EIO for blank media */ |
| else |
| continue; |
| #endif |
| } |
| |
| /* ENOSPC |
| */ |
| if (saved_errno == ENOSPC) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "read returned ENOSPC: will reopen, " |
| "and try again\n"); |
| Close(drivep); |
| ok = Open(drivep); |
| if (!ok) { |
| display_access_failed_message(drivep); |
| return DRIVE_ERROR_DEVICE; |
| } |
| #ifdef RESTORE |
| continue; |
| #endif |
| #ifdef DUMP |
| if (3 == try) |
| /* IRIX rmt returns ENOSPC for blank media */ |
| /* It reads to the EOD */ |
| return DRIVE_ERROR_BLANK; |
| else |
| continue; |
| #endif |
| } |
| |
| if (nread == (int)tape_recsz) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "nread == selected blocksize " |
| "indicates correct blocksize found\n"); |
| goto checkhdr; |
| } |
| |
| /* short read */ |
| if ((saved_errno == 0) && (nread < (int)tape_recsz)) { |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "nread < selected blocksize " |
| "indicates incorrect blocksize found " |
| "or foreign media\n"); |
| return DRIVE_ERROR_FOREIGN; |
| } |
| |
| |
| /* if we fell through the seive, code is wrong. |
| * display useful info and abort |
| */ |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, _( |
| "unexpected tape error: " |
| "errno %d " |
| "nread %d " |
| "blksz %d " |
| "recsz %d " |
| "\n"), |
| saved_errno, |
| nread, |
| tape_blksz, |
| tape_recsz, |
| 0); |
| |
| |
| |
| /*return DRIVE_ERROR_CORE; */ |
| return DRIVE_ERROR_MEDIA; |
| |
| checkhdr: |
| rval = validate_media_file_hdr(drivep); |
| if (rval) { |
| if (rval == DRIVE_ERROR_VERSION) { |
| global_hdr_t *grhdrp = drivep->d_greadhdrp; |
| mlog(MLOG_NORMAL | MLOG_DRIVE, _( |
| "media file header version (%d) " |
| "invalid: advancing\n"), |
| grhdrp->gh_version); |
| continue; |
| } else { |
| if (isefsdump(drivep)) { |
| mlog(MLOG_NORMAL |
| | |
| MLOG_WARNING |
| | |
| MLOG_DRIVE, |
| _("may be an EFS dump at BOT\n")); |
| } else |
| /* Check if the tape was erased by us */ |
| if (isxfsdumperasetape(drivep)) { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("This tape was erased earlier " |
| "by xfsdump.\n")); |
| (void)rewind_and_verify(drivep); |
| return DRIVE_ERROR_BLANK; |
| } else { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("bad media file header at BOT " |
| "indicates foreign or " |
| "corrupted tape\n")); |
| } |
| (void)rewind_and_verify(drivep); |
| ok = set_best_blk_and_rec_sz(drivep); |
| if (!ok) { |
| return DRIVE_ERROR_DEVICE; |
| } |
| return DRIVE_ERROR_FOREIGN; |
| } |
| } else { |
| drive_hdr_t *drhdrp; |
| rec_hdr_t *tprhdrp; |
| drhdrp = drivep->d_readhdrp; |
| tprhdrp = (rec_hdr_t *)drhdrp->dh_specific; |
| assert(tprhdrp->recsize >= 0); |
| tape_recsz = (size_t)tprhdrp->recsize; |
| break; |
| } |
| |
| largersize: |
| /* we end up here if we want to try a new larger record size |
| * because the last one was not big enough for the tape block |
| */ |
| if (changedblkszpr) { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("cannot determine tape block size " |
| "after two tries\n")); |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("assuming media is corrupt " |
| "or contains non-xfsdump data\n")); |
| ok = set_best_blk_and_rec_sz(drivep); |
| if (!ok) { |
| return DRIVE_ERROR_DEVICE; |
| } |
| return DRIVE_ERROR_FOREIGN; |
| } |
| /* Make it as large as we can go |
| */ |
| if (tape_recsz != STAPE_MAX_RECSZ) { |
| tape_recsz = STAPE_MAX_RECSZ; |
| if (!contextp->dc_isQICpr) { |
| tape_blksz = tape_recsz;; |
| } |
| changedblkszpr = BOOL_TRUE; |
| } else { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| _("cannot determine tape block size\n")); |
| return DRIVE_ERROR_MEDIA; |
| } |
| continue; |
| |
| } |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "read first record of first media file encountered on media: " |
| "recsz == %u\n", |
| tape_recsz); |
| |
| /* calculate maximum bytes lost without error at end of tape |
| */ |
| calc_max_lost(drivep); |
| |
| contextp->dc_iocnt = 1; |
| return 0; |
| } |
| |
| /* if BOOL_FALSE returned, errno is valid |
| */ |
| static bool_t |
| Open(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| int oflags; |
| |
| #ifdef DUMP |
| oflags = O_RDWR; |
| #endif /* DUMP */ |
| #ifdef RESTORE |
| oflags = O_RDONLY; |
| #endif /* RESTORE */ |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "tape op: opening drive\n"); |
| |
| assert(contextp->dc_fd == -1); |
| |
| errno = 0; |
| contextp->dc_fd = open(drivep->d_pathname, oflags); |
| |
| if (contextp->dc_fd <= 0) { |
| return BOOL_FALSE; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static void |
| Close(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "tape op: closing drive\n"); |
| |
| assert(contextp->dc_fd >= 0); |
| |
| (void)close(contextp->dc_fd); |
| |
| contextp->dc_fd = -1; |
| } |
| |
| static int |
| Read(drive_t *drivep, char *bufp, size_t cnt, int *errnop) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| int nread; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "tape op: reading %u bytes\n", |
| cnt); |
| |
| assert(contextp->dc_fd >= 0); |
| assert(bufp); |
| *errnop = 0; |
| errno = 0; |
| nread = read(contextp->dc_fd, (void *)bufp, cnt); |
| if (nread < 0) { |
| *errnop = errno; |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "tape op read of %u bytes failed: errno == %d (%s)\n", |
| cnt, |
| errno, |
| strerror(errno)); |
| } else if (nread != (int)cnt) { |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "tape op read of %u bytes short: nread == %d\n", |
| cnt, |
| nread); |
| } else { |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "tape op read of %u bytes successful\n", |
| cnt); |
| } |
| |
| return nread; |
| } |
| |
| static int |
| Write(drive_t *drivep, char *bufp, size_t cnt, int *errnop) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| int nwritten; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "tape op: writing %u bytes\n", |
| cnt); |
| |
| assert(contextp->dc_fd >= 0); |
| assert(bufp); |
| *errnop = 0; |
| errno = 0; |
| nwritten = write(contextp->dc_fd, (void *)bufp, cnt); |
| if (nwritten < 0) { |
| *errnop = errno; |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "tape op write of %u bytes failed: errno == %d (%s)\n", |
| cnt, |
| errno, |
| strerror(errno)); |
| } else if (nwritten != (int)cnt) { |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "tape op write of %u bytes short: nwritten == %d\n", |
| cnt, |
| nwritten); |
| } else { |
| mlog(MLOG_NITTY | MLOG_DRIVE, |
| "tape op write of %u bytes successful\n", |
| cnt); |
| } |
| |
| return nwritten; |
| } |
| |
| /* validate a record header, log any anomolies, and return appropriate |
| * indication. |
| */ |
| static int |
| record_hdr_validate(drive_t *drivep, char *bufp, bool_t chkoffpr) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| global_hdr_t *grhdrp = drivep->d_greadhdrp; |
| rec_hdr_t rechdr; |
| rec_hdr_t *rechdrp = &rechdr; |
| rec_hdr_t *tmprh = (rec_hdr_t *)bufp; |
| |
| if (!tape_rec_checksum_check(contextp, bufp)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("record %lld corrupt: bad record checksum\n"), |
| contextp->dc_iocnt - 1); |
| return DRIVE_ERROR_CORRUPTION; |
| |
| } |
| |
| xlate_rec_hdr(tmprh, rechdrp, 1); |
| |
| if (rechdrp->magic != STAPE_MAGIC) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("record %lld corrupt: bad magic number\n"), |
| contextp->dc_iocnt - 1); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| if (uuid_is_null(rechdrp->dump_uuid)) { |
| |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("record %lld corrupt: null dump id\n"), |
| contextp->dc_iocnt - 1); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| if (uuid_compare(grhdrp->gh_dumpid, |
| rechdrp->dump_uuid) != 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("record %lld corrupt: dump id mismatch\n"), |
| contextp->dc_iocnt - 1); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| /* can't check size field of record header, since |
| * 5.3 version of xfsdump did not set it properly. |
| */ |
| #ifdef RECSZCHK |
| if ((size_t)rechdrp->recsize != tape_recsz) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, _( |
| "record %lld corrupt: incorrect record size in header\n"), |
| contextp->dc_iocnt - 1); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| #else /* RECSZCHK */ |
| mlog((MLOG_NITTY + 1) | MLOG_NOTE | MLOG_DRIVE, |
| "record %lld header record size %d (0x%x)\n", |
| contextp->dc_iocnt - 1, |
| rechdrp->recsize, |
| rechdrp->recsize); |
| #endif /* RECSZCHK */ |
| |
| if (rechdrp->file_offset % (off64_t)tape_recsz) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("record %lld corrupt: record offset in header " |
| "not a multiple of record size\n"), |
| contextp->dc_iocnt - 1); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| if (chkoffpr |
| && |
| rechdrp->file_offset |
| != |
| (contextp->dc_iocnt - 1) * (off64_t)tape_recsz) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("record %lld corrupt: " |
| "incorrect record offset in header (0x%llx)\n"), |
| contextp->dc_iocnt - 1, |
| rechdrp->file_offset); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| if (rechdrp->rec_used > tape_recsz |
| || |
| rechdrp->rec_used < STAPE_HDR_SZ) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE, |
| _("record %lld corrupt: " |
| "incorrect record padding offset in header\n"), |
| contextp->dc_iocnt - 1); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| memcpy(tmprh, rechdrp, sizeof(*rechdrp)); |
| |
| return 0; |
| } |
| |
| /* do a read, determine DRIVE_ERROR_... if failure, and return failure code. |
| * return 0 on success. |
| */ |
| static int |
| read_record(drive_t *drivep, char *bufp) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| int nread; |
| int saved_errno; |
| int rval; |
| |
| nread = Read(drivep, bufp, tape_recsz, &saved_errno); |
| |
| if (nread == (int)tape_recsz) { |
| contextp->dc_iocnt++; |
| rval = record_hdr_validate(drivep, bufp, BOOL_TRUE); |
| return rval; |
| } |
| |
| /* check for a blank tape/EOD. |
| */ |
| if ((nread == 0) /* takes care of sun */ |
| || /* now handle SGI */ |
| (nread < 0 && saved_errno == ENOSPC)) { |
| mlog(MLOG_NORMAL | MLOG_DRIVE, |
| #ifdef DUMP |
| _("read_record encountered EOD : assuming blank media\n") |
| #endif |
| #ifdef RESTORE |
| _("read_record encountered EOD : end of data\n") |
| #endif |
| ); |
| #ifdef DUMP |
| return DRIVE_ERROR_BLANK; |
| #endif |
| #ifdef RESTORE |
| return DRIVE_ERROR_EOD; |
| #endif |
| } |
| |
| /* some other error |
| */ |
| if (saved_errno == EIO) { |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, |
| _("unexpected EIO error attempting to read record\n")); |
| #ifdef RESTORE |
| return DRIVE_ERROR_EOM; |
| #endif |
| #ifdef DUMP |
| return DRIVE_ERROR_CORRUPTION; |
| #endif |
| } |
| |
| /* short read |
| */ |
| if (nread >= 0) { |
| assert(nread <= (int)tape_recsz); |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "short read record %lld (nread == %d)\n", |
| contextp->dc_iocnt, |
| nread); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| /* some other error |
| */ |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, |
| _("unexpected error attempting to read record %lld: " |
| "read returns %d, errno %d (%s)\n"), |
| contextp->dc_iocnt, |
| nread, |
| saved_errno, |
| strerror(saved_errno)); |
| return DRIVE_ERROR_CORRUPTION; |
| } |
| |
| static int |
| ring_read(void *clientctxp, char *bufp) |
| { |
| return read_record((drive_t *)clientctxp, bufp); |
| } |
| |
| /* gets another record IF dc_recp is NULL |
| */ |
| static int |
| getrec(drive_t *drivep) |
| { |
| drive_context_t *contextp; |
| contextp = (drive_context_t *)drivep->d_contextp; |
| |
| while (!contextp->dc_recp) { |
| rec_hdr_t *rechdrp; |
| if (contextp->dc_singlethreadedpr) { |
| int rval; |
| contextp->dc_recp = contextp->dc_bufp; |
| rval = read_record(drivep, contextp->dc_recp); |
| if (rval) { |
| contextp->dc_errorpr = BOOL_TRUE; |
| return rval; |
| } |
| } else { |
| contextp->dc_msgp = Ring_get(contextp->dc_ringp); |
| switch(contextp->dc_msgp->rm_stat) { |
| case RING_STAT_OK: |
| contextp->dc_recp = contextp->dc_msgp->rm_bufp; |
| break; |
| case RING_STAT_INIT: |
| case RING_STAT_NOPACK: |
| case RING_STAT_IGNORE: |
| contextp->dc_msgp->rm_op = RING_OP_READ; |
| Ring_put(contextp->dc_ringp, |
| contextp->dc_msgp); |
| contextp->dc_msgp = 0; |
| continue; |
| case RING_STAT_ERROR: |
| contextp->dc_errorpr = BOOL_TRUE; |
| return contextp->dc_msgp->rm_rval; |
| default: |
| assert(0); |
| contextp->dc_errorpr = BOOL_TRUE; |
| return DRIVE_ERROR_CORE; |
| } |
| } |
| rechdrp = (rec_hdr_t *)contextp->dc_recp; |
| contextp->dc_recendp = contextp->dc_recp + tape_recsz; |
| contextp->dc_dataendp = contextp->dc_recp |
| + |
| rechdrp->rec_used; |
| contextp->dc_nextp = contextp->dc_recp |
| + |
| STAPE_HDR_SZ; |
| assert(contextp->dc_nextp <= contextp->dc_dataendp); |
| } |
| |
| return 0; |
| } |
| |
| /* do a write, determine DRIVE_ERROR_... if failure, and return failure code. |
| * return 0 on success. |
| */ |
| /*ARGSUSED*/ |
| static int |
| write_record(drive_t *drivep, char *bufp, bool_t chksumpr, bool_t xlatepr) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| int nwritten; |
| int saved_errno; |
| int rval; |
| |
| if (xlatepr) { |
| rec_hdr_t rechdr; |
| memcpy(&rechdr, bufp, sizeof(rechdr)); |
| xlate_rec_hdr(&rechdr, (rec_hdr_t *)bufp, 1); |
| } |
| |
| if (chksumpr) { |
| tape_rec_checksum_set(contextp, bufp); |
| } |
| |
| nwritten = Write(drivep, bufp, tape_recsz, &saved_errno); |
| |
| if (nwritten == (int)tape_recsz) { |
| contextp->dc_iocnt++; |
| return 0; |
| } |
| |
| rval = determine_write_error(nwritten, saved_errno); |
| assert(rval); |
| |
| return rval; |
| } |
| |
| static int |
| ring_write(void *clientctxp, char *bufp) |
| { |
| return write_record((drive_t *)clientctxp, bufp, BOOL_TRUE, BOOL_TRUE); |
| } |
| |
| static ring_msg_t * |
| Ring_get(ring_t *ringp) |
| { |
| ring_msg_t *msgp; |
| |
| mlog((MLOG_NITTY + 1) | MLOG_DRIVE, |
| "ring op: get\n"); |
| |
| msgp = ring_get(ringp); |
| return msgp; |
| } |
| |
| static void |
| Ring_put(ring_t *ringp, ring_msg_t *msgp) |
| { |
| mlog((MLOG_NITTY + 1) | MLOG_DRIVE, |
| "ring op: put %d\n", |
| msgp->rm_op); |
| |
| ring_put(ringp, msgp); |
| } |
| |
| static void |
| Ring_reset(ring_t *ringp, ring_msg_t *msgp) |
| { |
| mlog((MLOG_NITTY + 1) | MLOG_DRIVE, |
| "ring op: reset\n"); |
| |
| assert(ringp); |
| |
| ring_reset(ringp, msgp); |
| } |
| |
| /* a simple heuristic to calculate the maximum uncertainty |
| * of how much data actually was written prior to encountering |
| * end of media. |
| */ |
| static void |
| calc_max_lost(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| if (contextp->dc_isQICpr) { |
| contextp->dc_lostrecmax = 1; |
| } else { |
| contextp->dc_lostrecmax = 2; |
| } |
| |
| } |
| |
| static void |
| display_ring_metrics(drive_t *drivep, int mlog_flags) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| ring_t *ringp = contextp->dc_ringp; |
| char bufszbuf[16]; |
| char *bufszsfxp; |
| |
| if (tape_recsz == STAPE_MIN_MAX_BLKSZ) { |
| assert(!(STAPE_MIN_MAX_BLKSZ % 0x400)); |
| sprintf(bufszbuf, "%u", STAPE_MIN_MAX_BLKSZ / 0x400); |
| assert(strlen(bufszbuf) < sizeof(bufszbuf)); |
| bufszsfxp = _("KB"); |
| } else if (tape_recsz == STAPE_MAX_RECSZ) { |
| assert(!(STAPE_MAX_RECSZ % 0x100000)); |
| sprintf(bufszbuf, "%u", STAPE_MAX_RECSZ / 0x100000); |
| assert(strlen(bufszbuf) < sizeof(bufszbuf)); |
| bufszsfxp = _("MB"); |
| } else { |
| sprintf(bufszbuf, "%u", (unsigned int)(tape_recsz / 0x400)); |
| bufszsfxp = _("KB"); |
| } |
| |
| mlog(mlog_flags, _( |
| "I/O metrics: " |
| "%u by %s%s %sring; " |
| "%lld/%lld (%.0lf%%) records streamed; " |
| "%.0lfB/s\n"), |
| contextp->dc_ringlen, |
| bufszbuf, |
| bufszsfxp, |
| contextp->dc_ringpinnedpr ? _("pinned ") : "", |
| ringp->r_slave_msgcnt - ringp->r_slave_blkcnt, |
| ringp->r_slave_msgcnt, |
| percent64(ringp->r_slave_msgcnt - ringp->r_slave_blkcnt, |
| ringp->r_slave_msgcnt), |
| (double)(ringp->r_all_io_cnt) |
| * |
| (double)tape_recsz |
| / |
| (double)(time(0) - ringp->r_first_io_time)); |
| } |
| |
| #ifdef CLRMTAUD |
| static uint32_t |
| #else /* CLRMTAUD */ |
| static short |
| #endif /* CLRMTAUD */ |
| rewind_and_verify(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| ix_t try; |
| int rval; |
| |
| rval = mt_op(contextp->dc_fd, MTREW, 0); |
| for (try = 1 ; ; try++) { |
| if (rval) { |
| sleep(1); |
| rval = mt_op(contextp->dc_fd, MTREW, 0); |
| } else |
| return 0; |
| |
| if (try >= MTOP_TRIES_MAX) { |
| return 0; |
| } |
| sleep(1); |
| } |
| /* NOTREACHED */ |
| } |
| |
| #ifdef CLRMTAUD |
| static uint32_t |
| #else /* CLRMTAUD */ |
| static short |
| #endif /* CLRMTAUD */ |
| erase_and_verify(drive_t *drivep) |
| { |
| char *tempbufp; |
| int saved_errno; |
| |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| mlog(MLOG_DEBUG | MLOG_DRIVE, |
| "rmt : erase_and_verify\n"); |
| |
| /* Erase is not standard rmt. So we cannot use the line below. |
| |
| (void)mt_op(contextp->dc_fd, MTERASE, 0); |
| |
| * So write a record of zeros to fake erase . Poor man's erase! |
| * We put the ERASE_MAGIC string at the beginning so we can |
| * detect if we have erased the tape. |
| */ |
| |
| tempbufp = (char *) calloc(1, (size_t)tape_recsz); |
| strcpy(tempbufp, ERASE_MAGIC); |
| Write(drivep, tempbufp, tape_recsz, &saved_errno); |
| free(tempbufp); |
| |
| return 0; |
| } |
| |
| #ifdef CLRMTAUD |
| static uint32_t |
| #else /* CLRMTAUD */ |
| static short |
| #endif /* CLRMTAUD */ |
| bsf_and_verify(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| return mt_op(contextp->dc_fd, MTBSF, 1); |
| } |
| |
| #ifdef CLRMTAUD |
| static uint32_t |
| #else /* CLRMTAUD */ |
| static short |
| #endif /* CLRMTAUD */ |
| fsf_and_verify(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| (void)mt_op(contextp->dc_fd, MTFSF, 1); |
| |
| return 0; |
| } |
| |
| static bool_t |
| set_best_blk_and_rec_sz(drive_t *drivep) |
| { |
| drive_context_t *contextp = (drive_context_t *)drivep->d_contextp; |
| |
| if (contextp->dc_isQICpr == BOOL_TRUE) |
| tape_recsz = STAPE_MIN_MAX_BLKSZ; |
| else |
| tape_recsz = cmdlineblksize; |
| |
| return BOOL_TRUE; |
| } |
| |
| static bool_t |
| isefsdump(drive_t *drivep) |
| { |
| int32_t *efshdrp = (int32_t *)drivep->d_greadhdrp; |
| int32_t efsmagic = efshdrp[6]; |
| if (efsmagic == 60011 |
| || |
| efsmagic == 60012) { |
| return BOOL_TRUE; |
| } else { |
| return BOOL_FALSE; |
| } |
| } |
| |
| static bool_t |
| isxfsdumperasetape(drive_t *drivep) |
| { |
| if (!strcmp((char *)drivep->d_greadhdrp, ERASE_MAGIC)) |
| return BOOL_TRUE; |
| else |
| return BOOL_FALSE; |
| } |