blob: 086d1624bf3454800c0205ac85961bc20acd17a6 [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 <fcntl.h>
#include <time.h>
#include <errno.h>
#include <malloc.h>
#include <sched.h>
#include "types.h"
#include "util.h"
#include "stream.h"
#include "mlog.h"
#include "global.h"
#include "drive.h"
#include "media.h"
#include "arch_xlate.h"
/* this rmt junk is here because the rmt protocol supports writing ordinary
* (non-device) files in the remote /dev directory! yuck!
*/
#define open rmtopen
#define creat rmtcreat
#define close rmtclose
#define ioctl rmtioctl
#define read rmtread
#define write rmtwrite
extern int rmtclose( int );
extern int rmtcreat (char *path, int mode);
extern int rmtioctl( int, int, ... );
extern int rmtopen( char *, int, ... );
extern int rmtread( int, void*, uint);
extern int rmtwrite( int, const void *, uint);
/* drive_simple.c - drive strategy for standard in or a file
*/
/* structure definitions used locally ****************************************/
/* drive context - drive-specific context
* buf must be page-aligned and at least 1 page in size
*/
#define PGPERBUF 64 /* private read buffer */
#define BUFSZ ( PGPERBUF * PGSZ )
/* operational mode
*/
typedef enum { OM_NONE, OM_READ, OM_WRITE } om_t;
struct drive_context {
char dc_buf[ BUFSZ ]; /* input/output buffer */
om_t dc_mode; /* current mode of operation */
ix_t dc_fmarkcnt; /* how many file marks to the left */
char *dc_ownedp; /* first byte owned by caller */
size_t dc_ownedsz; /* how much owned by caller (write only) */
char *dc_nextp; /* next byte avail. to read/write */
char *dc_emptyp; /* first empty slot in buffer */
off64_t dc_bufstroff; /* offset in stream of top of buf */
intgen_t dc_fd; /* input/output file descriptor */
drive_mark_t dc_firstmark;/* first mark's offset within mfile (dump) */
ix_t dc_markcnt; /* count of marks set (dump) */
bool_t dc_rampr; /* can randomly access file (not a pipe) */
bool_t dc_isrmtpr; /* is accessed via rmt */
bool_t dc_israwdevpr; /* is a raw disk partition */
};
typedef struct drive_context drive_context_t;
/* declarations of externally defined global variables ***********************/
extern size_t pgsz;
/* 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 * );
/* declare manager operators
*/
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_rewind( drive_t * );
static intgen_t do_erase( drive_t * );
static intgen_t do_get_device_class( drive_t * );
static void do_quit( drive_t * );
/* definition of locally defined global variables ****************************/
/* simple drive strategy for file or stdin. referenced by drive.c
*/
drive_strategy_t drive_strategy_simple = {
DRIVE_STRATEGY_SIMPLE, /* ds_id */
"file dump (drive_simple)", /* ds_description */
ds_match, /* ds_match */
ds_instantiate, /* ds_instantiate */
0x1000000ll, /* ds_recmarksep */
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 */
0, /* do_fsf */
0, /* do_bsf */
do_rewind, /* do_rewind */
do_erase, /* do_erase */
0, /* do_eject_media */
do_get_device_class, /* do_get_device_class */
0, /* do_display_metrics */
do_quit, /* do_quit */
};
/* 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 )
{
bool_t isrmtpr;
struct stat64 statbuf;
/* sanity checks
*/
ASSERT( ! ( sizeofmember( drive_context_t, dc_buf ) % PGSZ ));
/* determine if this is an rmt file. if so, give a weak match:
* might be an ordinary file accessed via the rmt protocol.
*/
if ( strchr( drivep->d_pathname, ':') ) {
isrmtpr = BOOL_TRUE;
} else {
isrmtpr = BOOL_FALSE;
}
if ( isrmtpr ) {
return 1;
}
/* willing to pick up anything not picked up by other strategies,
* as long as it exists and is not a directory
*/
if ( ! strcmp( drivep->d_pathname, "stdio" )) {
return 1;
}
if ( stat64( drivep->d_pathname, &statbuf )) {
return -1;
}
if ( S_ISDIR( statbuf.st_mode )) {
return -1;
}
return 1;
}
/* 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;
/* hook up the drive ops
*/
drivep->d_opsp = &drive_ops;
/* initialize the drive context - allocate a page-aligned
* structure, so the buffer is page-aligned.
*/
contextp = ( drive_context_t * )memalign( PGSZ,
sizeof( drive_context_t ));
ASSERT( contextp );
ASSERT( ( void * )contextp->dc_buf == ( void * )contextp );
memset( ( void * )contextp, 0, sizeof( *contextp ));
/* scan drive device pathname to see if remote tape
*/
if ( strchr( drivep->d_pathname, ':') ) {
contextp->dc_isrmtpr = BOOL_TRUE;
} else {
contextp->dc_isrmtpr = BOOL_FALSE;
}
/* determine drive capabilities of and open the named file.
*/
drivep->d_capabilities = 0;
drivep->d_capabilities |= DRIVE_CAP_AUTOREWIND;
if ( ! strcmp( drivep->d_pathname, "stdio" )) {
#ifdef DUMP
contextp->dc_fd = 1;
#endif /* DUMP */
#ifdef RESTORE
drivep->d_capabilities |= DRIVE_CAP_READ;
contextp->dc_fd = 0;
#endif /* RESTORE */
} else if ( contextp->dc_isrmtpr ) {
intgen_t oflags;
#ifdef DUMP
oflags = O_WRONLY | O_CREAT | O_TRUNC;
#endif /* DUMP */
#ifdef RESTORE
oflags = O_RDONLY;
drivep->d_capabilities |= DRIVE_CAP_READ;
#endif /* RESTORE */
contextp->dc_rampr = BOOL_FALSE;
contextp->dc_fd = open( drivep->d_pathname,
oflags,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
if ( contextp->dc_fd < 0 ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("unable to open %s: %s\n"),
drivep->d_pathname,
strerror( errno ));
return BOOL_FALSE;
}
} else {
intgen_t oflags = 0;
struct stat statbuf;
intgen_t rval;
rval = stat( drivep->d_pathname, &statbuf );
#ifdef DUMP
if ( rval ) {
if ( errno != ENOENT ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("stat of %s failed: %s\n"),
drivep->d_pathname,
strerror( errno ));
return BOOL_FALSE;
}
drivep->d_capabilities |= DRIVE_CAP_REWIND;
drivep->d_capabilities |= DRIVE_CAP_READ;
drivep->d_capabilities |= DRIVE_CAP_ERASE;
contextp->dc_rampr = BOOL_TRUE;
oflags = O_RDWR | O_CREAT;
} else {
switch( statbuf.st_mode & S_IFMT ) {
case S_IFREG:
drivep->d_capabilities |= DRIVE_CAP_ERASE;
drivep->d_capabilities |= DRIVE_CAP_REWIND;
drivep->d_capabilities |= DRIVE_CAP_READ;
contextp->dc_rampr = BOOL_TRUE;
oflags = O_RDWR;
break;
case S_IFCHR:
contextp->dc_israwdevpr = BOOL_TRUE;
/* intentional fall-through */
case S_IFBLK:
/* intentional fall-through */
case S_IFIFO:
oflags = O_WRONLY;
break;
default:
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("cannot dump to %s "
"file type %x\n"),
drivep->d_pathname,
statbuf.st_mode & S_IFMT );
return BOOL_FALSE;
}
}
#endif /* DUMP */
#ifdef RESTORE
if ( rval ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("stat of %s failed: %s\n"),
drivep->d_pathname,
strerror( errno ));
return BOOL_FALSE;
}
oflags = O_RDONLY;
switch( statbuf.st_mode & S_IFMT ) {
case S_IFREG:
case S_IFCHR:
case S_IFBLK:
drivep->d_capabilities |= DRIVE_CAP_REWIND;
drivep->d_capabilities |= DRIVE_CAP_READ;
break;
case S_IFIFO:
drivep->d_capabilities |= DRIVE_CAP_READ;
break;
default:
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("cannot restore from %s file type %x\n"),
drivep->d_pathname,
statbuf.st_mode & S_IFMT );
return BOOL_FALSE;
}
#endif /* RESTORE */
contextp->dc_fd = open( drivep->d_pathname,
oflags,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
if ( contextp->dc_fd < 0 ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("unable to open %s: %s\n"),
drivep->d_pathname,
strerror( errno ));
return BOOL_FALSE;
}
}
/* initialize the operational mode. fmarkcnt is bumped on each
* end_read and end_write, set back to 0 on rewind.
*/
contextp->dc_mode = OM_NONE;
contextp->dc_fmarkcnt = 0;
drivep->d_contextp = ( void * )contextp;
drivep->d_cap_est = -1;
drivep->d_rate_est = -1;
return BOOL_TRUE;
}
/* drive op init - second pass drive manager init - nothing to do,
* since async I/O not used.
*/
/* 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 */
#ifdef DUMP
/* fill in media strategy id: artifact of first version of xfsdump
*/
mwhdrp->mh_strategyid = MEDIA_STRATEGY_SIMPLE;
#endif /* DUMP */
return BOOL_TRUE;
}
/* drive op init - third pass drive manager init - nothing to do,
* since async I/O not used.
*/
/* ARGSUSED */
static bool_t
do_sync( drive_t *drivep )
{
return BOOL_TRUE;
}
/* drive op begin_read - prepare file for reading - main job is to
* read the media hdr
*/
static intgen_t
do_begin_read( drive_t *drivep )
{
#ifdef DEBUG
intgen_t dcaps = drivep->d_capabilities;
#endif
global_hdr_t *grhdrp = drivep->d_greadhdrp;
drive_hdr_t *drhdrp = drivep->d_readhdrp;
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
intgen_t nread;
intgen_t rval;
global_hdr_t *tmphdr = (global_hdr_t *)malloc(GLOBAL_HDR_SZ);
drive_hdr_t *tmpdh = (drive_hdr_t *)tmphdr->gh_upper;
media_hdr_t *tmpmh = (media_hdr_t *)tmpdh->dh_upper;
content_hdr_t *tmpch = (content_hdr_t *)tmpmh->mh_upper;
content_inode_hdr_t *tmpcih = (content_inode_hdr_t *)tmpch->ch_specific;
drive_hdr_t *dh = (drive_hdr_t *)grhdrp->gh_upper;
media_hdr_t *mh = (media_hdr_t *)dh->dh_upper;
content_hdr_t *ch = (content_hdr_t *)mh->mh_upper;
content_inode_hdr_t *cih = (content_inode_hdr_t *)ch->ch_specific;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple begin_read( )\n" );
/* verify protocol being followed
*/
ASSERT( dcaps & DRIVE_CAP_READ );
ASSERT( contextp->dc_fd >= 0 );
ASSERT( contextp->dc_mode == OM_NONE );
/* can only read one media file
*/
if ( contextp->dc_fmarkcnt > 0 ) {
return DRIVE_ERROR_EOM;
}
/* prepare the drive context
*/
contextp->dc_ownedp = 0;
contextp->dc_emptyp = &contextp->dc_buf[ 0 ];
contextp->dc_nextp = contextp->dc_emptyp;
contextp->dc_bufstroff = 0;
/* read the global header using the read_buf() utility function and
* my own read and return_read_buf operators. spoof the mode
*/
contextp->dc_mode = OM_READ;
nread = read_buf( ( char * )tmphdr,
GLOBAL_HDR_SZ,
( void * )drivep,
( rfp_t )drivep->d_opsp->do_read,
( rrbfp_t )drivep->d_opsp->do_return_read_buf,
&rval );
contextp->dc_mode = OM_NONE;
/* if EOD and nread is zero, there is no data after the file mark
*/
if ( rval == DRIVE_ERROR_EOD ) {
if ( nread == 0 ) {
free(tmphdr);
return DRIVE_ERROR_BLANK;
} else {
free(tmphdr);
return DRIVE_ERROR_FORMAT;
}
}
if ( rval ) {
free(tmphdr);
return rval;
}
ASSERT( ( size_t )nread == GLOBAL_HDR_SZ );
mlog(MLOG_NITTY, "do_begin_read: global_hdr\n"
"\tgh_magic %.100s\n"
"\tgh_version %u\n"
"\tgh_checksum %u\n"
"\tgh_timestamp %u\n"
"\tgh_ipaddr %llu\n"
"\tgh_hostname %.100s\n"
"\tgh_dumplabel %.100s\n",
tmphdr->gh_magic,
tmphdr->gh_version,
tmphdr->gh_checksum,
tmphdr->gh_timestamp,
tmphdr->gh_ipaddr,
tmphdr->gh_hostname,
tmphdr->gh_dumplabel);
/* check the checksum
*/
if ( ! global_hdr_checksum_check( tmphdr )) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("media file header checksum error\n") );
free(tmphdr);
return DRIVE_ERROR_CORRUPTION;
}
xlate_global_hdr(tmphdr, grhdrp, 1);
xlate_drive_hdr(tmpdh, dh, 1);
*(( drive_mark_t * )dh->dh_specific) =
INT_GET(*(( drive_mark_t * )tmpdh->dh_specific), ARCH_CONVERT);
xlate_media_hdr(tmpmh, mh, 1);
xlate_content_hdr(tmpch, ch, 1);
xlate_content_inode_hdr(tmpcih, cih, 1);
free(tmphdr);
/* check the magic number
*/
if ( strncmp( grhdrp->gh_magic, GLOBAL_HDR_MAGIC, GLOBAL_HDR_MAGIC_SZ)) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("media file header magic number mismatch: %s, %s\n"),
grhdrp->gh_magic,
GLOBAL_HDR_MAGIC);
return DRIVE_ERROR_FORMAT;
}
/* check the version
*/
if ( global_version_check( grhdrp->gh_version ) != BOOL_TRUE ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("unrecognized media file header version (%d)\n"),
grhdrp->gh_version );
return DRIVE_ERROR_VERSION;
}
/* check the strategy id
*/
if ( drhdrp->dh_strategyid != drive_strategy_simple.ds_id ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("unrecognized drive strategy ID "
"(media says %d, expected %d)\n"),
drhdrp->dh_strategyid, drive_strategy_simple.ds_id );
return DRIVE_ERROR_FORMAT;
}
/* record the offset of the first mark
*/
contextp->dc_firstmark = *( drive_mark_t * )drhdrp->dh_specific;
/* adjust the drive capabilities based on presence of first mark.
* this is a hack workaround for a bug in xfsdump which causes the
* first mark offset to not always be placed in the hdr.
*/
if ( contextp->dc_firstmark ) {
drivep->d_capabilities |= DRIVE_CAP_NEXTMARK;
}
/* note that a successful begin_read ocurred
*/
contextp->dc_mode = OM_READ;
return 0;
}
/* read - supply the caller with some filled buffer
*/
static char *
do_read( drive_t *drivep,
size_t wantedcnt,
size_t *actualcntp,
intgen_t *rvalp )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
size_t remainingcnt;
size_t actualcnt;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple read( want %u )\n",
wantedcnt );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_READ );
ASSERT( ! contextp->dc_ownedp );
ASSERT( wantedcnt > 0 );
/* pre-initialize reference return
*/
*rvalp = 0;
/* count number of unread bytes in buffer
*/
ASSERT( contextp->dc_emptyp >= contextp->dc_nextp );
remainingcnt = ( size_t )( contextp->dc_emptyp - contextp->dc_nextp );
/* if no unread bytes in buffer, refill
*/
if ( remainingcnt == 0 ) {
size_t bufhowfullcnt;
int nread;
/* calculate how many bytes were in the buffer. this will
* be used to adjust the recorded offset (relative to the
* beginning of the media file) of the top of the buffer
* after we refill the buffer.
*/
bufhowfullcnt = ( size_t )
( contextp->dc_emptyp - contextp->dc_buf );
/* attempt to fill the buffer. nread may be less if at EOF
*/
nread = read( contextp->dc_fd, contextp->dc_buf, BUFSZ );
if ( nread < 0 ) {
*rvalp = DRIVE_ERROR_DEVICE;
return 0;
}
/* adjust the recorded offset of the top of the buffer
* relative to the beginning of the media file
*/
contextp->dc_bufstroff += ( off64_t )bufhowfullcnt;
/* record the ptrs to the first empty byte and the next
* byte to be read
*/
ASSERT( nread <= BUFSZ );
contextp->dc_emptyp = contextp->dc_buf + nread;
contextp->dc_nextp = contextp->dc_buf;
/* if no bytes were read, the caller has seen all bytes.
*/
if ( nread == 0 ) {
*rvalp = DRIVE_ERROR_EOD;
return 0;
}
/* adjust the remaining count
*/
remainingcnt = ( size_t )nread;
}
/* the caller specified at most how many bytes he wants. if less
* than that remain unread in buffer, just return that many.
*/
actualcnt = min( wantedcnt, remainingcnt );
/* set the owned ptr to the first byte to be supplied
*/
contextp->dc_ownedp = contextp->dc_nextp;
/* advance the next ptr to the next byte to be supplied
*/
contextp->dc_nextp += actualcnt;
ASSERT( contextp->dc_nextp <= contextp->dc_emptyp );
/* return the actual number of bytes supplied, and a ptr to the first
*/
*actualcntp = actualcnt;
return contextp->dc_ownedp;
}
/* return_read_buf - lets the caller give back all of the
* buffer obtained from a call to do_read().
*/
/* ARGSUSED */
static void
do_return_read_buf( drive_t *drivep, char *retp, size_t retcnt )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
/* REFERENCED */
size_t ownedcnt;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple return_read_buf( returning %u )\n",
retcnt );
/* verify protocol
*/
ASSERT( contextp->dc_mode == OM_READ );
ASSERT( contextp->dc_ownedp );
/* verify returning right buffer
*/
ASSERT( retp == contextp->dc_ownedp );
/* verify all of buffer provided is being returned
*/
ownedcnt = ( size_t )( contextp->dc_nextp - contextp->dc_ownedp );
ASSERT( retcnt == ownedcnt );
/* indicate nothing now owned by caller
*/
contextp->dc_ownedp = 0;
}
/* the mark is simply the offset into the media file of the
* next byte to be read
*/
static void
do_get_mark( drive_t *drivep, drive_mark_t *markp )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
off64_t nextoff;
off64_t strmoff;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple get_mark( )\n" );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_READ );
ASSERT( ! contextp->dc_ownedp );
/* calculate the offset of the next byte to be supplied relative to
* the beginning of the buffer and relative to the beginning of
* ther media file.
*/
nextoff = ( off64_t )( contextp->dc_nextp - contextp->dc_buf );
strmoff = contextp->dc_bufstroff + nextoff;
*markp = ( drive_mark_t )strmoff;
}
/* seek forward to the specified mark. the caller must not have already read
* past that point.
*/
static intgen_t
do_seek_mark( drive_t *drivep, drive_mark_t *markp )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
off64_t mark = *( off64_t * )markp;
off64_t nextoff;
off64_t strmoff;
/* REFERENCED */
intgen_t nread;
off64_t nreadneeded64;
intgen_t nreadneeded;
intgen_t rval;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple seek_mark( )\n" );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_READ );
ASSERT( ! contextp->dc_ownedp );
/* calculate the current offset within the media file
* of the next byte to be read
*/
nextoff = ( off64_t )( contextp->dc_nextp - contextp->dc_buf );
strmoff = contextp->dc_bufstroff + nextoff;
/* if the caller attempts to seek past the current offset,
* this is really bad
*/
if ( strmoff > mark ) {
return DRIVE_ERROR_CORE;
}
/* use read_buf util func to eat up difference
*/
nreadneeded64 = mark - strmoff;
ASSERT( nreadneeded64 <= INTGENMAX );
nreadneeded = ( intgen_t )nreadneeded64;
nread = read_buf( 0,
( size_t )nreadneeded,
( void * )drivep,
( rfp_t )drivep->d_opsp->do_read,
( rrbfp_t )drivep->d_opsp->do_return_read_buf,
&rval );
if ( rval ) {
return rval;
}
ASSERT( nread == nreadneeded );
/* verify we are on the mark
*/
nextoff = ( off64_t )( contextp->dc_nextp - contextp->dc_buf );
strmoff = contextp->dc_bufstroff + nextoff;
ASSERT( strmoff == mark );
return 0;
}
/* seek forward to the next mark. we only know of one mark, the first
* mark in the media file (recorded in the header). if the caller
* has already read past that mark, blow up.
*/
static intgen_t
do_next_mark( drive_t *drivep )
{
#ifdef DEBUG
intgen_t dcaps = drivep->d_capabilities;
#endif
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
drive_mark_t mark = contextp->dc_firstmark;
intgen_t rval;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple next_mark( )\n" );
/* assert protocol
*/
ASSERT( dcaps & DRIVE_CAP_NEXTMARK );
ASSERT( contextp->dc_mode == OM_READ );
ASSERT( ! contextp->dc_ownedp );
if ( ! mark ) {
return DRIVE_ERROR_EOF;
}
rval = do_seek_mark( drivep, ( drive_mark_t * )&mark );
if ( rval ) {
return rval;
}
return 0;
}
/* end_read - tell the drive we are done reading the media file
* just discards any buffered data
*/
void
do_end_read( drive_t *drivep )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple end_read( )\n" );
/* be sure we are following protocol
*/
ASSERT( contextp->dc_mode == OM_READ );
contextp->dc_mode = OM_NONE;
/* bump the file mark cnt
*/
contextp->dc_fmarkcnt++;
ASSERT( contextp->dc_fmarkcnt == 1 );
}
/* begin_write - prepare file for writing
*/
static intgen_t
do_begin_write( drive_t *drivep )
{
intgen_t dcaps = drivep->d_capabilities;
global_hdr_t *gwhdrp = drivep->d_gwritehdrp;
drive_hdr_t *dwhdrp = drivep->d_writehdrp;
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
int rval;
global_hdr_t *tmphdr;
drive_hdr_t *tmpdh;
media_hdr_t *tmpmh;
content_hdr_t *tmpch;
content_inode_hdr_t *tmpcih;
drive_hdr_t *dh = (drive_hdr_t *)gwhdrp->gh_upper;
media_hdr_t *mh = (media_hdr_t *)dh->dh_upper;
content_hdr_t *ch = (content_hdr_t *)mh->mh_upper;
content_inode_hdr_t *cih = (content_inode_hdr_t *)ch->ch_specific;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple begin_write( )\n" );
/* sanity checks
*/
ASSERT( dwhdrp->dh_strategyid == DRIVE_STRATEGY_SIMPLE );
/* assert protocol
*/
ASSERT( contextp->dc_fd >= 0 );
ASSERT( contextp->dc_mode == OM_NONE );
/* only one media file may be written
*/
if ( contextp->dc_fmarkcnt > 0 ) {
return DRIVE_ERROR_EOM;
}
/* indicate in the header that there is no recorded mark.
*/
*( ( off64_t * )dwhdrp->dh_specific ) = 0;
/* prepare the drive context. initially the caller does not own
* any of the write buffer, so the next portion of the buffer to
* be supplied is the top of the buffer. emptyp never changes;
* it always points to the byte after the end of the buffer. markcnt
* keeps track of the number marks the caller has set in the media file.
*/
contextp->dc_ownedp = 0;
contextp->dc_nextp = contextp->dc_buf;
contextp->dc_emptyp = contextp->dc_buf + sizeof( contextp->dc_buf );
contextp->dc_bufstroff = 0;
contextp->dc_markcnt = 0;
/* truncate the destination if it supports read.
*/
if ( dcaps & DRIVE_CAP_READ ) {
rval = ftruncate( contextp->dc_fd, 0 );
if ( rval ) {
mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("attempt to truncate %s failed: %d (%s)\n"),
drivep->d_pathname,
errno,
strerror( errno ));
}
}
/* set the mode
*/
contextp->dc_mode = OM_WRITE;
tmphdr = (global_hdr_t *)malloc(GLOBAL_HDR_SZ);
ASSERT(tmphdr);
memset(tmphdr, 0, GLOBAL_HDR_SZ);
tmpdh = (drive_hdr_t *)tmphdr->gh_upper;
tmpmh = (media_hdr_t *)tmpdh->dh_upper;
tmpch = (content_hdr_t *)tmpmh->mh_upper;
tmpcih = (content_inode_hdr_t *)tmpch->ch_specific;
xlate_global_hdr(gwhdrp, tmphdr, 1);
xlate_drive_hdr(dh, tmpdh, 1);
INT_SET(*(( drive_mark_t * )tmpdh->dh_specific),
ARCH_CONVERT,
*(( drive_mark_t * )dh->dh_specific));
xlate_media_hdr(mh, tmpmh, 1);
xlate_content_hdr(ch, tmpch, 1);
xlate_content_inode_hdr(cih, tmpcih, 1);
/* checksum the header
*/
global_hdr_checksum_set( tmphdr );
mlog(MLOG_NITTY, "do_begin_write: global_hdr\n"
"\tgh_magic %.100s\n"
"\tgh_version %u\n"
"\tgh_checksum %u\n"
"\tgh_timestamp %u\n"
"\tgh_ipaddr %llu\n"
"\tgh_hostname %.100s\n"
"\tgh_dumplabel %.100s\n",
tmphdr->gh_magic,
tmphdr->gh_version,
tmphdr->gh_checksum,
tmphdr->gh_timestamp,
tmphdr->gh_ipaddr,
tmphdr->gh_hostname,
tmphdr->gh_dumplabel);
if ( ! global_hdr_checksum_check( tmphdr )) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_DRIVE,
_("media file header checksum error\n") );
}
else {
mlog( MLOG_NITTY, "media file header checksum OK!\n" );
}
/* write the header using the write_buf() utility function and
* my own get_write_buf and write operators.
*/
rval = write_buf( ( char * )tmphdr,
GLOBAL_HDR_SZ,
( void * )drivep,
( gwbfp_t )drivep->d_opsp->do_get_write_buf,
( wfp_t )drivep->d_opsp->do_write );
free(tmphdr);
/* if error while writing hdr, undo mode
*/
if ( rval ) {
contextp->dc_mode = OM_NONE;
}
return rval;
}
/* do_set_mark - record a markrecord and callback
*/
static void
do_set_mark( drive_t *drivep,
drive_mcbfp_t cbfuncp,
void *cbcontextp,
drive_markrec_t *markrecp )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
drive_mark_t mark;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple set_mark( )\n" );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_WRITE );
ASSERT( ! contextp->dc_ownedp );
ASSERT( contextp->dc_nextp );
/* calculate the mark offset
*/
mark = ( drive_mark_t )( contextp->dc_bufstroff
+
( off64_t )
( contextp->dc_nextp - contextp->dc_buf ));
/* fill in the mark field of the mark record
*/
markrecp->dm_log = mark;
/* bump the mark count. if this is the first mark, record it
* in the drive strategy-specific header. this allows multiple
* media object restores to work. NOTE that the mark will not
* be recorded if the destination does not support random access
* and the write buffer has been flushed at least once.
* this is hidden by save_first_mark, and detected during restore
* by noting the first mark offset is NULL. to do this, must seek
* back to the header, rewrite and rechecksum the header,
* and seek back to the current position. HOWEVER, if the write
* buffer has not yet been flushed, we can just edit the buffer.
*/
contextp->dc_markcnt++;
if ( contextp->dc_markcnt == 1 ) {
if ( contextp->dc_bufstroff == 0 ) {
/* cast the write buffer into a media file hdr
*/
global_hdr_t *gwhdrp =
( global_hdr_t * )contextp->dc_buf;
drive_hdr_t *dwhdrp = ( drive_hdr_t * )gwhdrp->gh_upper;
mlog( MLOG_NITTY | MLOG_DRIVE,
"re-writing media file header with first mark "
"(in buffer)\n" );
/* record mark in hdr
*/
INT_SET(*( ( drive_mark_t * )dwhdrp->dh_specific ), ARCH_CONVERT, mark);
/* adjust header checksum
*/
global_hdr_checksum_set( gwhdrp );
} else if ( contextp->dc_rampr ) {
global_hdr_t *gwhdrp = drivep->d_gwritehdrp;
drive_hdr_t *dwhdrp = drivep->d_writehdrp;
off64_t newoff;
/* REFERENCED */
intgen_t nwritten;
/* assert the header has been flushed
*/
ASSERT( contextp->dc_bufstroff >= sizeof( *gwhdrp ));
/* record mark in hdr
*/
INT_SET(*( ( drive_mark_t * )dwhdrp->dh_specific ), ARCH_CONVERT, mark);
/* adjust header checksum
*/
global_hdr_checksum_set( gwhdrp );
/* seek to beginning
*/
newoff = lseek64( contextp->dc_fd, ( off64_t )0, SEEK_SET );
if ( newoff < 0 ) {
mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("could not save first mark: %d (%s)\n"),
errno,
strerror( errno ));
} else {
global_hdr_t *tmphdr;
drive_hdr_t *tmpdh;
media_hdr_t *tmpmh;
content_hdr_t *tmpch;
content_inode_hdr_t *tmpcih;
drive_hdr_t *dh = (drive_hdr_t *)gwhdrp->gh_upper;
media_hdr_t *mh = (media_hdr_t *)dh->dh_upper;
content_hdr_t *ch = (content_hdr_t *)mh->mh_upper;
content_inode_hdr_t *cih = (content_inode_hdr_t *)ch->ch_specific;
ASSERT( newoff == 0 );
/* write and seek back to current offset
*/
mlog( MLOG_NITTY | MLOG_DRIVE,
"re-writing media file header "
"with first mark "
"(on media)\n" );
tmphdr = (global_hdr_t *)malloc(GLOBAL_HDR_SZ);
ASSERT(tmphdr);
tmpdh = (drive_hdr_t *)tmphdr->gh_upper;
tmpmh = (media_hdr_t *)tmpdh->dh_upper;
tmpch = (content_hdr_t *)tmpmh->mh_upper;
tmpcih = (content_inode_hdr_t *)tmpch->ch_specific;
xlate_global_hdr(gwhdrp, tmphdr, 1);
xlate_drive_hdr(dh, tmpdh, 1);
INT_SET(*(( drive_mark_t * )tmpdh->dh_specific),
ARCH_CONVERT,
*(( drive_mark_t * )dh->dh_specific));
xlate_media_hdr(mh, tmpmh, 1);
xlate_content_hdr(ch, tmpch, 1);
xlate_content_inode_hdr(cih, tmpcih, 1);
/* adjust header checksum
*/
global_hdr_checksum_set( tmphdr );
mlog(MLOG_NITTY, "do_set_mark: global_hdr\n"
"\tgh_magic %.100s\n"
"\tgh_version %u\n"
"\tgh_checksum %u\n"
"\tgh_timestamp %u\n"
"\tgh_ipaddr %llu\n"
"\tgh_hostname %.100s\n"
"\tgh_dumplabel %.100s\n",
tmphdr->gh_magic,
tmphdr->gh_version,
tmphdr->gh_checksum,
tmphdr->gh_timestamp,
tmphdr->gh_ipaddr,
tmphdr->gh_hostname,
tmphdr->gh_dumplabel);
nwritten = write( contextp->dc_fd,
tmphdr,
sizeof( *tmphdr ));
ASSERT( ( size_t )nwritten == sizeof( *tmphdr ));
free(tmphdr);
newoff = lseek64( contextp->dc_fd,
contextp->dc_bufstroff,
SEEK_SET );
ASSERT( newoff == contextp->dc_bufstroff );
}
}
}
/* if all written are committed, send the mark back immediately.
* otherwise put the mark record on the tail of the queue.
*/
if ( contextp->dc_nextp == contextp->dc_buf ) {
ASSERT( drivep->d_markrecheadp == 0 );
( * cbfuncp )( cbcontextp, markrecp, BOOL_TRUE );
return;
} else {
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;
}
}
}
/* get_write_buf - supply the caller with buffer space. the caller
* will fill the space with data, then call write() to request that
* some or all of the buffer be written and to return the buffer space.
*/
/*ARGSUSED*/
static char *
do_get_write_buf( drive_t *drivep, size_t wanted_bufsz, size_t *actual_bufszp )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
size_t remaining_bufsz;
size_t actual_bufsz;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple get_write_buf( want %u )\n",
wanted_bufsz );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_WRITE );
ASSERT( ! contextp->dc_ownedp );
ASSERT( contextp->dc_nextp );
ASSERT( contextp->dc_nextp < contextp->dc_emptyp );
ASSERT( contextp->dc_ownedsz == 0 );
/* calculate how much buffer remains
*/
remaining_bufsz =( size_t )( contextp->dc_emptyp - contextp->dc_nextp );
/* give the caller the lesser of what he wants and what is available
*/
actual_bufsz = min( wanted_bufsz, remaining_bufsz );
/* caller will own that portion of buffer
*/
contextp->dc_ownedp = contextp->dc_nextp;
contextp->dc_ownedsz = actual_bufsz;
/* won't know nextp until write
*/
contextp->dc_nextp = 0;
/* return the portion of the buffer to the caller
*/
*actual_bufszp = actual_bufsz;
return contextp->dc_ownedp;
}
/* write - write and accept ownership of the portion of the buffer owned by the
* caller.
*/
/*ARGSUSED*/
static intgen_t
do_write( drive_t *drivep, char *bufp, size_t writesz )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
off64_t ownedstroff = contextp->dc_bufstroff
+
( off64_t )
( contextp->dc_ownedp - contextp->dc_buf );
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple write( "
"offset %lld (0x%llx 0%llo) "
"size %u (0x%x 0%o) "
")\n",
ownedstroff,
ownedstroff,
ownedstroff,
writesz,
writesz,
writesz,
0 );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_WRITE );
ASSERT( contextp->dc_ownedp );
ASSERT( bufp == contextp->dc_ownedp );
ASSERT( ! contextp->dc_nextp );
ASSERT( contextp->dc_ownedp < contextp->dc_emptyp );
ASSERT( writesz == contextp->dc_ownedsz );
/* calculate next portion of buffer available for get_write_buf,
* and indicate no portion is owned.
*/
contextp->dc_nextp = contextp->dc_ownedp + writesz;
ASSERT( contextp->dc_nextp <= contextp->dc_emptyp );
contextp->dc_ownedp = 0;
contextp->dc_ownedsz = 0;
if ( writesz == 0 ) {
return 0; /* returning unused buffer */
}
/* if buffer is full, flush it
*/
if ( contextp->dc_nextp == contextp->dc_emptyp ) {
intgen_t nwritten;
mlog( MLOG_DEBUG | MLOG_DRIVE,
"flushing write buf addr 0x%x size 0x%x\n",
contextp->dc_buf,
sizeof( contextp->dc_buf ));
contextp->dc_nextp = 0;
nwritten = write( contextp->dc_fd,
contextp->dc_buf,
sizeof( contextp->dc_buf ));
if ( nwritten < 0 ) {
mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("write to %s failed: %d (%s)\n"),
drivep->d_pathname,
errno,
strerror( errno ));
nwritten = 0;
}
contextp->dc_bufstroff += ( off64_t )nwritten;
drive_mark_commit( drivep, contextp->dc_bufstroff );
contextp->dc_nextp = contextp->dc_buf;
if ( ( size_t )nwritten < sizeof( contextp->dc_buf )) {
return DRIVE_ERROR_EOM;
}
}
return 0;
}
/* 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.
*/
static size_t
do_get_align_cnt( drive_t *drivep )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
__psint_t next_alignment_off;
char *next_alignment_point;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple get_align_cnt( )\n" );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_WRITE );
ASSERT( ! contextp->dc_ownedp );
ASSERT( contextp->dc_nextp );
ASSERT( contextp->dc_nextp < contextp->dc_emptyp );
/* calculate the next alignment point at or beyond the current nextp.
* the following algorithm works because dc_buf is 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_emptyp );
/* return the number of bytes to the next alignment point
*/
return ( size_t )( next_alignment_point - contextp->dc_nextp );
}
/* end_write - flush any buffered data, and return by reference how many
* bytes were committed.
*/
static intgen_t
do_end_write( drive_t *drivep, off64_t *ncommittedp )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
size_t remaining_bufsz;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple end_write( )\n" );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_WRITE );
ASSERT( ! contextp->dc_ownedp );
ASSERT( contextp->dc_nextp );
ASSERT( contextp->dc_nextp < contextp->dc_emptyp );
/* calculate length of un-written portion of buffer
*/
ASSERT( contextp->dc_nextp >= contextp->dc_buf );
remaining_bufsz = ( size_t )( contextp->dc_nextp - contextp->dc_buf );
if ( remaining_bufsz ) {
int nwritten;
if ( contextp->dc_israwdevpr ) {
remaining_bufsz = ( remaining_bufsz + ( BBSIZE - 1 ))
&
~( BBSIZE - 1 );
}
mlog( MLOG_DEBUG | MLOG_DRIVE,
"flushing write buf addr 0x%x size 0x%x\n",
contextp->dc_buf,
remaining_bufsz );
nwritten = write( contextp->dc_fd,
contextp->dc_buf,
remaining_bufsz );
if ( nwritten < 0 ) {
mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("write to %s failed: %d (%s)\n"),
drivep->d_pathname,
errno,
strerror( errno ));
drive_mark_discard( drivep );
*ncommittedp = contextp->dc_bufstroff;
contextp->dc_mode = OM_NONE;
return DRIVE_ERROR_DEVICE;
}
contextp->dc_bufstroff += ( off64_t )nwritten;
drive_mark_commit( drivep, contextp->dc_bufstroff );
if ( ( size_t )nwritten < remaining_bufsz ) {
*ncommittedp = contextp->dc_bufstroff;
contextp->dc_mode = OM_NONE;
return DRIVE_ERROR_EOM;
}
}
/* bump the file mark cnt
*/
contextp->dc_fmarkcnt++;
ASSERT( contextp->dc_fmarkcnt == 1 );
*ncommittedp = contextp->dc_bufstroff;
contextp->dc_mode = OM_NONE;
return 0;
}
/* rewind - return the current file offset to the beginning
*/
intgen_t
do_rewind( drive_t *drivep )
{
#ifdef DEBUG
intgen_t dcaps = drivep->d_capabilities;
#endif
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
off64_t newoff;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple rewind( )\n" );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_NONE );
ASSERT( dcaps & DRIVE_CAP_REWIND );
ASSERT( contextp->dc_fd >= 0 );
/* seek to beginning of file
*/
newoff = lseek64( contextp->dc_fd, ( off64_t )0, SEEK_SET );
if ( newoff ) {
ASSERT( newoff < 0 );
mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("could not rewind %s: %s\n"),
drivep->d_pathname,
strerror( errno ));
return DRIVE_ERROR_DEVICE;
}
return 0;
}
/* erase - truncate to zero length
*/
intgen_t
do_erase( drive_t *drivep )
{
#ifdef DEBUG
intgen_t dcaps = drivep->d_capabilities;
#endif
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
off64_t newoff;
intgen_t rval;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple erase( )\n" );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_NONE );
ASSERT( dcaps & DRIVE_CAP_ERASE );
ASSERT( contextp->dc_fd >= 0 );
/* seek to beginning of file
*/
newoff = lseek64( contextp->dc_fd, ( off64_t )0, SEEK_SET );
if ( newoff ) {
ASSERT( newoff < 0 );
mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("could not rewind %s in prep for erase: %s\n"),
drivep->d_pathname,
strerror( errno ));
return DRIVE_ERROR_DEVICE;
}
/* erase to beginning of file
*/
rval = ftruncate64( contextp->dc_fd, ( off64_t )0 );
if ( rval ) {
mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_DRIVE,
_("could not erase %s: %s (%d)\n"),
drivep->d_pathname,
strerror( errno ),
errno );
return DRIVE_ERROR_DEVICE;
}
contextp->dc_fmarkcnt = 0;
return 0;
}
/* get_media_class()
*/
/* ARGSUSED */
static intgen_t
do_get_device_class( drive_t *drivep )
{
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple get_device_class( )\n" );
ASSERT( drivep );
return DEVICE_NONREMOVABLE;
}
static void
do_quit( drive_t *drivep )
{
drive_context_t *contextp = ( drive_context_t * )drivep->d_contextp;
mlog( MLOG_NITTY | MLOG_DRIVE,
"drive_simple quit( )\n" );
/* assert protocol
*/
ASSERT( contextp->dc_mode == OM_NONE );
ASSERT( contextp );
/* close file
*/
if ( contextp->dc_fd > 1 ) {
close( contextp->dc_fd );
}
contextp->dc_fd = -1;
/* free context
*/
free( ( void * )contextp );
drivep->d_contextp = 0;
}