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