/*
 * 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 <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <malloc.h>
#include <sched.h>
#include <assert.h>
#include <string.h>
#include <uuid/uuid.h>
#include <xfs/xfs.h>
#include <xfs/jdm.h>	/* needed only for util.h include */

#include "config.h"

#include "types.h"
#include "util.h"	/* needed onyl for I/O routines */
#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 */
	int 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 int ds_match(int, char *[], drive_t *);
static int ds_instantiate(int, char *[], drive_t *);

/* declare manager operators
 */
static bool_t do_init(drive_t *);
static bool_t do_sync(drive_t *);
static int do_begin_read(drive_t *);
static char *do_read(drive_t *, size_t, size_t *, int *);
static void do_return_read_buf(drive_t *, char *, size_t);
static void do_get_mark(drive_t *, drive_mark_t *);
static int do_seek_mark(drive_t *, drive_mark_t *);
static int do_next_mark(drive_t *);
static void do_get_mark(drive_t *, drive_mark_t *);
static void do_end_read(drive_t *);
static int do_begin_write(drive_t *);
static void do_set_mark(drive_t *, drive_mcbfp_t, void *, drive_markrec_t *);
static char * do_get_write_buf(drive_t *, size_t, size_t *);
static int do_write(drive_t *, char *, size_t);
static size_t do_get_align_cnt(drive_t *);
static int do_end_write(drive_t *, off64_t *);
static int do_rewind(drive_t *);
static int do_erase(drive_t *);
static int 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 int
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) {
		int 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 {
		int oflags = 0;
		struct stat statbuf;
		int 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 int
do_begin_read(drive_t *drivep)
{
#ifdef DEBUG
	int 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;
	int nread;
	int 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,
         int *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 int
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 */
	int nread;
	off64_t nreadneeded64;
	int nreadneeded;
	int 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;
	while (nreadneeded64 > 0) {
		if (nreadneeded64 > INTGENMAX)
			nreadneeded = INTGENMAX;
		else
			nreadneeded = (int)nreadneeded64;
		nread = read_buf(0, nreadneeded, drivep,
				  (rfp_t)drivep->d_opsp->do_read,
				(rrbfp_t)drivep->d_opsp->do_return_read_buf,
				  &rval);
		if  (rval) {
			return rval;
		}
		nreadneeded64 -= nread;
	}

	/* 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 int
do_next_mark(drive_t *drivep)
{
#ifdef DEBUG
	int dcaps = drivep->d_capabilities;
#endif
	drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
	drive_mark_t mark = contextp->dc_firstmark;
	int 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 int
do_begin_write(drive_t *drivep)
{
	int 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 */
			int		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 int
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) {
		int 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;
	intptr_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 = (intptr_t)contextp->dc_nextp;
	next_alignment_off +=  PGMASK;
	next_alignment_off &= ~PGMASK;
	next_alignment_point = (char *)next_alignment_off;
	assert(next_alignment_point <= contextp->dc_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 int
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
 */
int
do_rewind(drive_t *drivep)
{
#ifdef DEBUG
	int 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
 */
int
do_erase(drive_t *drivep)
{
#ifdef DEBUG
	int dcaps = drivep->d_capabilities;
#endif
	drive_context_t *contextp = (drive_context_t *)drivep->d_contextp;
	off64_t newoff;
	int 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 int
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;
}
