X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=device-src%2Ftape-device.c;h=36498ddf8ce54dce6bc0da4942c371b7d205e7c4;hb=b116e9366c7b2ea2c2eb53b0a13df4090e176235;hp=77e7bfa068a27712a61dc8ba6e899e761ee80a76;hpb=96f35b20267e8b1a1c846d476f27fcd330e0b018;p=debian%2Famanda diff --git a/device-src/tape-device.c b/device-src/tape-device.c index 77e7bfa..36498dd 100644 --- a/device-src/tape-device.c +++ b/device-src/tape-device.c @@ -1,47 +1,128 @@ /* - * Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved. - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 2.1 as - * published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, but + * Copyright (c) 2007, 2008, 2009, 2010 Zmanda, 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 version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300 - * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com + * 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 to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300 + * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com */ +#include "amanda.h" +#include "pipespawn.h" #include /* memset() */ #include "util.h" -#include "tape-device.h" -#include "tape-ops.h" +#include "device.h" -/* This is equal to 2*1024*1024*1024 - 16*1024*1024 - 1, but written +#ifdef HAVE_SYS_TAPE_H +# include +#endif +#ifdef HAVE_SYS_MTIO_H +# include +#endif +#ifdef HAVE_LIMITS_H +# include +#endif + +/* This is equal to 2*1024*1024*1024 - 16*1024*1024 - 1, but written explicitly to avoid overflow issues. */ #define RESETOFS_THRESHOLD (0x7effffff) /* Largest possible block size on SCSI systems. */ #define LARGEST_BLOCK_ESTIMATE (16 * 1024 * 1024) +/* + * Type checking and casting macros + */ + +#define TYPE_TAPE_DEVICE (tape_device_get_type()) +#define TAPE_DEVICE(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), tape_device_get_type(), TapeDevice) +#define TAPE_DEVICE_CONST(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), tape_device_get_type(), TapeDevice const) +#define TAPE_DEVICE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), tape_device_get_type(), TapeDeviceClass) +#define IS_TAPE_DEVICE(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), tape_device_get_type ()) +#define TAPE_DEVICE_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), tape_device_get_type(), TapeDeviceClass) +GType tape_device_get_type (void); + +/* + * Main object structure + */ +typedef struct TapeDevicePrivate_s TapeDevicePrivate; +typedef struct _TapeDevice { + Device __parent__; + + /* It should go without saying that all this stuff is + * look-but-don't-touch. */ + + /* characteristics of the device */ + gboolean fsf, bsf, fsr, bsr, eom, bsf_after_eom, broken_gmt_online; + gboolean leom; + gboolean nonblocking_open, fsf_after_filemark; + int final_filemarks; + + /* 0 if we opened with O_RDWR; error otherwise. */ + gboolean write_open_errno; + int fd; + + TapeDevicePrivate * private; +} TapeDevice; + struct TapeDevicePrivate_s { /* This holds the total number of bytes written to the device, modulus RESETOFS_THRESHOLD. */ int write_count; char * device_filename; - gsize read_buffer_size; + gsize read_block_size; +}; + +/* + * Class definition + */ + +typedef struct _TapeDeviceClass TapeDeviceClass; +struct _TapeDeviceClass { + DeviceClass __parent__; }; +void tape_device_register(void); + +/* useful callback for tape ops */ +void tape_device_set_capabilities(TapeDevice *self, + gboolean fsf, PropertySurety fsf_surety, PropertySource fsf_source, + gboolean fsf_after_filemark, PropertySurety faf_surety, PropertySource faf_source, + gboolean bsf, PropertySurety bsf_surety, PropertySource bsf_source, + gboolean fsr, PropertySurety fsr_surety, PropertySource fsr_source, + gboolean bsr, PropertySurety bsr_surety, PropertySource bsr_source, + gboolean eom, PropertySurety eom_surety, PropertySource eom_source, + gboolean leom, PropertySurety leom_surety, PropertySource leom_source, + gboolean bsf_after_eom, PropertySurety bae_surety, PropertySource bae_source, + guint final_filemarks, PropertySurety ff_surety, PropertySource ff_source); + +/* Real Operations (always return FALSE if not implemented) */ +gboolean tape_rewind(int fd); +gboolean tape_fsf(int fd, guint count); +gboolean tape_bsf(int fd, guint count); +gboolean tape_fsr(int fd, guint count); +gboolean tape_bsr(int fd, guint count); +gint tape_fileno(int fd); + +/* tape_fileno returns tape position file number, or one of these: */ +#define TAPE_OP_ERROR -1 +#define TAPE_POSITION_UNKNOWN -2 + /* Possible (abstracted) results from a system I/O operation. */ typedef enum { RESULT_SUCCESS, - RESULT_ERROR, /* Undefined error. */ + RESULT_ERROR, /* Undefined error (*errmsg set) */ RESULT_SMALL_BUFFER, /* Tried to read with a buffer that is too small. */ RESULT_NO_DATA, /* End of File, while reading */ @@ -51,26 +132,47 @@ typedef enum { RESULT_MAX } IoResult; -/* - * Our device-specific properties. These are not static because they are - * accessed from the OS-specific tape-*.c files. - */ -DevicePropertyBase device_property_broken_gmt_online; -DevicePropertyBase device_property_fsf; -DevicePropertyBase device_property_fsf_after_filemark; -DevicePropertyBase device_property_bsf; -DevicePropertyBase device_property_fsr; -DevicePropertyBase device_property_bsr; -DevicePropertyBase device_property_eom; -DevicePropertyBase device_property_bsf_after_eom; -DevicePropertyBase device_property_nonblocking_open; -DevicePropertyBase device_property_final_filemarks; +/* returns a fileno like tape_fileno */ +gint tape_eod(int fd); -void tape_device_register(void); +gboolean tape_weof(int fd, guint8 count); +gboolean tape_setcompression(int fd, gboolean on); + +gboolean tape_offl(int fd); + +DeviceStatusFlags tape_is_tape_device(int fd); +DeviceStatusFlags tape_is_ready(int fd, TapeDevice *t_self); #define tape_device_read_size(self) \ - (((TapeDevice *)(self))->private->read_buffer_size? \ - ((TapeDevice *)(self))->private->read_buffer_size : ((Device *)(self))->block_size) + (((TapeDevice *)(self))->private->read_block_size? \ + ((TapeDevice *)(self))->private->read_block_size : ((Device *)(self))->block_size) + +/* + * Our device-specific properties. + */ + +#define PROPERTY_BROKEN_GMT_ONLINE (device_property_broken_gmt_online.ID) +#define PROPERTY_FSF (device_property_fsf.ID) +#define PROPERTY_FSF_AFTER_FILEMARK (device_property_fsf_after_filemark.ID) +#define PROPERTY_BSF (device_property_bsf.ID) +#define PROPERTY_FSR (device_property_fsr.ID) +#define PROPERTY_BSR (device_property_bsr.ID) +#define PROPERTY_EOM (device_property_eom.ID) +#define PROPERTY_BSF_AFTER_EOM (device_property_bsf_after_eom.ID) +#define PROPERTY_NONBLOCKING_OPEN (device_property_nonblocking_open.ID) +#define PROPERTY_FINAL_FILEMARKS (device_property_final_filemarks.ID) + +static DevicePropertyBase device_property_broken_gmt_online; +static DevicePropertyBase device_property_fsf; +static DevicePropertyBase device_property_fsf_after_filemark; +static DevicePropertyBase device_property_bsf; +static DevicePropertyBase device_property_fsr; +static DevicePropertyBase device_property_bsr; +static DevicePropertyBase device_property_eom; +static DevicePropertyBase device_property_bsf_after_eom; +static DevicePropertyBase device_property_nonblocking_open; +static DevicePropertyBase device_property_final_filemarks; +static DevicePropertyBase device_property_read_buffer_size; /* old name for READ_BLOCK_SIZE */ /* here are local prototypes */ static void tape_device_init (TapeDevice * o); @@ -82,7 +184,9 @@ static gboolean tape_device_set_final_filemarks_fn(Device *p_self, DevicePropert GValue *val, PropertySurety surety, PropertySource source); static gboolean tape_device_set_compression_fn(Device *p_self, DevicePropertyBase *base, GValue *val, PropertySurety surety, PropertySource source); -static gboolean tape_device_set_read_buffer_size_fn(Device *p_self, DevicePropertyBase *base, +static gboolean tape_device_get_read_block_size_fn(Device *p_self, DevicePropertyBase *base, + GValue *val, PropertySurety *surety, PropertySource *source); +static gboolean tape_device_set_read_block_size_fn(Device *p_self, DevicePropertyBase *base, GValue *val, PropertySurety surety, PropertySource source); static void tape_device_open_device (Device * self, char * device_name, char * device_type, char * device_node); static Device * tape_device_factory (char * device_name, char * device_type, char * device_node); @@ -96,12 +200,12 @@ static gboolean tape_device_start_file (Device * self, dumpfile_t * ji); static gboolean tape_device_finish_file (Device * self); static dumpfile_t * tape_device_seek_file (Device * self, guint file); static gboolean tape_device_seek_block (Device * self, guint64 block); +static gboolean tape_device_eject (Device * self); static gboolean tape_device_finish (Device * self); static IoResult tape_device_robust_read (TapeDevice * self, void * buf, - int * count); -static IoResult tape_device_robust_write (TapeDevice * self, void * buf, int count); + int * count, char **errmsg); +static IoResult tape_device_robust_write (TapeDevice * self, void * buf, int count, char **errmsg); static gboolean tape_device_fsf (TapeDevice * self, guint count); -static gboolean tape_device_bsf (TapeDevice * self, guint count, guint file); static gboolean tape_device_fsr (TapeDevice * self, guint count); static gboolean tape_device_bsr (TapeDevice * self, guint count, guint file, guint block); static gboolean tape_device_eod (TapeDevice * self); @@ -112,7 +216,7 @@ static DeviceClass *parent_class = NULL; GType tape_device_get_type (void) { static GType type = 0; - + if G_UNLIKELY(type == 0) { static const GTypeInfo info = { sizeof (TapeDeviceClass), @@ -126,7 +230,7 @@ GType tape_device_get_type (void) (GInstanceInitFunc) tape_device_init, NULL }; - + type = g_type_register_static (TYPE_DEVICE, "TapeDevice", &info, (GTypeFlags)0); } @@ -134,7 +238,7 @@ GType tape_device_get_type (void) return type; } -static void +static void tape_device_init (TapeDevice * self) { Device * d_self; GValue response; @@ -159,6 +263,7 @@ tape_device_init (TapeDevice * self) { self->fsr = FALSE; self->bsr = FALSE; self->eom = FALSE; + self->leom = FALSE; self->bsf_after_eom = FALSE; g_value_init(&response, G_TYPE_BOOLEAN); @@ -177,6 +282,8 @@ tape_device_init (TapeDevice * self) { &response, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); device_set_simple_property(d_self, PROPERTY_EOM, &response, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); + device_set_simple_property(d_self, PROPERTY_LEOM, + &response, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); device_set_simple_property(d_self, PROPERTY_BSF_AFTER_EOM, &response, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); @@ -197,10 +304,10 @@ tape_device_init (TapeDevice * self) { &response, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); g_value_unset(&response); - self->private->read_buffer_size = 0; + self->private->read_block_size = 0; g_value_init(&response, G_TYPE_UINT); - g_value_set_uint(&response, self->private->read_buffer_size); - device_set_simple_property(d_self, PROPERTY_READ_BUFFER_SIZE, + g_value_set_uint(&response, self->private->read_block_size); + device_set_simple_property(d_self, PROPERTY_READ_BLOCK_SIZE, &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DEFAULT); g_value_unset(&response); @@ -221,7 +328,7 @@ tape_device_init (TapeDevice * self) { g_value_unset(&response); g_value_init(&response, G_TYPE_BOOLEAN); - g_value_set_boolean(&response, FALSE); + g_value_set_boolean(&response, TRUE); device_set_simple_property(d_self, PROPERTY_APPENDABLE, &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); g_value_unset(&response); @@ -232,6 +339,12 @@ tape_device_init (TapeDevice * self) { &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); g_value_unset(&response); + g_value_init(&response, G_TYPE_BOOLEAN); + g_value_set_boolean(&response, FALSE); + device_set_simple_property(d_self, PROPERTY_FULL_DELETION, + &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); + g_value_unset(&response); + g_value_init(&response, MEDIA_ACCESS_MODE_TYPE); g_value_set_enum(&response, MEDIA_ACCESS_MODE_READ_WRITE); device_set_simple_property(d_self, PROPERTY_MEDIUM_ACCESS_TYPE, @@ -251,14 +364,14 @@ static void tape_device_finalize(GObject * obj_self) { amfree(self->private); } -static void +static void tape_device_class_init (TapeDeviceClass * c) { DeviceClass *device_class = (DeviceClass *)c; GObjectClass *g_object_class = (GObjectClass *)c; parent_class = g_type_class_ref (TYPE_DEVICE); - + device_class->open_device = tape_device_open_device; device_class->read_label = tape_device_read_label; device_class->write_block = tape_device_write_block; @@ -268,8 +381,9 @@ tape_device_class_init (TapeDeviceClass * c) device_class->finish_file = tape_device_finish_file; device_class->seek_file = tape_device_seek_file; device_class->seek_block = tape_device_seek_block; + device_class->eject = tape_device_eject; device_class->finish = tape_device_finish; - + g_object_class->finalize = tape_device_finalize; } @@ -335,10 +449,15 @@ tape_device_base_init (TapeDeviceClass * c) NULL, tape_device_set_compression_fn); - device_class_register_property(device_class, PROPERTY_READ_BUFFER_SIZE, + device_class_register_property(device_class, PROPERTY_READ_BLOCK_SIZE, PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_BEFORE_START, - device_simple_property_get_fn, - tape_device_set_read_buffer_size_fn); + tape_device_get_read_block_size_fn, + tape_device_set_read_block_size_fn); + + device_class_register_property(device_class, device_property_read_buffer_size.ID, + PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_BEFORE_START, + tape_device_get_read_block_size_fn, + tape_device_set_read_block_size_fn); } static gboolean @@ -458,20 +577,31 @@ tape_device_set_compression_fn(Device *p_self, DevicePropertyBase *base, } static gboolean -tape_device_set_read_buffer_size_fn(Device *p_self, DevicePropertyBase *base, +tape_device_get_read_block_size_fn(Device *p_self, DevicePropertyBase *base G_GNUC_UNUSED, + GValue *val, PropertySurety *surety, PropertySource *source) +{ + /* use the READ_BLOCK_SIZE, even if we're invoked to get the old READ_BUFFER_SIZE */ + return device_simple_property_get_fn(p_self, &device_property_read_block_size, + val, surety, source); +} + +static gboolean +tape_device_set_read_block_size_fn(Device *p_self, DevicePropertyBase *base G_GNUC_UNUSED, GValue *val, PropertySurety surety, PropertySource source) { TapeDevice *self = TAPE_DEVICE(p_self); - guint buffer_size = g_value_get_uint(val); + guint read_block_size = g_value_get_uint(val); - if (buffer_size != 0 && - ((gsize)buffer_size < p_self->block_size || - (gsize)buffer_size > p_self->max_block_size)) + if (read_block_size != 0 && + ((gsize)read_block_size < p_self->block_size || + (gsize)read_block_size > p_self->max_block_size)) return FALSE; - self->private->read_buffer_size = buffer_size; + self->private->read_block_size = read_block_size; - return device_simple_property_set_fn(p_self, base, val, surety, source); + /* use the READ_BLOCK_SIZE, even if we're invoked to get the old READ_BUFFER_SIZE */ + return device_simple_property_set_fn(p_self, &device_property_read_block_size, + val, surety, source); } void tape_device_register(void) { @@ -502,7 +632,6 @@ void tape_device_register(void) { G_TYPE_BOOLEAN, "bsr", "Does this drive support the MTBSR command?"); - /* FIXME: Is this feature even useful? */ device_property_fill_and_register(&device_property_eom, G_TYPE_BOOLEAN, "eom", "Does this drive support the MTEOM command?"); @@ -521,6 +650,10 @@ void tape_device_register(void) { G_TYPE_UINT, "final_filemarks", "How many filemarks to write after the last tape file?" ); + device_property_fill_and_register(&device_property_read_buffer_size, + G_TYPE_UINT, "read_buffer_size", + "(deprecated name for READ_BLOCK_SIZE)"); + /* Then the device itself */ register_device(tape_device_factory, device_prefix_list); } @@ -638,84 +771,68 @@ static int try_open_tape_device(TapeDevice * self, char * device_filename) { } static void -tape_device_open_device (Device * d_self, char * device_name G_GNUC_UNUSED, - char * device_type G_GNUC_UNUSED, char * device_node) { +tape_device_open_device (Device * dself, char * device_name, + char * device_type, char * device_node) { TapeDevice * self; + GValue val; - self = TAPE_DEVICE(d_self); + self = TAPE_DEVICE(dself); self->fd = -1; self->private->device_filename = stralloc(device_node); - /* Get tape drive/OS info */ - tape_device_detect_capabilities(self); - - /* Chain up */ - if (parent_class->open_device) { - parent_class->open_device(d_self, device_node, device_type, device_node); - } -} - -void -tape_device_set_capabilities(TapeDevice *self, - gboolean fsf, PropertySurety fsf_surety, PropertySource fsf_source, - gboolean fsf_after_filemark, PropertySurety faf_surety, PropertySource faf_source, - gboolean bsf, PropertySurety bsf_surety, PropertySource bsf_source, - gboolean fsr, PropertySurety fsr_surety, PropertySource fsr_source, - gboolean bsr, PropertySurety bsr_surety, PropertySource bsr_source, - gboolean eom, PropertySurety eom_surety, PropertySource eom_source, - gboolean bsf_after_eom, PropertySurety bae_surety, PropertySource bae_source, - guint final_filemarks, PropertySurety ff_surety, PropertySource ff_source) -{ - Device *dself = DEVICE(self); - GValue val; - - /* this function is called by tape_device_detect_capabilities, and basically - * exists to take care of the GValue mechanics in one place */ - - g_assert(final_filemarks == 1 || final_filemarks == 2); - + /* Set tape drive/OS info */ bzero(&val, sizeof(val)); g_value_init(&val, G_TYPE_BOOLEAN); - self->fsf = fsf; - g_value_set_boolean(&val, fsf); - device_set_simple_property(dself, PROPERTY_FSF, &val, fsf_surety, fsf_source); + self->fsf = TRUE; + g_value_set_boolean(&val, self->fsf); + device_set_simple_property(dself, PROPERTY_FSF, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); - self->fsf_after_filemark = fsf_after_filemark; - g_value_set_boolean(&val, fsf_after_filemark); - device_set_simple_property(dself, PROPERTY_FSF_AFTER_FILEMARK, &val, faf_surety, faf_source); + self->fsf_after_filemark = DEFAULT_FSF_AFTER_FILEMARK; + g_value_set_boolean(&val, self->fsf_after_filemark); + device_set_simple_property(dself, PROPERTY_FSF_AFTER_FILEMARK, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); - self->bsf = bsf; - g_value_set_boolean(&val, bsf); - device_set_simple_property(dself, PROPERTY_BSF, &val, bsf_surety, bsf_source); + self->bsf = TRUE; + g_value_set_boolean(&val, self->bsf); + device_set_simple_property(dself, PROPERTY_BSF, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); - self->fsr = fsr; - g_value_set_boolean(&val, fsr); - device_set_simple_property(dself, PROPERTY_FSR, &val, fsr_surety, fsr_source); + self->fsr = TRUE; + g_value_set_boolean(&val, self->fsr); + device_set_simple_property(dself, PROPERTY_FSR, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); - self->bsr = bsr; - g_value_set_boolean(&val, bsr); - device_set_simple_property(dself, PROPERTY_BSR, &val, bsr_surety, bsr_source); + self->bsr = TRUE; + g_value_set_boolean(&val, self->bsr); + device_set_simple_property(dself, PROPERTY_BSR, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); - self->eom = eom; - g_value_set_boolean(&val, eom); - device_set_simple_property(dself, PROPERTY_EOM, &val, eom_surety, eom_source); + self->eom = TRUE; + g_value_set_boolean(&val, self->eom); + device_set_simple_property(dself, PROPERTY_EOM, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); - self->bsf_after_eom = bsf_after_eom; - g_value_set_boolean(&val, bsf_after_eom); - device_set_simple_property(dself, PROPERTY_BSF_AFTER_EOM, &val, bae_surety, bae_source); + self->leom = FALSE; + g_value_set_boolean(&val, self->leom); + device_set_simple_property(dself, PROPERTY_LEOM, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); + + self->bsf_after_eom = FALSE; + g_value_set_boolean(&val, self->bsf_after_eom); + device_set_simple_property(dself, PROPERTY_BSF_AFTER_EOM, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); g_value_unset(&val); g_value_init(&val, G_TYPE_UINT); - self->final_filemarks = final_filemarks; - g_value_set_uint(&val, final_filemarks); - device_set_simple_property(dself, PROPERTY_FINAL_FILEMARKS, &val, ff_surety, ff_source); + self->final_filemarks = 2; + g_value_set_uint(&val, self->final_filemarks); + device_set_simple_property(dself, PROPERTY_FINAL_FILEMARKS, &val, PROPERTY_SURETY_BAD, PROPERTY_SOURCE_DEFAULT); g_value_unset(&val); + + /* Chain up */ + if (parent_class->open_device) { + parent_class->open_device(dself, device_name, device_type, device_node); + } } + static DeviceStatusFlags tape_device_read_label(Device * dself) { TapeDevice * self; char * header_buffer; @@ -723,18 +840,17 @@ static DeviceStatusFlags tape_device_read_label(Device * dself) { IoResult result; dumpfile_t *header; DeviceStatusFlags new_status; + char *msg = NULL; self = TAPE_DEVICE(dself); amfree(dself->volume_label); amfree(dself->volume_time); - amfree(dself->volume_header); + dumpfile_free(dself->volume_header); + dself->volume_header = NULL; if (device_in_error(self)) return dself->status; - header = dself->volume_header = g_new(dumpfile_t, 1); - fh_init(header); - if (self->fd == -1) { self->fd = try_open_tape_device(self, self->private->device_filename); /* if the open failed, then try_open_tape_device already set the @@ -746,33 +862,58 @@ static DeviceStatusFlags tape_device_read_label(Device * dself) { /* Rewind it. */ if (!tape_rewind(self->fd)) { device_set_error(dself, - vstrallocf(_("Error rewinding device %s"), self->private->device_filename), + vstrallocf(_("Error rewinding device %s to read label: %s"), + self->private->device_filename, strerror(errno)), DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); - robust_close(self->fd); return dself->status; } buffer_len = tape_device_read_size(self); header_buffer = malloc(buffer_len); - result = tape_device_robust_read(self, header_buffer, &buffer_len); + result = tape_device_robust_read(self, header_buffer, &buffer_len, &msg); if (result != RESULT_SUCCESS) { free(header_buffer); tape_rewind(self->fd); /* I/O error. */ - if (result == RESULT_NO_DATA) { + switch (result) { + case RESULT_NO_DATA: + msg = stralloc(_("no data")); new_status = (DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_VOLUME_UNLABELED); - } else { + header = dself->volume_header = g_new(dumpfile_t, 1); + fh_init(header); + break; + + case RESULT_SMALL_BUFFER: + msg = stralloc(_("block size too small")); + new_status = (DEVICE_STATUS_DEVICE_ERROR | + DEVICE_STATUS_VOLUME_ERROR); + header = dself->volume_header = g_new(dumpfile_t, 1); + fh_init(header); + header->type = F_WEIRD; + break; + + default: + msg = stralloc(_("unknown error")); + case RESULT_ERROR: new_status = (DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_VOLUME_UNLABELED); + break; } - device_set_error(dself, stralloc(_("Error reading Amanda header")), new_status); + device_set_error(dself, + g_strdup_printf(_("Error reading Amanda header: %s"), + msg? msg : _("unknown error")), + new_status); + amfree(msg); return dself->status; } + header = dself->volume_header = g_new(dumpfile_t, 1); + fh_init(header); + parse_file_header(header_buffer, header, buffer_len); amfree(header_buffer); if (header->type != F_TAPESTART) { @@ -796,6 +937,7 @@ tape_device_write_block(Device * pself, guint size, gpointer data) { TapeDevice * self; char *replacement_buffer = NULL; IoResult result; + char *msg = NULL; self = TAPE_DEVICE(pself); @@ -813,7 +955,7 @@ tape_device_write_block(Device * pself, guint size, gpointer data) { size = pself->block_size; } - result = tape_device_robust_write(self, data, size); + result = tape_device_robust_write(self, data, size, &msg); amfree(replacement_buffer); switch (result) { @@ -824,14 +966,16 @@ tape_device_write_block(Device * pself, guint size, gpointer data) { device_set_error(pself, stralloc(_("No space left on device")), DEVICE_STATUS_VOLUME_ERROR); - pself->is_eof = TRUE; + pself->is_eom = TRUE; return FALSE; default: + msg = stralloc(_("unknown error")); case RESULT_ERROR: device_set_error(pself, - vstrallocf(_("Error writing block: %s"), strerror(errno)), + g_strdup_printf(_("Error writing block: %s"), msg), DEVICE_STATUS_DEVICE_ERROR); + amfree(msg); return FALSE; } @@ -846,7 +990,8 @@ static int tape_device_read_block (Device * pself, gpointer buf, int size; IoResult result; gssize read_block_size = tape_device_read_size(pself); - + char *msg = NULL; + self = TAPE_DEVICE(pself); g_assert(self->fd >= 0); @@ -860,7 +1005,7 @@ static int tape_device_read_block (Device * pself, gpointer buf, } size = *size_req; - result = tape_device_robust_read(self, buf, &size); + result = tape_device_robust_read(self, buf, &size, &msg); switch (result) { case RESULT_SUCCESS: *size_req = size; @@ -883,15 +1028,15 @@ static int tape_device_read_block (Device * pself, gpointer buf, } g_assert (new_size > (gsize)*size_req); - g_warning("Device %s indicated blocksize %zd was too small; using %zd.", + g_info("Device %s indicated blocksize %zd was too small; using %zd.", pself->device_name, (gsize)*size_req, new_size); *size_req = (int)new_size; - self->private->read_buffer_size = new_size; + self->private->read_block_size = new_size; bzero(&newval, sizeof(newval)); g_value_init(&newval, G_TYPE_UINT); - g_value_set_uint(&newval, self->private->read_buffer_size); - device_set_simple_property(pself, PROPERTY_READ_BUFFER_SIZE, + g_value_set_uint(&newval, self->private->read_block_size); + device_set_simple_property(pself, PROPERTY_READ_BLOCK_SIZE, &newval, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); g_value_unset(&newval); @@ -906,9 +1051,12 @@ static int tape_device_read_block (Device * pself, gpointer buf, return -1; default: + msg = stralloc(_("unknown error")); + case RESULT_ERROR: device_set_error(pself, - vstrallocf(_("Error reading from tape device: %s"), strerror(errno)), + vstrallocf(_("Error reading from tape device: %s"), msg), DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); + amfree(msg); return -1; } @@ -921,32 +1069,36 @@ static gboolean write_tapestart_header(TapeDevice * self, char * label, IoResult result; dumpfile_t * header; char * header_buf; - int header_size; - gboolean header_fits; Device * d_self = (Device*)self; + char *msg = NULL; + tape_rewind(self->fd); - + header = make_tapestart_header(d_self, label, timestamp); g_assert(header != NULL); - header_buf = device_build_amanda_header(d_self, header, &header_size, - &header_fits); - amfree(header); - g_assert(header_buf != NULL); - - if (!header_fits) { - amfree(header_buf); + header_buf = device_build_amanda_header(d_self, header, NULL); + if (header_buf == NULL) { device_set_error(d_self, stralloc(_("Tapestart header won't fit in a single block!")), DEVICE_STATUS_DEVICE_ERROR); + dumpfile_free(header); return FALSE; } + dumpfile_free(d_self->volume_header); + d_self->volume_header = NULL; - g_assert(header_size >= (int)d_self->min_block_size); - result = tape_device_robust_write(self, header_buf, header_size); + result = tape_device_robust_write(self, header_buf, d_self->block_size, &msg); if (result != RESULT_SUCCESS) { - device_set_error(d_self, - vstrallocf(_("Error writing tapestart header: %s"), strerror(errno)), + device_set_error(d_self, + g_strdup_printf(_("Error writing tapestart header: %s"), + (result == RESULT_ERROR)? msg : _("out of space")), DEVICE_STATUS_DEVICE_ERROR); + + if (result == RESULT_NO_SPACE) + d_self->is_eom = TRUE; + + amfree(msg); + dumpfile_free(header); amfree(header_buf); return FALSE; } @@ -958,14 +1110,17 @@ static gboolean write_tapestart_header(TapeDevice * self, char * label, vstrallocf(_("Error writing filemark: %s"), strerror(errno)), DEVICE_STATUS_DEVICE_ERROR|DEVICE_STATUS_VOLUME_ERROR); + /* can't tell if this was EOM or not, so assume it is */ + d_self->is_eom = TRUE; + dumpfile_free(header); return FALSE; } + d_self->volume_header = header; return TRUE; - } -static gboolean +static gboolean tape_device_start (Device * d_self, DeviceAccessMode mode, char * label, char * timestamp) { TapeDevice * self; @@ -1001,7 +1156,7 @@ tape_device_start (Device * d_self, DeviceAccessMode mode, char * label, return FALSE; } else if (!tape_rewind(self->fd)) { device_set_error(d_self, - vstrallocf(_("Couldn't rewind device: %s"), strerror(errno)), + vstrallocf(_("Error rewinding device to start: %s"), strerror(errno)), DEVICE_STATUS_DEVICE_ERROR); return FALSE; } @@ -1022,7 +1177,7 @@ tape_device_start (Device * d_self, DeviceAccessMode mode, char * label, return FALSE; } break; - + case ACCESS_READ: if (d_self->volume_label == NULL && device_read_label(d_self) != DEVICE_STATUS_SUCCESS) { /* device_read_label already set our error message */ @@ -1031,7 +1186,7 @@ tape_device_start (Device * d_self, DeviceAccessMode mode, char * label, if (!tape_rewind(self->fd)) { device_set_error(d_self, - vstrallocf(_("Couldn't rewind device: %s"), strerror(errno)), + vstrallocf(_("Error rewinding device after reading label: %s"), strerror(errno)), DEVICE_STATUS_DEVICE_ERROR); return FALSE; } @@ -1064,8 +1219,7 @@ static gboolean tape_device_start_file(Device * d_self, TapeDevice * self; IoResult result; char * amanda_header; - int header_size; - gboolean header_fits; + char *msg = NULL; self = TAPE_DEVICE(d_self); @@ -1077,20 +1231,26 @@ static gboolean tape_device_start_file(Device * d_self, /* Make the Amanda header suitable for writing to the device. */ /* Then write the damn thing. */ - amanda_header = device_build_amanda_header(d_self, info, - &header_size, &header_fits); - if (!header_fits) { + amanda_header = device_build_amanda_header(d_self, info, NULL); + if (amanda_header == NULL) { device_set_error(d_self, stralloc(_("Amanda file header won't fit in a single block!")), DEVICE_STATUS_DEVICE_ERROR); return FALSE; } - result = tape_device_robust_write(self, amanda_header, header_size); + + result = tape_device_robust_write(self, amanda_header, d_self->block_size, &msg); if (result != RESULT_SUCCESS) { device_set_error(d_self, - vstrallocf(_("Error writing file header: %s"), strerror(errno)), + vstrallocf(_("Error writing file header: %s"), + (result == RESULT_ERROR)? msg : _("out of space")), DEVICE_STATUS_DEVICE_ERROR); + + if (result == RESULT_NO_SPACE) + d_self->is_eom = TRUE; + amfree(amanda_header); + amfree(msg); return FALSE; } amfree(amanda_header); @@ -1114,6 +1274,8 @@ tape_device_finish_file (Device * d_self) { device_set_error(d_self, vstrallocf(_("Error writing filemark: %s"), strerror(errno)), DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); + /* can't tell if this was EOM or not, so assume it is */ + d_self->is_eom = TRUE; return FALSE; } @@ -1121,14 +1283,16 @@ tape_device_finish_file (Device * d_self) { return TRUE; } -static dumpfile_t * +static dumpfile_t * tape_device_seek_file (Device * d_self, guint file) { TapeDevice * self; + gint got_file; int difference; char * header_buffer; dumpfile_t * rval; int buffer_len; IoResult result; + char *msg; self = TAPE_DEVICE(d_self); @@ -1147,6 +1311,7 @@ tape_device_seek_file (Device * d_self, guint file) { d_self->is_eof = FALSE; d_self->block = 0; +reseek: if (difference > 0) { /* Seeking forwards */ if (!tape_device_fsf(self, difference)) { @@ -1156,40 +1321,94 @@ tape_device_seek_file (Device * d_self, guint file) { DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); return NULL; } - } else if (difference < 0) { - /* Seeking backwards */ - if (!tape_device_bsf(self, -difference, d_self->file)) { - tape_rewind(self->fd); - device_set_error(d_self, - vstrallocf(_("Could not seek backward to file %d"), file), - DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); - return NULL; - } + } else { /* (difference <= 0) */ + /* Seeking backwards, or to this file itself */ + + /* if the drive supports bsf, we can do this the fancy way */ + if (self->bsf) { + /* bsf one more than the difference */ + if (!tape_bsf(self->fd, -difference + 1)) { + tape_rewind(self->fd); + device_set_error(d_self, + vstrallocf(_("Could not seek backward to file %d"), file), + DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); + return NULL; + } + + /* now we are on the BOT side of the desired filemark, so FSF to get to the + * EOT side of it */ + if (!tape_device_fsf(self, 1)) { + tape_rewind(self->fd); + device_set_error(d_self, + vstrallocf(_("Could not seek forward to file %d"), file), + DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); + return NULL; + } + } else { + /* no BSF, so just rewind and seek forward */ + if (!tape_rewind(self->fd)) { + device_set_error(d_self, + vstrallocf(_("Could not rewind device while emulating BSF")), + DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); + return FALSE; + } + + if (!tape_device_fsf(self, file)) { + tape_rewind(self->fd); + device_set_error(d_self, + vstrallocf(_("Could not seek forward to file %d"), file), + DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); + return NULL; + } + } + } + + /* double-check that we're on the right fileno, if possible. This is most + * likely a programming error if it occurs, but could also be due to a weird + * tape drive or driver (and that would *never* happen, right?) */ + got_file = tape_fileno(self->fd); + if (got_file >= 0 && (guint)got_file != file) { + device_set_error(d_self, + vstrallocf(_("Could not seek to file %d correctly; got %d"), + file, got_file), + DEVICE_STATUS_DEVICE_ERROR); + d_self->file = (guint)got_file; + return NULL; } buffer_len = tape_device_read_size(d_self); header_buffer = malloc(buffer_len); d_self->is_eof = FALSE; - result = tape_device_robust_read(self, header_buffer, &buffer_len); + result = tape_device_robust_read(self, header_buffer, &buffer_len, &msg); if (result != RESULT_SUCCESS) { free(header_buffer); tape_rewind(self->fd); - if (result == RESULT_NO_DATA) { + switch (result) { + case RESULT_NO_DATA: /* If we read 0 bytes, that means we encountered a double * filemark, which indicates end of tape. This should * work even with QIC tapes on operating systems with * proper support. */ d_self->file = file; /* other attributes are already correct */ return make_tapeend_header(); + + case RESULT_SMALL_BUFFER: + msg = stralloc(_("block size too small")); + break; + + default: + msg = stralloc(_("unknown error")); + case RESULT_ERROR: + break; } - /* I/O error. */ device_set_error(d_self, - stralloc(_("Error reading Amanda header")), + g_strdup_printf(_("Error reading Amanda header: %s"), msg), DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); + amfree(msg); return NULL; } - + rval = g_new(dumpfile_t, 1); parse_file_header(header_buffer, rval, buffer_len); amfree(header_buffer); @@ -1199,6 +1418,15 @@ tape_device_seek_file (Device * d_self, guint file) { case F_SPLIT_DUMPFILE: break; + case F_NOOP: + /* a NOOP is written on QIC tapes to avoid writing two sequential + * filemarks when closing a device in WRITE or APPEND mode. In this + * case, we just seek to the next file. */ + amfree(rval); + file++; + difference = 1; + goto reseek; + default: tape_rewind(self->fd); device_set_error(d_self, @@ -1214,7 +1442,7 @@ tape_device_seek_file (Device * d_self, guint file) { return rval; } -static gboolean +static gboolean tape_device_seek_block (Device * d_self, guint64 block) { TapeDevice * self; int difference; @@ -1224,18 +1452,18 @@ tape_device_seek_block (Device * d_self, guint64 block) { if (device_in_error(self)) return FALSE; difference = block - d_self->block; - + if (difference > 0) { if (!tape_device_fsr(self, difference)) { device_set_error(d_self, - vstrallocf(_("Could not seek forward to block %ju"), (uintmax_t)block), + vstrallocf(_("Could not seek forward to block %ju: %s"), (uintmax_t)block, strerror(errno)), DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); return FALSE; } } else if (difference < 0) { if (!tape_device_bsr(self, difference, d_self->file, d_self->block)) { device_set_error(d_self, - vstrallocf(_("Could not seek backward to block %ju"), (uintmax_t)block), + vstrallocf(_("Could not seek backward to block %ju: %s"), (uintmax_t)block, strerror(errno)), DEVICE_STATUS_VOLUME_ERROR | DEVICE_STATUS_DEVICE_ERROR); return FALSE; } @@ -1245,58 +1473,138 @@ tape_device_seek_block (Device * d_self, guint64 block) { return TRUE; } -static gboolean -tape_device_finish (Device * d_self) { +static gboolean +tape_device_eject (Device * d_self) { TapeDevice * self; self = TAPE_DEVICE(d_self); if (device_in_error(self)) return FALSE; - if (d_self->access_mode == ACCESS_NULL) + /* Open the device if not already opened */ + if (self->fd == -1) { + self->fd = try_open_tape_device(self, self->private->device_filename); + /* if the open failed, then try_open_tape_device already set the + * approppriate error status */ + if (self->fd == -1) + return FALSE; + } + + /* Rewind it. */ + if (!tape_rewind(self->fd)) { + device_set_error(d_self, + vstrallocf(_("Error rewinding device %s before ejecting: %s"), + self->private->device_filename, strerror(errno)), + DEVICE_STATUS_DEVICE_ERROR + | DEVICE_STATUS_VOLUME_ERROR); + return FALSE; + } + + if (tape_offl(self->fd)) return TRUE; + device_set_error(d_self, + vstrallocf(_("Error ejecting device %s: %s\n"), + self->private->device_filename, strerror(errno)), + DEVICE_STATUS_DEVICE_ERROR); + + return FALSE; +} + +static gboolean +tape_device_finish (Device * d_self) { + TapeDevice * self; + char *msg = NULL; + + self = TAPE_DEVICE(d_self); + + if (device_in_error(self)) + goto finish_error; + + /* if we're already in ACCESS_NULL, then there are no filemarks or anything + * to worry about, but we need to release the kernel device */ + if (d_self->access_mode == ACCESS_NULL) { + robust_close(self->fd); + self->fd = -1; + return TRUE; + } + /* Polish off this file, if relevant. */ if (d_self->in_file && IS_WRITABLE_ACCESS_MODE(d_self->access_mode)) { if (!device_finish_file(d_self)) - return FALSE; + goto finish_error; } - /* Write an extra filemark, if needed. The OS will give us one for - sure. */ - /* device_finish_file already wrote one for us */ - /* - if (self->final_filemarks > 1 && + /* Straighten out the filemarks. We already wrote one in finish_file, and + * the device driver will write another filemark when we rewind. This means + * that, if we do nothing, we'll get two filemarks. If final_filemarks is + * 1, this would be wrong, so in this case we insert a F_NOOP header between + * the two filemarks. */ + if (self->final_filemarks == 1 && IS_WRITABLE_ACCESS_MODE(d_self->access_mode)) { - if (!tape_weof(self->fd, 1)) { - device_set_error(d_self, - vstrallocf(_("Error writing final filemark: %s"), strerror(errno)), - DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); - return FALSE; - } + dumpfile_t file; + char *header; + int result; + + /* write a F_NOOP header */ + fh_init(&file); + file.type = F_NOOP; + header = device_build_amanda_header(d_self, &file, NULL); + if (!header) { + device_set_error(d_self, + stralloc(_("Amanda file header won't fit in a single block!")), + DEVICE_STATUS_DEVICE_ERROR); + goto finish_error; + } + + result = tape_device_robust_write(self, header, d_self->block_size, &msg); + if (result != RESULT_SUCCESS) { + device_set_error(d_self, + vstrallocf(_("Error writing file header: %s"), + (result == RESULT_ERROR)? msg : _("out of space")), + DEVICE_STATUS_DEVICE_ERROR); + amfree(header); + amfree(msg); + goto finish_error; + } + amfree(header); } - */ - /* Rewind. */ + /* Rewind (the kernel will write a filemark first) */ if (!tape_rewind(self->fd)) { device_set_error(d_self, - vstrallocf(_("Couldn't rewind device: %s"), strerror(errno)), + vstrallocf(_("Couldn't rewind device to finish: %s"), strerror(errno)), DEVICE_STATUS_DEVICE_ERROR); - return FALSE; + goto finish_error; } d_self->is_eof = FALSE; d_self->access_mode = ACCESS_NULL; + /* release the kernel's device */ + robust_close(self->fd); + self->fd = -1; + return TRUE; + +finish_error: + d_self->access_mode = ACCESS_NULL; + + /* release the kernel's device */ + robust_close(self->fd); + self->fd = -1; + + return FALSE; } /* Works just like read(), except for the following: * 1) Retries on EINTR & friends. * 2) Stores count in parameter, not return value. - * 3) Provides explicit return result. */ + * 3) Provides explicit return result. + * *errmsg is only set on RESULT_ERROR. + */ static IoResult -tape_device_robust_read (TapeDevice * self, void * buf, int * count) { +tape_device_robust_read (TapeDevice * self, void * buf, int * count, char **errmsg) { Device * d_self; int result; @@ -1341,16 +1649,15 @@ tape_device_robust_read (TapeDevice * self, void * buf, int * count) { #endif )) { /* Buffer too small. */ + g_warning("Buffer is too small (%d bytes) from %s: %s", + *count, self->private->device_filename, strerror(errno)); return RESULT_SMALL_BUFFER; } else { - device_set_error(d_self, - vstrallocf(_("Error reading %d bytes from %s: %s"), - *count, self->private->device_filename, strerror(errno)), - DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); + *errmsg = g_strdup_printf(_("Error reading %d bytes from %s: %s"), + *count, self->private->device_filename, strerror(errno)); return RESULT_ERROR; } } - } g_assert_not_reached(); @@ -1380,31 +1687,65 @@ static void check_resetofs(TapeDevice * self G_GNUC_UNUSED, #endif } -static IoResult -tape_device_robust_write (TapeDevice * self, void * buf, int count) { +/* *errmsg is only set on RESULT_ERROR */ +static IoResult +tape_device_robust_write (TapeDevice * self, void * buf, int count, char **errmsg) { Device * d_self; int result; + gboolean retry = FALSE; d_self = (Device*)self; - + check_resetofs(self, count); for (;;) { result = write(self->fd, buf, count); - if (result == count) { - /* Success. */ - - self->private->write_count ++; + /* Success. */ + if (result == count) return RESULT_SUCCESS; - } else if (result >= 0) { - /* write() returned a short count. This should not happen. */ - device_set_error(d_self, - vstrallocf(_("Mysterious short write on tape device: Tried %d, got %d"), - count, result), - DEVICE_STATUS_DEVICE_ERROR); + + if (result > 0) { + /* write() returned a short count. This should not happen if the block sizes + * are properly aligned. */ + *errmsg = g_strdup_printf("Short write on tape device: Tried %d, got %d. Is " + "the drive using a block size smaller than %d bytes?", + count, result, count); return RESULT_ERROR; - } else if (0 + } + + /* Detect LEOM (early warning) and handle properly + * + * FreeBSD: 0-length write; next write will succeed + * http://lists.freebsd.org/pipermail/freebsd-scsi/2010-June/004414.html + * + * Solaris: 0-length write; next write will succeed + * (from Matthew Jacob on FreeBSD thread) + * + * Linux: -1/ENOSPC; next write will succeed + * http://www.mjmwired.net/kernel/Documentation/scsi/st.txt + * + * HP/UX: -1/ENOSPC; next write will succeed + * http://www.adssasia.com/Manual/IBM%203581%20tape%20autoloader.pdf + */ + if (result == 0 +#ifdef ENOSPC + || (result < 0 && errno == ENOSPC) +#endif + ) { + /* if we've retried once already, then we're probably really out of space */ + if (retry) + return RESULT_NO_SPACE; + retry = TRUE; + d_self->is_eom = TRUE; + + g_debug("empty write to tape; treating as LEOM early warning and retrying"); + continue; + } + + /* at this point result < 0, so an error occurred - sort out what */ + + if (0 #ifdef EAGAIN || errno == EAGAIN #endif @@ -1435,10 +1776,8 @@ tape_device_robust_write (TapeDevice * self, void * buf, int count) { return RESULT_NO_SPACE; } else { /* WTF */ - device_set_error(d_self, - vstrallocf(_("Kernel gave unexpected write() result of \"%s\" on device %s"), - strerror(errno), self->private->device_filename), - DEVICE_STATUS_DEVICE_ERROR); + *errmsg = vstrallocf(_("Kernel gave unexpected write() result of \"%s\" on device %s"), + strerror(errno), self->private->device_filename); return RESULT_ERROR; } } @@ -1458,7 +1797,7 @@ static int drain_tape_blocks(TapeDevice * self, int count) { buffer_size = tape_device_read_size(self); - buffer = malloc(sizeof(buffer_size)); + buffer = malloc(buffer_size); for (i = 0; i < count || count < 0;) { int result; @@ -1511,15 +1850,12 @@ static int drain_tape_blocks(TapeDevice * self, int count) { } } } - + amfree(buffer); return count; } -/* FIXME: Make sure that there are no cycles in reimplementation - dependencies. */ - -static gboolean +static gboolean tape_device_fsf (TapeDevice * self, guint count) { if (self->fsf) { return tape_fsf(self->fd, count); @@ -1533,33 +1869,8 @@ tape_device_fsf (TapeDevice * self, guint count) { } } -/* Seek back over count + 1 filemarks to the start of the given file. */ -static gboolean -tape_device_bsf (TapeDevice * self, guint count, guint file) { - if (self->bsf) { - /* The BSF operation is not very smart; it includes the - filemark of the present file as part of the count, and seeks - to the wrong (BOT) side of the filemark. We compensate for - this by seeking one filemark too many, then FSFing back over - it. - - If this procedure fails for some reason, we can still try - the backup plan. */ - if (tape_bsf(self->fd, count + 1) && - tape_device_fsf(self, 1)) - return TRUE; - } /* Fall through to backup plan. */ - - /* We rewind the tape, then seek forward the given number of - files. */ - if (!tape_rewind(self->fd)) - return FALSE; - - return tape_device_fsf(self, file); -} - -static gboolean +static gboolean tape_device_fsr (TapeDevice * self, guint count) { if (self->fsr) { return tape_fsr(self->fd, count); @@ -1572,15 +1883,27 @@ tape_device_fsr (TapeDevice * self, guint count) { /* Seek back the given number of blocks to block number block within * the current file, numbered file. */ -static gboolean +static gboolean tape_device_bsr (TapeDevice * self, guint count, guint file, guint block) { if (self->bsr) { return tape_bsr(self->fd, count); - } else { - /* We BSF, then FSR. */ - if (!tape_device_bsf(self, 0, file)) + } else if (self->bsf && self->fsf) { + /* BSF, FSF to the right side of the filemark, and then FSR. */ + if (!tape_bsf(self->fd, 1)) return FALSE; - + + if (!tape_fsf(self->fd, 1)) + return FALSE; + + return tape_device_fsr(self, block); + } else { + /* rewind, FSF, and FSR */ + if (!tape_rewind(self->fd)) + return FALSE; + + if (!tape_device_fsf(self, file)) + return FALSE; + return tape_device_fsr(self, block); } g_assert_not_reached(); @@ -1588,47 +1911,57 @@ tape_device_bsr (TapeDevice * self, guint count, guint file, guint block) { /* Go to the right place to write more data, and update the file number if possible. */ -static gboolean +static gboolean tape_device_eod (TapeDevice * self) { Device * d_self; + int count; + d_self = (Device*)self; if (self->eom) { int result; - result = tape_eod(self->fd); + result = tape_eod(self->fd); if (result == TAPE_OP_ERROR) { return FALSE; - } else if (result == TAPE_POSITION_UNKNOWN) { - d_self->file = -1; + } else if (result != TAPE_POSITION_UNKNOWN) { + /* great - we just fast-forwarded to EOD, but don't know where we are, so + * now we have to rewind and drain all of that data. Warn the user so that + * we can skip the fast-forward-rewind stage on the next run */ + g_warning("Seek to end of tape does not give an accurate tape position; set " + "the EOM property to 0 to avoid useless tape movement."); + /* and set the property so that next time *this* object is opened for + * append, we skip this stage */ + self->eom = FALSE; + /* fall through to draining blocks, below */ } else { /* We drop by 1 because Device will increment the first time the user does start_file. */ d_self->file = result - 1; + return TRUE; } - return TRUE; - } else { - int count = 0; - if (!tape_rewind(self->fd)) - return FALSE; - - for (;;) { - /* We alternately read a block and FSF. If the read is - successful, then we are not there yet and should FSF - again. */ - int result; - result = drain_tape_blocks(self, 1); - if (result == 1) { - /* More data, FSF. */ - tape_device_fsf(self, 1); - count ++; - } else if (result == 0) { - /* Finished. */ - d_self->file = count; - return TRUE; - } else { - return FALSE; - } - } + } + + if (!tape_rewind(self->fd)) + return FALSE; + + count = 0; + for (;;) { + /* We alternately read a block and FSF. If the read is + successful, then we are not there yet and should FSF + again. */ + int result; + result = drain_tape_blocks(self, 1); + if (result == 1) { + /* More data, FSF. */ + tape_device_fsf(self, 1); + count ++; + } else if (result == 0) { + /* Finished. */ + d_self->file = count - 1; + return TRUE; + } else { + return FALSE; + } } } @@ -1640,3 +1973,184 @@ tape_device_factory (char * device_name, char * device_type, char * device_node) device_open_device(rval, device_name, device_type, device_node); return rval; } + +/* + * Tape Operations using the POSIX interface + */ + +/* Having one name for every operation would be too easy. */ +#if !defined(MTCOMPRESSION) && defined(MTCOMP) +# define MTCOMPRESSION MTCOMP +#endif + +#if !defined(MTSETBLK) && defined(MTSETBSIZ) +# define MTSETBLK MTSETBSIZ +#endif + +#if !defined(MTEOM) && defined(MTEOD) +# define MTEOM MTEOD +#endif + +gboolean tape_rewind(int fd) { + int count = 5; + time_t stop_time; + + /* We will retry this for up to 30 seconds or 5 retries, + whichever is less, because some hardware/software combinations + (notably EXB-8200 on FreeBSD) can fail to rewind. */ + stop_time = time(NULL) + 30; + + while (--count >= 0 && time(NULL) < stop_time) { + struct mtop mt; + mt.mt_op = MTREW; + mt.mt_count = 1; + + if (0 == ioctl(fd, MTIOCTOP, &mt)) + return TRUE; + + sleep(3); + } + + return FALSE; +} + +gboolean tape_fsf(int fd, guint count) { + struct mtop mt; + mt.mt_op = MTFSF; + mt.mt_count = count; + return 0 == ioctl(fd, MTIOCTOP, &mt); +} + +gboolean tape_bsf(int fd, guint count) { + struct mtop mt; + mt.mt_op = MTBSF; + mt.mt_count = count; + return 0 == ioctl(fd, MTIOCTOP, &mt); +} + +gboolean tape_fsr(int fd, guint count) { + struct mtop mt; + mt.mt_op = MTFSR; + mt.mt_count = count; + return 0 == ioctl(fd, MTIOCTOP, &mt); +} + +gboolean tape_bsr(int fd, guint count) { + struct mtop mt; + mt.mt_op = MTBSR; + mt.mt_count = count; + return 0 == ioctl(fd, MTIOCTOP, &mt); +} + +gint tape_fileno(int fd) { + struct mtget get; + + if (0 != ioctl(fd, MTIOCGET, &get)) + return TAPE_POSITION_UNKNOWN; + if (get.mt_fileno < 0) + return TAPE_POSITION_UNKNOWN; + else + return get.mt_fileno; +} + +gint tape_eod(int fd) { + struct mtop mt; + struct mtget get; + mt.mt_op = MTEOM; + mt.mt_count = 1; + if (0 != ioctl(fd, MTIOCTOP, &mt)) + return TAPE_OP_ERROR; + + /* Ignored result. This is just to flush buffers. */ + mt.mt_op = MTNOP; + ioctl(fd, MTIOCTOP, &mt); + + if (0 != ioctl(fd, MTIOCGET, &get)) + return TAPE_POSITION_UNKNOWN; + if (get.mt_fileno < 0) + return TAPE_POSITION_UNKNOWN; + else + return get.mt_fileno; +} + +gboolean tape_weof(int fd, guint8 count) { + struct mtop mt; + mt.mt_op = MTWEOF; + mt.mt_count = count; + return 0 == ioctl(fd, MTIOCTOP, &mt); +} + +gboolean tape_setcompression(int fd G_GNUC_UNUSED, + gboolean on G_GNUC_UNUSED) { +#ifdef MTCOMPRESSION + struct mtop mt; + mt.mt_op = MTCOMPRESSION; + mt.mt_count = on; + return 0 == ioctl(fd, MTIOCTOP, &mt); +#else + return 0; +#endif +} + +gboolean tape_offl(int fd) { + struct mtop mt; + int safe_errno; + + mt.mt_op = MTOFFL; + mt.mt_count = 1; + if (0 == ioctl(fd, MTIOCTOP, &mt)) + return TRUE; + + safe_errno = errno; + g_debug("tape_off: ioctl(MTIOCTOP/MTOFFL) failed: %s", strerror(errno)); + errno = safe_errno; + + return FALSE; +} + +DeviceStatusFlags tape_is_tape_device(int fd) { + struct mtop mt; + mt.mt_op = MTNOP; + mt.mt_count = 1; + if (0 == ioctl(fd, MTIOCTOP, &mt)) { + return DEVICE_STATUS_SUCCESS; +#ifdef ENOMEDIUM + } else if (errno == ENOMEDIUM) { + return DEVICE_STATUS_VOLUME_MISSING; +#endif + } else { + g_debug("tape_is_tape_device: ioctl(MTIOCTOP/MTNOP) failed: %s", + strerror(errno)); + if (errno == EIO) { + /* some devices return EIO while the drive is busy loading */ + return DEVICE_STATUS_DEVICE_ERROR|DEVICE_STATUS_DEVICE_BUSY; + } else { + return DEVICE_STATUS_DEVICE_ERROR; + } + } +} + +DeviceStatusFlags tape_is_ready(int fd, TapeDevice *t_self G_GNUC_UNUSED) { + struct mtget get; + if (0 == ioctl(fd, MTIOCGET, &get)) { +#if defined(GMT_ONLINE) || defined(GMT_DR_OPEN) + if (1 +#ifdef GMT_ONLINE + && (t_self->broken_gmt_online || GMT_ONLINE(get.mt_gstat)) +#endif +#ifdef GMT_DR_OPEN + && !GMT_DR_OPEN(get.mt_gstat) +#endif + ) { + return DEVICE_STATUS_SUCCESS; + } else { + return DEVICE_STATUS_VOLUME_MISSING; + } +#else /* Neither macro is defined. */ + return DEVICE_STATUS_SUCCESS; +#endif + } else { + return DEVICE_STATUS_VOLUME_ERROR; + } +} +