blob: 878a5d7c76da773180dee2940f1da1d24084318b [file] [log] [blame]
/*
* 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_scsitape.c - drive strategy for all scsi tape devices
*/
/* 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 /* 2Mb */
/* 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.
* i.e. we use this in the remote case.
*/
#define STAPE_MIN_MAX_BLKSZ 0x3c000 /* 240K, 245760 */
/* On linux, not all kernels can handle block sizes of 2Mb
* so we use a reduced amount.
*/
#define STAPE_MAX_LINUX_RECSZ 0x100000
/* 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. worker may have read ahead /
* written behind.
*/
int dc_fd;
/* drive file descriptor. -1 when not open
*/
bool_t dc_isrmtpr;
/* TRUE if drive being accessed via RMT
*/
bool_t dc_isvarpr;
/* TRUE if variable block size device
*/
bool_t dc_cangetblkszpr;
/* TRUE if can get blksz info from driver
*/
bool_t dc_cansetblkszpr;
/* TRUE if can set blksz info to driver
*/
size_t dc_maxblksz;
/* maximum block size.
*/
size_t dc_origcurblksz;
/* Save original curblksz to restore on quit.
*/
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 worker)
*/
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;
/* 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)
/* macros to interpret tape status information returned by reference from
* mt_get_status().
*/
/* tape is positioned at end-of-tape */
#define IS_EOT(mtstat) GMT_EOT(mtstat)
/* tape is positioned at beginning-of-tape */
#define IS_BOT(mtstat) GMT_BOT(mtstat)
/* the tape is write protected */
#define IS_WPROT(mtstat) GMT_WR_PROT(mtstat)
/* the tape drive is online */
#define IS_ONL(mtstat) GMT_ONLINE(mtstat)
/* the tape is positioned at the end of user data */
#define IS_EOD(mtstat) GMT_EOD(mtstat)
/* the tape is positioned at at file mark */
#define IS_FMK(mtstat) GMT_EOF(mtstat)
/* tape is positioned at early warning */
/* not supported by linux tape drivers */
#define IS_EW(mtstat) (0)
typedef long mtstat_t;
/* 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 mt_blkinfo(int, struct mtblkinfo *);
static bool_t mt_get_fileno(drive_t *, long *);
static bool_t mt_get_status(drive_t *, mtstat_t *);
static int determine_write_error(drive_t *, 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 void status_failed_message(drive_t *);
static bool_t get_tpcaps(drive_t *);
static bool_t set_fixed_blksz(drive_t *, size_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 quick_backup(drive_t *drivep,
drive_context_t *contextp,
ix_t skipcnt);
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);
static mtstat_t rewind_and_verify(drive_t *drivep);
static mtstat_t erase_and_verify(drive_t *drivep);
static mtstat_t bsf_and_verify(drive_t *drivep);
static mtstat_t fsf_and_verify(drive_t *drivep);
static void calc_best_blk_and_rec_sz(drive_t *drivep);
static bool_t set_best_blk_and_rec_sz(drive_t *drivep);
static bool_t isefsdump(drive_t *drivep);
static int get_driver_character_major(const char *);
static void map_ts_status(struct mtget *, struct mtget_sgi);
/* 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 */
/* definition of locally defined global variables ****************************/
/* tape driver
*/
int TS_ISDRIVER=0;
/* scsitape drive strategy. referenced by drive.c
*/
drive_strategy_t drive_strategy_scsitape = {
DRIVE_STRATEGY_SCSITAPE, /* ds_id */
"scsi tape (drive_scsitape)", /* 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 ****************************/
static bool_t
is_scsi_driver(char *pathname)
{
char rp[PATH_MAX];
struct stat64 statbuf;
int dev_major;
if (realpath(pathname, rp) == NULL) {
return BOOL_FALSE;
}
if (stat64(pathname, &statbuf) == -1) {
return BOOL_FALSE;
}
if (!S_ISCHR(statbuf.st_mode)) {
return BOOL_FALSE;
}
/* In the past, this routine would look at the pathname of the
* file to determine if we had a tape device or not. That method
* worked fine for tape devices residing in one of the directories
* in the list of 'valid' pathnames. However, that method did not
* work for tape applications like TMF and OpenVault, which allow
* the user the flexibility of creating tape deivces with pathnames
* of their choice.
*
* In order to provide support for such applications, this routine
* will now look at the major number of the character device to
* determine whether we have a tape device or not. Major number
* 9 is reserved for ST, while major numbers for TS and TMF are not
* officially registered on Linux (and may be subject to change).
* For this reason, we will call the get_driver_character_major()
* routine to get the major numbers for TS and TMF (providing the
* drivers are loaded). We will also look up the major number for
* ST, for sanity and consistency. We can compare the major number
* of the target device to the major number of each of the tape
* drivers to determine 1) if we have a tape device, and 2) which
* tape driver the device is using.
*/
dev_major = major(statbuf.st_rdev);
if (dev_major == get_driver_character_major("st")) {
return BOOL_TRUE;
}
else if (dev_major == get_driver_character_major("ts") ||
dev_major == get_driver_character_major("tmf")) {
TS_ISDRIVER = 1;
return BOOL_TRUE;
}
else {
return BOOL_FALSE;
}
}
/* strategy match - determines if this is the right strategy
*/
/* ARGSUSED */
static int
ds_match(int argc, char *argv[], drive_t *drivep)
{
struct mtget mt_stat;
int fd;
/* heuristics to determine if this is a drive.
*/
if (!strcmp(drivep->d_pathname, "stdio")) {
return -10;
}
if (strchr(drivep->d_pathname, ':')) {
errno = 0;
fd = open(drivep->d_pathname, O_RDONLY);
if (fd < 0) {
return -10;
}
if (ioctl(fd, MTIOCGET, &mt_stat) < 0) {
close(fd);
return -10;
}
close(fd);
return 10;
} else {
if (is_scsi_driver(drivep->d_pathname)) {
return 10;
}
else {
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_filesz = 0;
contextp->dc_isQICpr = BOOL_FALSE;
#ifdef DUMP
contextp->dc_filesz = 0;
#endif
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;
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);
break;
#ifdef DUMP
case GETOPT_OVERWRITE:
contextp->dc_overwritepr = BOOL_TRUE;
break;
case GETOPT_FILESZ:
if (!optarg || optarg[0] == '-') {
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("-%c argument missing\n"),
c);
return BOOL_FALSE;
}
/* given in Mb */
contextp->dc_filesz = (off64_t)atoi(optarg) * 1024 * 1024;
if (contextp->dc_filesz <= 0) {
mlog(MLOG_NORMAL | MLOG_ERROR | 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;
}
}
/* scan drive device pathname to see if remote tape
*/
if (strchr(drivep->d_pathname, ':')) {
contextp->dc_isrmtpr = BOOL_TRUE;
} else {
contextp->dc_isrmtpr = 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.
*/
if (contextp->dc_isrmtpr) {
contextp->dc_cangetblkszpr = BOOL_FALSE;
contextp->dc_cansetblkszpr = BOOL_FALSE;
contextp->dc_isvarpr = BOOL_TRUE;
contextp->dc_isQICpr = BOOL_FALSE;
} else {
contextp->dc_cangetblkszpr = BOOL_UNKNOWN;
contextp->dc_cansetblkszpr = BOOL_UNKNOWN;
contextp->dc_isvarpr = BOOL_UNKNOWN;
}
/* 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_APPEND
|
DRIVE_CAP_NEXTMARK
|
DRIVE_CAP_EJECT
|
DRIVE_CAP_READ
|
DRIVE_CAP_REMOVABLE
|
DRIVE_CAP_ERASE
;
/* initialize origcurblksz so we only save it once
*/
contextp->dc_origcurblksz = 0;
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,
"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 worker 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,
"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,
"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,
"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,
"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,
"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,
"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,
"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;
mtstat_t mtstat;
size_t tailsz;
int rval;
bool_t ok;
/* assert protocol being followed.
*/
assert(contextp->dc_mode == OM_READ);
assert(!contextp->dc_errorpr);
assert(!contextp->dc_ownedp);
mlog(MLOG_DEBUG | MLOG_DRIVE,
"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;
}
ok = mt_get_status(drivep, &mtstat);
if (!ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
if (IS_FMK(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"encountered EOF attempting to read record\n");
return DRIVE_ERROR_EOF;
}
if (IS_EOD(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"encountered EOD attempting to read record\n");
return DRIVE_ERROR_EOD;
}
if (IS_EOT(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"encountered EOM attempting to read record\n");
return DRIVE_ERROR_EOM;
}
if (IS_EW(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"encountered EW attempting to read record\n");
return DRIVE_ERROR_EOM;
}
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 %d (%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,
"drive op: end read\n");
/* assert protocol being followed
*/
assert(contextp->dc_mode == OM_READ);
assert(!contextp->dc_ownedp);
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;
mtstat_t mtstat;
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,
"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 scsi tape requires reading first.
*/
assert(contextp->dc_fd != -1);
/* get tape device status. verify tape is positioned
*/
if (!mt_get_status(drivep, &mtstat)) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
if (IS_EOT(mtstat)) {
return DRIVE_ERROR_EOM;
}
if (IS_EW(mtstat) && !(IS_BOT(mtstat))) {
return DRIVE_ERROR_EOM;
}
/* 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,
"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,
"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,
"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,
"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,
"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;
mtstat_t mtstat;
bool_t ok;
weofrval = mt_op(contextp->dc_fd, MTWEOF, 1);
if (!weofrval) {
ok = mt_get_status(drivep, &mtstat);
if (!ok) {
status_failed_message(drivep);
mtstat = 0;
rval = DRIVE_ERROR_DEVICE;
}
} else {
mtstat = 0;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"MTWEOF returned %d: errno == %d (%s)\n",
weofrval,
errno,
strerror(errno));
}
if (weofrval || IS_EW(mtstat) || IS_EOT(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"hit EOM trying to write file mark\n");
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;
mtstat_t mtstat;
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,
"drive op: fsf: count %d\n",
count);
assert(count);
assert(contextp->dc_mode == OM_NONE);
/* get tape status
*/
if (!mt_get_status(drivep, &mtstat)) {
status_failed_message(drivep);
*statp = DRIVE_ERROR_DEVICE;
return 0;
}
for (i = 0; i < count; i++) {
done = 0;
opcount = 2;
/* the tape may encounter errors will trying to
* reach the next file.
*/
while (!done) {
/* check for end-of-data and end-of-tape conditions
*/
if (IS_EOT(mtstat)) {
*statp = DRIVE_ERROR_EOM;
return i;
} else if (IS_EOD(mtstat)) {
*statp = DRIVE_ERROR_EOD;
return i;
}
/* 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;
}
if (!mt_get_status(drivep, &mtstat)) {
status_failed_message(drivep);
*statp = DRIVE_ERROR_DEVICE;
return i;
}
/* Check for a file mark to
* determine if the fsf command worked.
*/
if ((!op_failed) && (IS_FMK(mtstat))) {
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;
mtstat_t mtstat;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"drive op: bsf: count %d\n",
count);
assert(contextp->dc_mode == OM_NONE);
*statp = 0;
/* first move to the left of the last file mark.
* if BOT encountered, return 0. also check for
* being at BOT or file mark and count == 0: no motion needed
*/
/* get tape status
*/
if (!mt_get_status(drivep, &mtstat)) {
status_failed_message(drivep);
*statp = DRIVE_ERROR_DEVICE;
return 0;
}
/* check for beginning-of-tape condition. close/reopen hack here
*/
if (IS_BOT(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"reopening drive while at BOT\n");
Close(drivep);
if (!Open(drivep)) {
display_access_failed_message(drivep);
*statp = DRIVE_ERROR_DEVICE;
return 0;
}
if (!mt_get_status(drivep, &mtstat)) {
status_failed_message(drivep);
*statp = DRIVE_ERROR_DEVICE;
return 0;
}
assert(IS_BOT(mtstat));
*statp = DRIVE_ERROR_BOM;
return 0;
}
/* check if already at (and to right of) file mark and
* count is zero.
*/
if (IS_FMK(mtstat) && count == 0) {
return 0;
}
/* back space - places us to left of previous file mark
*/
assert(drivep->d_capabilities & DRIVE_CAP_BSF);
mtstat = bsf_and_verify(drivep);
/* check again for beginning-of-tape condition
*/
if (IS_BOT(mtstat)) {
*statp = DRIVE_ERROR_BOM;
return 0;
}
/* should be to the left of a file mark. drive status
* indicates file mark whether to left or right - for
* TS devices !!! LINUX ST tape driver only reports
* GMT_EOF to the right of the filemark !!
*/
if (TS_ISDRIVER) {
if (!IS_FMK(mtstat)) {
*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.
*/
mtstat = bsf_and_verify(drivep);
if (IS_BOT(mtstat)) {
*statp = DRIVE_ERROR_BOM;
return skipped + 1;
}
if (TS_ISDRIVER) {
if (!IS_FMK(mtstat)) {
*statp = DRIVE_ERROR_DEVICE;
return 0;
}
}
}
/* finally, move to the right side of the file mark
*/
mtstat = fsf_and_verify(drivep);
if(IS_EOT(mtstat)) {
*statp = DRIVE_ERROR_EOM;
}
if (!IS_FMK(mtstat)) {
*statp = DRIVE_ERROR_DEVICE;
}
/* indicate the number of media files skipped
*/
return skipped;
}
/* do_rewind
* Position the tape at the beginning of the recorded media.
*
* RETURNS:
* 0 on success
* DRIVE_ERROR_* on failure
*/
static int
do_rewind(drive_t *drivep)
{
#ifdef DEBUG
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
#endif
mtstat_t mtstat;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"drive op: rewind\n");
assert(contextp->dc_mode == OM_NONE);
assert(contextp->dc_fd >= 0);
/* use validating tape rewind util func
*/
mtstat = rewind_and_verify(drivep);
if (!IS_BOT(mtstat)) {
return DRIVE_ERROR_DEVICE;
} else {
return 0;
}
}
/* do_erase
* erase media from beginning
*
* RETURNS:
* 0 on success
* DRIVE_ERROR_* on failure
*/
static int
do_erase(drive_t *drivep)
{
#ifdef DEBUG
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
#endif
mtstat_t mtstat;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"drive op: erase\n");
assert(contextp->dc_mode == OM_NONE);
assert(contextp->dc_fd >= 0);
/* use validating tape rewind util func
*/
mtstat = rewind_and_verify(drivep);
if (!IS_BOT(mtstat)) {
return DRIVE_ERROR_DEVICE;
}
/* use validating tape erase util func
*/
(void)erase_and_verify(drivep);
/* rewind again
*/
mtstat = rewind_and_verify(drivep);
if (!IS_BOT(mtstat)) {
return DRIVE_ERROR_DEVICE;
}
/* 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 success
* 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,
"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,
"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,
"drive op: quit\n");
/* print the ring metrics and kill the ring
*/
if (ringp) {
display_ring_metrics(drivep, MLOG_VERBOSE);
/* tell worker to die
*/
mlog((MLOG_NITTY + 1) | MLOG_DRIVE,
"ring op: destroy\n");
ring_destroy(ringp);
}
if (!contextp->dc_isvarpr
&&
! contextp->dc_isQICpr
&&
contextp->dc_cansetblkszpr
&&
(contextp->dc_origcurblksz != 0)) {
(void)set_fixed_blksz(drivep, contextp->dc_origcurblksz);
}
/* issue tape unload
*/
if (contextp->dc_unloadokpr) {
(void)mt_op(contextp->dc_fd, MTUNLOAD, 0);
}
mlog(MLOG_DEBUG | MLOG_DRIVE,
"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
*/
/*
* Notes for restoring in Linux/IRIX.
* These are the assumption being made about the scsi tape drivers
* in IRIX and Linux.
* The 1st read() below is made prior to calling read_label().
* The 2nd read() is the one made inside read_label().
* Extraneous status flags, such as online, are not mentioned.
*
* -----------------------------------------
* Full tape (incomplete dump - over >1 tapes)
*
* Linux
* read->0 <fmk>
* read->0 <fmk, eod> <<<**** caused my problem
*
* IRIX
* read->0 <fmk, ew>
* read->-1 (ENOSPC) <eod, ew>
* -----------------------------------------
* Partial tape (complete dump - just 1 tape)
*
* Linux
* read->0 <fmk>
* read->0 <eod>
*
* IRIX
* read->0 <fmk>
* read->-1 (ENOSPC) <eod>
* -----------------------------------------
*/
static int
read_label(drive_t *drivep)
{
drive_context_t *contextp;
int nread;
int saved_errno;
mtstat_t mtstat;
bool_t wasatbotpr;
int rval;
bool_t ok;
/* initialize context ptr
*/
contextp = (drive_context_t *)drivep->d_contextp;
/* if not at BOT or a file mark, advance to right of next file mark
*/
ok = mt_get_status(drivep, &mtstat);
if (!ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
if (!IS_BOT(mtstat) && !IS_FMK(mtstat)) {
mtstat = fsf_and_verify(drivep);
}
/* if we hit EOM or early warning, just return
*/
if (IS_EOT(mtstat) || IS_EW(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"begin read hit EOM/EW\n");
return DRIVE_ERROR_EOM;
}
/* if we hit EOD, a file mark is missing
*/
if (IS_EOD(mtstat)) {
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("file mark missing from tape (hit EOD)\n"));
#ifdef DUMP
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("writing file mark at EOD\n"));
rval = mt_op(contextp->dc_fd, MTWEOF, 1);
if (rval) {
mlog(MLOG_NORMAL | MLOG_WARNING,
_("unable to write file mark at eod: %s (%d)\n"),
strerror(errno),
errno);
return DRIVE_ERROR_MEDIA;
}
ok = mt_get_status(drivep, &mtstat);
if (!ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
#endif /* DUMP */
}
/* verify we are either at BOT or a file mark
*/
if (!IS_BOT(mtstat) && !IS_FMK(mtstat)) {
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("file mark missing from tape\n"));
#ifdef DUMP
return DRIVE_ERROR_MEDIA;
#endif
}
/* remember if we were at BOT, so we know how to reposition if EOD
* encountered
*/
if (IS_BOT(mtstat)) {
wasatbotpr = BOOL_TRUE;
} else {
wasatbotpr = BOOL_FALSE;
}
/* 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);
ok = mt_get_status(drivep, &mtstat);
if (!ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
} else {
mtstat = 0;
}
/* 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. NOTE: shouldn't get here!
*/
if (nread == 0 && wasatbotpr) {
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("unexpectedly encountered EOD at BOT: "
"assuming corrupted media\n"));
(void)rewind_and_verify(drivep);
return DRIVE_ERROR_MEDIA;
}
/* if we hit end of tape or early warning, indicate EOM
*/
if (IS_EOT(mtstat) || IS_EW(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"hit EOM\n");
return DRIVE_ERROR_EOM;
}
#ifdef DUMP
/* if we hit EOD, re-position in anticipation of appending.
*/
if (IS_EOD(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"hit EOD: repositioning for append\n");
if (drivep->d_capabilities & DRIVE_CAP_BSF) {
(void)bsf_and_verify(drivep);
}
(void)fsf_and_verify(drivep);
return DRIVE_ERROR_EOD;
}
#endif /* DUMP */
#ifdef RESTORE
/* Linux case */
if (IS_EOD(mtstat) && IS_FMK(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"hit EOM\n");
return DRIVE_ERROR_EOM;
}
if (IS_EOD(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"hit EOD\n");
return DRIVE_ERROR_EOD;
}
#endif /* RESTORE */
/* if we hit a file mark, this is very bad.
* indicates the media has been corrupted
*/
if (IS_FMK(mtstat)) {
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("unexpectedly encountered a file mark: "
"assuming corrupted media\n"));
(void)rewind_and_verify(drivep);
return DRIVE_ERROR_MEDIA;
}
/* 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);
mlog(MLOG_NITTY, "validate_media_file_hdr\n"
"\tgh_magic %.100s\n"
"\tgh_version %u\n"
"\tgh_checksum %u\n"
"\tgh_timestamp %u\n"
"\tgh_ipaddr %llu\n"
"\tgh_hostname %.100s\n"
"\tgh_dumplabel %.100s\n",
tmpgh->gh_magic,
tmpgh->gh_version,
tmpgh->gh_checksum,
tmpgh->gh_timestamp,
tmpgh->gh_ipaddr,
tmpgh->gh_hostname,
tmpgh->gh_dumplabel);
/* 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_DEBUG | 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;
}
/* set_fixed_blksz()
* Issue the MTSETBLK ioctl to set the tape block size.
* Before issuing the call, close/reopen the device.
* if fails, rewind and try again. This is done to set the tape to BOT
* and to turn the CT_MOTION flag off so that the MTSETBLK call will
* succeed. If the call still fails, print an error but keep running.
*
* RETURNS:
* TRUE on success
* FALSE on failure
*/
static bool_t
set_fixed_blksz(drive_t *drivep, size_t blksz)
{
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
ix_t try;
/* sanity checks
*/
assert(blksz);
assert(contextp->dc_isvarpr == BOOL_FALSE);
assert(contextp->dc_cansetblkszpr);
assert(contextp->dc_fd >= 0);
/* give it two tries: first without rewinding, second with rewinding
*/
for (try = 1; try <= 2; try++) {
struct mtblkinfo mtinfo;
/* set the tape block size. requires re-open
*/
mlog(MLOG_DEBUG | MLOG_DRIVE,
"setting fixed block size to %d\n",
blksz);
/* close and re-open
*/
Close(drivep);
if (!Open(drivep)) {
display_access_failed_message(drivep);
return BOOL_FALSE;
}
/* issue call to set block size
*/
if (mt_op(contextp->dc_fd,
MTSETBLK,
(int)blksz)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"MTSETBLK %u failed: %s (%d)\n",
blksz,
strerror(errno),
errno);
}
/* see if we were successful (can't look if RMT, so assume
* it worked)
*/
if (!contextp->dc_isrmtpr) {
bool_t ok;
ok = mt_blkinfo(contextp->dc_fd, &mtinfo);
if (! ok) {
return BOOL_FALSE;
}
if (mtinfo.curblksz == blksz) {
return BOOL_TRUE;
}
} else {
return BOOL_TRUE;
}
/* so rewind and try again
*/
(void)rewind_and_verify(drivep);
}
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("unable to set block size to %d\n"),
blksz);
return BOOL_FALSE;
}
/* 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)
{
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
assert(contextp->dc_fd >= 0);
if (contextp->dc_isrmtpr) {
/* 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.
*/
contextp->dc_maxblksz = 0;
contextp->dc_isQICpr = BOOL_FALSE;
contextp->dc_cangetblkszpr = BOOL_FALSE;
contextp->dc_cansetblkszpr = BOOL_FALSE;
drivep->d_capabilities |= DRIVE_CAP_OVERWRITE;
drivep->d_capabilities |= DRIVE_CAP_BSF;
} else {
/* not remote, so we can ask the driver
*/
struct mtblkinfo mtinfo;
bool_t ok;
ok = mt_blkinfo(contextp->dc_fd, &mtinfo);
if (!ok) {
return BOOL_FALSE;
}
contextp->dc_canfsrpr = BOOL_FALSE;
if (contextp->dc_isQICpr) {
contextp->dc_cangetblkszpr = BOOL_FALSE;
contextp->dc_cansetblkszpr = BOOL_FALSE;
contextp->dc_maxblksz = QIC_BLKSZ;
drivep->d_capabilities &= ~DRIVE_CAP_OVERWRITE;
drivep->d_capabilities &= ~DRIVE_CAP_BSF;
}
else {
contextp->dc_cangetblkszpr = BOOL_TRUE;
contextp->dc_cansetblkszpr = BOOL_TRUE;
contextp->dc_maxblksz = mtinfo.maxblksz;
if (contextp->dc_origcurblksz == 0)
contextp->dc_origcurblksz = mtinfo.curblksz;
drivep->d_capabilities |= DRIVE_CAP_OVERWRITE;
drivep->d_capabilities |= DRIVE_CAP_BSF;
#ifdef HIDDEN
Need to find equivalent in Linux.
if (mtcapablity & MTCAN_SEEK) {
contextp->dc_canfsrpr = BOOL_TRUE;
}
#endif
}
}
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;