blob: 5c9ee89d1e507f1c3bb144caaedb6ccfe7f1ddb8 [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. slave 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 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;
/* 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 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,
"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 slave 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;
off64_t fsize = drive_strategy_scsitape.ds_recmfilesz;
off64_t marksep = drive_strategy_scsitape.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_blkinfo
* In IRIX, issue MTIOGETBLKINFO ioctl operation to the tape device.
* There is no equivalent call in Linux at the moment.
* However, the minblks and maxblks are stored internally in
* the scsi/st.c driver and may be exported in the future.
* The current blk size comes from the mt_dsreg field.
*
* RETURNS:
* TRUE on success
* FALSE on failure
*/
static bool_t
mt_blkinfo(int fd, struct mtblkinfo *minfo)
{
struct mtget mt_stat;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape op: get block size info\n");
if (TS_ISDRIVER) { /* Use TS ioctl MTIOCGETBLKINFO so we don't
* have to hard code the max block size */
struct mtblkinfo ts_blkinfo;
if (ioctl(fd, MTIOCGETBLKINFO, &ts_blkinfo) < 0) {
/* failure
*/
mlog(MLOG_DEBUG,
"tape command MTIOCGETBLKINFO failed : %d (%s)\n",
errno,
strerror(errno));
return BOOL_FALSE;
}
minfo->curblksz = ts_blkinfo.curblksz;
minfo->maxblksz = ts_blkinfo.maxblksz;
}
else {
if (ioctl(fd, MTIOCGET, &mt_stat) < 0) {
/* failure
*/
mlog(MLOG_DEBUG,
"tape command MTIOCGET failed : %d (%s)\n",
errno,
strerror(errno));
return BOOL_FALSE;
}
minfo->curblksz = (mt_stat.mt_dsreg >> MT_ST_BLKSIZE_SHIFT) &
MT_ST_BLKSIZE_MASK;
minfo->maxblksz = STAPE_MAX_LINUX_RECSZ;
}
mlog(MLOG_NITTY | MLOG_DRIVE,
"max=%u cur=%u\n",
minfo->maxblksz,
minfo->curblksz);
/* success
*/
return BOOL_TRUE;
}
/* mt_op
* Issue MTIOCTOP ioctl operation to the tape device.
*
* RETURNS:
* 0 on success
* -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:
printstr = "back space file";
break;
case MTWEOF:
printstr = "write file mark";
break;
case MTFSF:
printstr = "forward space file";
break;
case MTREW:
printstr = "rewind";
break;
case MTUNLOAD:
printstr = "unload";
break;
case MTEOM:
printstr = "advance to EOD";
break;
case MTFSR:
printstr = "forward space block";
break;
case MTERASE:
printstr = "erase";
break;
case MTSETBLK:
printstr = "set block size";
break;
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;
}
static bool_t
mt_get_fileno(drive_t *drivep, long *fileno)
{
struct mtget mt_stat;
drive_context_t *contextp;
contextp = (drive_context_t *)drivep->d_contextp;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape op: get fileno\n");
assert(contextp->dc_fd >= 0);
if (ioctl(contextp->dc_fd, MTIOCGET, &mt_stat) < 0) {
/* failure
*/
mlog(MLOG_DEBUG,
"tape command MTIOCGET failed : %d (%s)\n",
errno,
strerror(errno));
return BOOL_FALSE;
}
*fileno = mt_stat.mt_fileno;
return BOOL_TRUE;
}
/* mt_get_status
* Get the current status of the tape device.
*
* RETURNS:
* TRUE if status was obtained
* FALSE if not
*/
static bool_t
mt_get_status(drive_t *drivep, long *status)
{
struct mtget mt_stat;
drive_context_t *contextp;
contextp = (drive_context_t *)drivep->d_contextp;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape op: get status\n");
assert(contextp->dc_fd >= 0);
if (TS_ISDRIVER) {
/*
* Call the MTIOCGET_SGI ioctl for TS devices, then
* map the results into ST format (mtget) using the
* TS workaround routine called map_ts_status().
* See comments in map_ts_status() for more detail.
*/
struct mtget_sgi mt_stat_sgi;
if (ioctl(contextp->dc_fd, MTIOCGET_SGI, &mt_stat_sgi) < 0) {
/* failure
*/
mlog(MLOG_DEBUG,
"tape command MTIOCGET_SGI failed : %d (%s)\n",
errno,
strerror(errno));
return BOOL_FALSE;
}
map_ts_status(&mt_stat, mt_stat_sgi);
} else {
if (ioctl(contextp->dc_fd, MTIOCGET, &mt_stat) < 0) {
/* failure
*/
mlog(MLOG_DEBUG,
"tape command MTIOCGET failed : %d (%s)\n",
errno,
strerror(errno));
return BOOL_FALSE;
}
}
/* success
*/
*status = mt_stat.mt_gstat;
/* print out symbolic form of tape status */
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape status = %s%s%s%s%s%s%s\n",
IS_BOT(*status)? "bot ":"",
IS_FMK(*status)? "fmk ":"",
IS_EOD(*status)? "eod ":"",
IS_EOT(*status)? "eot ":"",
IS_EW(*status)? "ew ":"",
IS_WPROT(*status)? "wprot ":"",
IS_ONL(*status)? "onl ":"");
return BOOL_TRUE;
}
/* 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(drive_t *drivep, int nwritten, int saved_errno)
{
mtstat_t mtstat;
int ret;
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
/* get tape device status
*/
if (! mt_get_status(drivep, &mtstat)) {
status_failed_message(drivep);
ret = DRIVE_ERROR_DEVICE;
} else if (IS_WPROT(mtstat) && (saved_errno == EROFS)) {
mlog(MLOG_NORMAL,
_("tape is write protected\n"));
ret = DRIVE_ERROR_DEVICE;
} else if ((!IS_BOT(mtstat)) &&
(IS_EOT(mtstat) || IS_EW(mtstat) ||
(saved_errno == ENOSPC))) {
ret = DRIVE_ERROR_EOM;
} else if (saved_errno == EIO) {
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) &&
(nwritten > 0) &&
contextp->dc_isQICpr) {
/* short write on one of this devices indicates
* early warning for end-of-media.
*/
ret = DRIVE_ERROR_EOM;
} else {
ret = DRIVE_ERROR_CORE;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape unknown error on write operation: "
"0x%x, %d, %d\n",
mtstat, nwritten, saved_errno);
}
mlog(MLOG_NITTY | MLOG_DRIVE,
"tape write operation status 0x%x, nwritten %d, errno %d\n",
mtstat,
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)
{
drive_context_t *contextp;
/* get pointer to drive context
*/
contextp = (drive_context_t *)drivep->d_contextp;
if (contextp->dc_isrmtpr) {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("attempt to access/open remote "
"tape drive %s failed: %d (%s)\n"),
drivep->d_pathname,
errno,
strerror(errno));
} else {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("attempt to access/open device %s failed: %d (%s)\n"),
drivep->d_pathname,
errno,
strerror(errno));
}
return;
}
/* status_failed_message()
* Print tape status device failed message.
*
* RETURNS:
* one
*/
static void
status_failed_message(drive_t *drivep)
{
drive_context_t *contextp;
/* get pointer to drive context
*/
contextp = (drive_context_t *)drivep->d_contextp;
/* the get status call could have failed due to the
* tape device being closed by a CTLR-\ from the operator.
*/
if (contextp->dc_fd != -1) {
if (contextp->dc_isrmtpr) {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("attempt to get status of remote "
"tape drive %s failed: %d (%s)\n"),
drivep->d_pathname,
errno,
strerror(errno));
} else {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("attempt to get status of "
"tape drive %s failed: %d (%s)\n"),
drivep->d_pathname,
errno,
strerror(errno));
}
}
return;
}
static bool_t
is_variable(drive_t *drivep, bool_t *varblk)
{
drive_context_t *contextp;
contextp = (drive_context_t *)drivep->d_contextp;
if (TS_ISDRIVER) {
char value[MT_ATTR_MAX_VALLEN+1];
struct mt_attr mtattr;
value[0] = '\0';
mtattr.ma_value = value;
mtattr.ma_name = MT_ATTR_NAME_VARIABLE;
mtattr.ma_vlen = sizeof(value);
ioctl(contextp->dc_fd, MTGETATTR, &mtattr);
if (strcmp(value, MT_ATTR_VALUE_TRUE) == 0)
*varblk = BOOL_TRUE;
else if (strcmp(value, MT_ATTR_VALUE_FALSE) == 0)
*varblk = BOOL_FALSE;
else
return BOOL_FALSE; /* failure */
} else {
bool_t ok;
struct mtblkinfo minfo;
ok = mt_blkinfo(contextp->dc_fd, &minfo);
if (!ok)
return BOOL_FALSE; /* failure */
/* for Linux scsi driver the blksize == 0 if variable */
if (minfo.curblksz == 0)
*varblk = BOOL_TRUE;
else
*varblk = BOOL_FALSE;
}
return BOOL_TRUE;
}
/* 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;
mtstat_t mtstat;
bool_t ok;
ix_t try;
ix_t maxtries;
bool_t changedblkszpr;
int rval;
int varblk;
/* 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;
}
continue;
}
}
/* read device status (uses an ioctl)
*/
mtstat = 0;
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
/* look at status to check if the device is online.
* also check if write-protected (DUMP only), and give up
* after a few tries.
*/
if (IS_ONL(mtstat)) {
#ifdef DUMP
if (IS_WPROT(mtstat)) {
mlog(MLOG_NORMAL,
_("tape is write protected\n"));
return DRIVE_ERROR_MEDIA;
}
#endif /* DUMP */
/* populate a struct stat. NOTE: this may do a temporary open/close
* NOTE: may do this only on local drives: rmt does not support!
*/
if (contextp->dc_isrmtpr) {
contextp->dc_isvarpr = BOOL_FALSE;
} else {
/* check for special device dev_t for fixed or variable type
* of device. set context and drive flags accordingly.
*/
if (is_variable(drivep, &varblk) == BOOL_FALSE)
return DRIVE_ERROR_DEVICE;
if (varblk) {
contextp->dc_isvarpr = BOOL_TRUE;
mlog(MLOG_TRACE | MLOG_DRIVE,
"variable block size "
"tape drive at %s\n",
drivep->d_pathname);
} else {
contextp->dc_isvarpr = BOOL_FALSE;
mlog(MLOG_TRACE | MLOG_DRIVE,
"fixed block size tape "
"drive at %s\n",
drivep->d_pathname);
}
}
break;
} else if (try >= maxtries) {
mlog(MLOG_VERBOSE | MLOG_DRIVE,
_("giving up waiting for drive "
"to indicate online\n"));
return DRIVE_ERROR_MEDIA;
}
/* drive is not ready. sleep for a while and try again
*/
mlog(MLOG_VERBOSE | MLOG_DRIVE,
_("tape drive %s is not ready (0x%x): "
"retrying ...\n"),
drivep->d_pathname,
mtstat);
Close(drivep);
}
assert(IS_ONL(mtstat));
/* 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;
}
/* disallow access of QIC via variable
*/
if (contextp->dc_isvarpr && contextp->dc_isQICpr) {
mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE, _(
"use of QIC drives via variable blocksize device nodes "
"is not supported\n"));
return DRIVE_ERROR_INVAL;
}
/* if the overwrite option was specified , set the best blocksize
* we can and return.
*/
if (contextp->dc_overwritepr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"Overwrite option specified. "
"Trying best blocksize\n");
ok = set_best_blk_and_rec_sz(drivep);
if (! ok) {
return DRIVE_ERROR_DEVICE;
}
return DRIVE_ERROR_OVERWRITE;
}
/* establish the initial block and record sizes we will try.
* if unable to ask drive about fixed block sizes, we will
* guess.
*/
calc_best_blk_and_rec_sz(drivep);
/* if the drive status says we are at a file mark, there is
* an ambiguity: we could be positioned just before or just
* after the file mark. we want to always be positioned just after
* file marks. To disambiguate and force positioning after,
* we will use tape motion. back up two file marks, because
* typically we will be positioned after last file mark at EOD.
*/
if (! IS_BOT(mtstat) && IS_FMK(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape positioned at file mark, "
"but do not know if before or after: "
"forcing tape motion to disambiguate\n");
#ifdef RESTORE
(void)fsf_and_verify(drivep);
#endif /* RESTORE */
rval = quick_backup(drivep, contextp, 0);
if (rval) {
return rval;
}
}
/* 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.
*
* **************
* LINUX WARNING:
* **************
* This code has a lot of conditionals on the current status
* and error result from the read.
* Under Linux using the scsi tape driver, as opposed to IRIX,
* the semantics may well be different for some error codes.
* For example, it seems on Linux if we use an erased tape
* then the first read returns an EIO, whereas on IRIX we would
* get an ENOSPC.
*/
maxtries = 5;
changedblkszpr = BOOL_FALSE;
for (try = 1 ; ; try++) {
bool_t wasatbotpr;
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);
/* if a fixed device, but not QIC, and possible to set the block
* size, do so.
*/
if (! contextp->dc_isvarpr
&&
! contextp->dc_isQICpr
&&
contextp->dc_cansetblkszpr) {
ok = set_fixed_blksz(drivep, tape_blksz);
if (! ok) {
return DRIVE_ERROR_DEVICE;
}
}
/* refresh the tape status
*/
mtstat = 0;
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
/* first ensure we are positioned at BOT or just after
* a file mark. if the drive says we are at a file mark, we
* don't know if we are just before or just after the file mark.
* so we must either bsf or rewind to eliminate the uncertainty.
* if BSF is not supported, must rewind.
*/
if (! IS_BOT(mtstat) && ! IS_FMK(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape position unknown: searching backward "
"for file mark or BOT\n");
rval = quick_backup(drivep, contextp, 0);
if (rval) {
return rval;
}
mtstat = 0;
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
}
/* if we can't position the tape, call it a media error
*/
if (! IS_BOT(mtstat) && ! IS_FMK(mtstat)) {
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("unable to backspace/rewind media\n"));
return DRIVE_ERROR_MEDIA;
}
if (IS_BOT(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape positioned at BOT: "
"doing redundant rewind\n");
mtstat = rewind_and_verify(drivep);
if (! IS_BOT(mtstat)) {
return DRIVE_ERROR_DEVICE;
}
}
if (IS_FMK(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape positioned at file mark\n");
}
/* determine if we are at BOT. remember, so if read fails
* we can make a better decision on what to do next.
*/
if (IS_BOT(mtstat)) {
wasatbotpr = BOOL_TRUE;
} else if (IS_FMK(mtstat)) {
wasatbotpr = BOOL_FALSE;
} else {
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("unable to backspace/rewind media\n"));
return DRIVE_ERROR_MEDIA;
}
/* 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;
}
/* tried to read past EOD and was at BOT
*/
if (saved_errno == ENOSPC
&&
wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"errno ENOSPC while at BOT "
"indicates blank tape: returning\n");
(void)rewind_and_verify(drivep);
ok = set_best_blk_and_rec_sz(drivep);
if (! ok) {
return DRIVE_ERROR_DEVICE;
}
return DRIVE_ERROR_BLANK;
}
/* was at BOT and got EIO on read
* On Linux, using the scsi tape driver this
* seems to happen with an erased/blank tape
*/
if (saved_errno == EIO
&&
wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"errno EIO while at BOT "
"indicates blank tape: returning\n");
(void)rewind_and_verify(drivep);
ok = set_best_blk_and_rec_sz(drivep);
if (! ok) {
return DRIVE_ERROR_DEVICE;
}
return DRIVE_ERROR_BLANK;
}
/* tried to read past EOD and NOT at BOT
*/
if (saved_errno == ENOSPC
&&
! wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"errno ENOSPC while not at BOT "
"indicates EOD: retrying\n");
rval = quick_backup(drivep, contextp, 1);
if (rval) {
return rval;
}
continue;
}
/* I/O error
*/
if (saved_errno == EIO) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"read returned EIO: will reopen, rewind, "
"and try again\n");
Close(drivep);
ok = Open(drivep);
if (! ok) {
display_access_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
(void)rewind_and_verify(drivep);
continue;
}
/* freshen up the tape status. useful in decision-making
* done below.
*/
mtstat = 0;
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
if (nread == 0
&&
! contextp->dc_isvarpr
&&
IS_EOD(mtstat)
&&
wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == 0 and EOD while at BOT on "
"fixed blocksize drive "
"indicates blank tape: returning\n");
(void)rewind_and_verify(drivep);
ok = set_best_blk_and_rec_sz(drivep);
if (! ok) {
return DRIVE_ERROR_DEVICE;
}
return DRIVE_ERROR_BLANK;
}
if (nread == 0
&&
! contextp->dc_isvarpr
&&
IS_EOD(mtstat)
&&
! wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == 0 and EOD while not at BOT on "
"fixed blocksize drive "
"indicates EOD: backing up and retrying\n");
rval = quick_backup(drivep, contextp, 1);
if (rval) {
return rval;
}
continue;
}
if (nread == 0
&&
! contextp->dc_isvarpr
&&
IS_EOT(mtstat)
&&
! wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == 0 and EOT while not at BOT on "
"fixed blocksize drive "
"indicates EOD: backing up and retrying\n");
rval = quick_backup(drivep, contextp, 1);
if (rval) {
return rval;
}
continue;
}
if (nread == 0
&&
! contextp->dc_isvarpr
&&
! IS_EOD(mtstat)
&&
! IS_FMK(mtstat)
&&
! IS_EOT(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == 0 and not EOD, not EOT, "
"and not at a file mark on fixed blocksize drive "
"indicates wrong blocksize\n");
goto newsize;
}
if (nread == 0
&&
contextp->dc_isvarpr
&&
IS_EOD(mtstat)
&&
wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == 0 and EOD indication at BOT "
"on variable tape "
"indicates blank tape: returning\n");
(void)rewind_and_verify(drivep);
ok = set_best_blk_and_rec_sz(drivep);
if (! ok) {
return DRIVE_ERROR_DEVICE;
}
return DRIVE_ERROR_BLANK;
}
if (nread == 0
&&
contextp->dc_isvarpr
&&
IS_EOD(mtstat)
&&
! wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == 0 and EOD while not at BOT on "
"variable blocksize drive "
"indicates EOD: backing up and retrying\n");
rval = quick_backup(drivep, contextp, 1);
if (rval) {
return rval;
}
continue;
}
if (nread == 0
&&
contextp->dc_isvarpr
&&
IS_EOT(mtstat)
&&
! wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == 0 and EOT while not at BOT on "
"variable blocksize drive "
"indicates EOT: backing up and retrying\n");
rval = quick_backup(drivep, contextp, 1);
if (rval) {
return rval;
}
continue;
}
if (nread == 0
&&
contextp->dc_isvarpr
&&
IS_FMK(mtstat)
&&
wasatbotpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == 0 at BOT and at a file mark "
"on variable blocksize drive "
"indicates foreign tape: returning\n");
(void)rewind_and_verify(drivep);
ok = set_best_blk_and_rec_sz(drivep);
if (! ok) {
return DRIVE_ERROR_DEVICE;
}
return DRIVE_ERROR_FOREIGN;
}
if (nread > 0
&&
contextp->dc_isvarpr
&&
! IS_EOD(mtstat)
&&
! IS_FMK(mtstat)
&&
! IS_EOT(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread > 0 and not EOD, not EOT, "
"and not at a file mark on variable blocksize drive "
"indicates correct blocksize found\n");
goto checkhdr;
}
if (nread < (int)tape_recsz
&&
! contextp->dc_isvarpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread less than selected record size on "
"fixed blocksize drive "
"indicates wrong blocksize\n");
goto newsize;
}
if (nread == (int)tape_recsz
&&
! contextp->dc_isvarpr) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"nread == selected blocksize "
"on fixed blocksize drive "
"indicates correct blocksize found\n");
goto checkhdr;
}
/* 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 "
"isvar %d "
"wasatbot %d "
"eod %d "
"fmk %d "
"eot %d "
"onl %d "
"wprot %d "
"ew %d "
"\n"),
saved_errno,
nread,
tape_blksz,
tape_recsz,
!! contextp->dc_isvarpr,
wasatbotpr,
IS_EOD(mtstat) > 0,
IS_FMK(mtstat) > 0,
IS_EOT(mtstat) > 0,
IS_ONL(mtstat) > 0,
IS_WPROT(mtstat) > 0,
IS_EW(mtstat) > 0,
0);
/* Common Linux Problem */
if (errno == EOVERFLOW) {
mlog(MLOG_NORMAL | MLOG_NOTE | MLOG_DRIVE,
_("likely problem is that the block size, %d, "
"is too large for Linux\n"),
tape_blksz);
mlog(MLOG_NORMAL | MLOG_NOTE | MLOG_DRIVE,
_("either try using a smaller block size with "
"the -b option, or increase max_sg_segs for "
"the scsi tape driver\n"));
}
return DRIVE_ERROR_CORE;
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 (wasatbotpr) {
if (isefsdump(drivep)) {
mlog(MLOG_NORMAL
|
MLOG_WARNING
|
MLOG_DRIVE,
_("may be an EFS dump at BOT\n"));
} 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 {
/* back up and try again.
*/
mlog(MLOG_DEBUG | MLOG_DRIVE,
"media file header invalid: "
"backing up "
"to try a previous media file\n");
rval = quick_backup(drivep, contextp, 1);
if (rval) {
return rval;
}
continue;
}
} 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;
mlog(MLOG_DEBUG | MLOG_DRIVE,
"tape record size set to header's "
"record size = %d\n", tape_recsz);
break;
}
newsize:
/* we end up here if we want to try a new record size.
* only do this once.
*/
if (changedblkszpr) {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("cannot determine tape block size "
"after two tries\n"));
if (! wasatbotpr) {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("will rewind and try again\n"));
(void)rewind_and_verify(drivep);
Close(drivep);
goto retry;
} else {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("assuming media is corrupt "
"or contains non-xfsdump data\n"));
(void)rewind_and_verify(drivep);
ok = set_best_blk_and_rec_sz(drivep);
if (! ok) {
return DRIVE_ERROR_DEVICE;
}
return DRIVE_ERROR_FOREIGN;
}
}
if (tape_recsz > STAPE_MIN_MAX_BLKSZ) {
tape_recsz = STAPE_MIN_MAX_BLKSZ;
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;
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"));
if (! wasatbotpr) {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("will rewind and try again\n"));
(void)rewind_and_verify(drivep);
Close(drivep);
goto retry;
} else {
mlog(MLOG_NORMAL | MLOG_DRIVE,
_("assuming media is corrupt "
"or contains non-xfsdump data\n"));
(void)rewind_and_verify(drivep);
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;
} /* loop reading 1st record 'til get correct blksz */
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;
}
/* probably should use do_bsf instead.
* backs up and positions tape after previous file mark.
* skips skipcnt media files.
*/
/* ARGSUSED */
static int
quick_backup(drive_t *drivep, drive_context_t *contextp, ix_t skipcnt)
{
if (drivep->d_capabilities & DRIVE_CAP_BSF) {
do {
mtstat_t mtstat;
mtstat = bsf_and_verify(drivep);
if (IS_BOT(mtstat)) {
return 0;
}
if (TS_ISDRIVER) {
if (! IS_FMK(mtstat)) {
mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("unable to backspace tape: "
"assuming media error\n"));
return DRIVE_ERROR_MEDIA;
}
}
} while (skipcnt--);
(void)fsf_and_verify(drivep);
} else {
(void)rewind_and_verify(drivep);
}
return 0;
}
/* 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;
}
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;
}
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;
mtstat_t mtstat;
int rval;
bool_t ok;
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;
}
/* get drive status
*/
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
status_failed_message(drivep);
return DRIVE_ERROR_DEVICE;
}
/* encountered a file mark
*/
if (IS_FMK(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"encountered EOF attempting to read record %lld\n",
contextp->dc_iocnt);
return DRIVE_ERROR_EOF;
}
/* encountered a end of recorded data
*/
if (IS_EOD(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"encountered EOD attempting to read record %lld\n",
contextp->dc_iocnt);
return DRIVE_ERROR_EOD;
}
/* encountered a end of media
*/
if (IS_EOT(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"encountered EOM attempting to read record %lld\n",
contextp->dc_iocnt);
return DRIVE_ERROR_EOM;
}
/* encountered a end of media (early warning indicated)
*/
if (IS_EW(mtstat)) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"encountered EW attempting to read record %lld\n",
contextp->dc_iocnt);
return DRIVE_ERROR_EOM;
}
/* 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,
errno,
strerror(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(drivep, 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 if (tape_recsz == STAPE_MAX_LINUX_RECSZ) {
assert(! (STAPE_MAX_LINUX_RECSZ % 0x100000));
sprintf(bufszbuf, "%u", STAPE_MAX_LINUX_RECSZ / 0x100000);
assert(strlen(bufszbuf) < sizeof(bufszbuf));
bufszsfxp = "MB";
} else {
bufszsfxp = "";
}
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));
}
static mtstat_t
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++) {
mtstat_t mtstat;
bool_t ok;
if (rval) {
sleep(1);
rval = mt_op(contextp->dc_fd, MTREW, 0);
}
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
mtstat = 0;
status_failed_message(drivep);
if (try > 1) {
return 0;
}
}
if (IS_BOT(mtstat)) {
return mtstat;
}
if (try >= MTOP_TRIES_MAX) {
return mtstat;
}
if (rval) {
return mtstat;
}
sleep(1);
}
/* NOTREACHED */
}
static mtstat_t
erase_and_verify(drive_t *drivep)
{
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
mtstat_t mtstat;
bool_t ok;
(void)mt_op(contextp->dc_fd, MTERASE, 0);
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
mtstat = 0;
status_failed_message(drivep);
}
return mtstat;
}
static mtstat_t
bsf_and_verify(drive_t *drivep)
{
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
ix_t try;
bool_t ok;
/* Can't do with LINUX ST driver, as GMT_EOF never set for left of fmk */
if (TS_ISDRIVER) {
(void)mt_op(contextp->dc_fd, MTBSF, 1);
for (try = 1 ; ; try++) {
mtstat_t mtstat;
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
mtstat = 0;
status_failed_message(drivep);
if (try > 1) {
return 0;
}
}
if (IS_FMK(mtstat)) {
return mtstat;
}
if (IS_BOT(mtstat)) {
return mtstat;
}
if (try >= MTOP_TRIES_MAX) {
return mtstat;
}
sleep(1);
}
} else {
long fileno;
mtstat_t mtstat;
bool_t ok;
/*
* Workaround for linux st driver bug.
* Don't do a bsf if still in the first file.
* Do a rewind instead because the status won't be
* set correctly otherwise. [TS:Oct/2000]
*/
ok = mt_get_fileno(drivep, &fileno);
if (! ok) {
status_failed_message(drivep);
return 0;
}
if (fileno == 0) {
mlog(MLOG_DEBUG | MLOG_DRIVE,
"In first file, do a rewind to achieve bsf\n");
return rewind_and_verify(drivep);
}
(void)mt_op(contextp->dc_fd, MTBSF, 1);
try = 1;
status: ok = mt_get_status(drivep, &mtstat);
if (! ok) {
mtstat = 0;
status_failed_message(drivep);
if (try > 1) {
return 0;
}
try++;
sleep(1);
goto status;
}
return mtstat;
}
/* NOTREACHED */
}
static mtstat_t
fsf_and_verify(drive_t *drivep)
{
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
ix_t try;
(void)mt_op(contextp->dc_fd, MTFSF, 1);
for (try = 1 ; ; try++) {
mtstat_t mtstat;
bool_t ok;
ok = mt_get_status(drivep, &mtstat);
if (! ok) {
mtstat = 0;
status_failed_message(drivep);
if (try > 1) {
return 0;
}
}
if (IS_FMK(mtstat)) {
return mtstat;
}
if (IS_EOD(mtstat)) {
return mtstat;
}
if (IS_EOT(mtstat)) {
return mtstat;
}
if (try >= MTOP_TRIES_MAX) {
return mtstat;
}
sleep(1);
}
/* NOTREACHED */
}
static void
calc_best_blk_and_rec_sz(drive_t *drivep)
{
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
if (! contextp->dc_isrmtpr) {
if (cmdlineblksize > 0) {
tape_blksz = cmdlineblksize;
} else {
tape_blksz = contextp->dc_maxblksz;
}
if (tape_blksz > STAPE_MAX_RECSZ) {
tape_blksz = STAPE_MAX_RECSZ;
}
if (contextp->dc_isQICpr) {
tape_recsz = STAPE_MAX_RECSZ;
} else {
tape_recsz = tape_blksz;
}
} else {
tape_recsz = STAPE_MIN_MAX_BLKSZ;
}
}
static bool_t
set_best_blk_and_rec_sz(drive_t *drivep)
{
drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
calc_best_blk_and_rec_sz(drivep);
if (! contextp->dc_isvarpr
&&
! contextp->dc_isQICpr
&&
contextp->dc_cansetblkszpr) {
bool_t ok;
ok = set_fixed_blksz(drivep, tape_blksz);
if (! ok) {
return BOOL_FALSE;
}
}
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;
}
}
/*
* General purpose routine which dredges through procfs trying to
* match up device driver names with the associated major numbers
* being used in the running kernel. (This routine is based on
* the get_driver_block_major() routine in the libdisk library.)
*/
static int
get_driver_character_major(const char *driver)
{
FILE *f;
char buf[64], puf[64];
int major = -1;
#define PROC_DEVICES "/proc/devices"
if ((f = fopen(PROC_DEVICES, "r")) == NULL)
return major;
while (fgets(buf, sizeof(buf), f)) /* skip to character dev section */
if (strncmp("Character devices:\n", buf, sizeof(buf)) == 0)
break;
while (fgets(buf, sizeof(buf), f))
if ((sscanf(buf, "%u %s\n", &major, puf) == 2) &&
(strncmp(puf, driver, sizeof(puf)) == 0))
goto found;
major = -1;
found:
fclose(f);
return major;
}
static void
map_ts_status(struct mtget *mtstat, struct mtget_sgi mtstat_sgi)
{
/*
* This routine has been added as a workaround for a TS/APD
* bug which sets the BOT flag incorrectly when positioned
* at a filemark. The BOT bug has been fixed in APD 2.8,
* however the fix won't be released until spring 2004 (well
* after xfsdump 2.2.15). This xfsdump workaround begins
* in mt_get_status(), where we call the MTIOCGET_SGI ioctl
* rather than the ST-emulated MTIOCGET ioctl for TS devices.
* This routine is called following the MTIOCGET_SGI ioctl
* to map the TS mtget_sgi.mt_dposn bits back to an ST value
* for the mt_gstat status field. NOTE that this routine does
* NOT map the entire mtget struct, ONLY the mt_gstat field,
* as required by mt_get_status()!!! The ST mtget structure
* is returned to mt_get_status() with ONLY the correct mt_gstat
* flags set. This workaround can be removed once APD 2.8 has
* been released for at least 6 months (Oct-Dec 2004).
*/
mtstat->mt_gstat = 0;
if (mtstat_sgi.mt_dposn & MT_BOT) {
mtstat->mt_gstat |= GMT_BOT(0xffffffff);
}
if (mtstat_sgi.mt_dposn & MT_EOT) {
mtstat->mt_gstat |= GMT_EOT(0xffffffff);
}
if (mtstat_sgi.mt_dposn & MT_WPROT) {
mtstat->mt_gstat |= GMT_WR_PROT(0xffffffff);
}
if (mtstat_sgi.mt_dposn & MT_ONL) {
mtstat->mt_gstat |= GMT_ONLINE(0xffffffff);
}
if (mtstat_sgi.mt_dposn & MT_EOD) {
mtstat->mt_gstat |= GMT_EOD(0xffffffff);
}
if (mtstat_sgi.mt_dposn & MT_FMK) {
mtstat->mt_gstat |= GMT_EOF(0xffffffff);
}
return;
}