Imported Upstream version 3.2.0
[debian/amanda] / device-src / tape-device.c
index ba8f7689f27a91d9a4684cb242c659a8de13279b..36498ddf8ce54dce6bc0da4942c371b7d205e7c4 100644 (file)
 #include "pipespawn.h"
 #include <string.h> /* memset() */
 #include "util.h"
-#include "tape-device.h"
-#include "tape-ops.h"
+#include "device.h"
+
+#ifdef HAVE_SYS_TAPE_H
+# include <sys/tape.h>
+#endif
+#ifdef HAVE_SYS_MTIO_H
+# include <sys/mtio.h>
+#endif
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
 
 /* This is equal to 2*1024*1024*1024 - 16*1024*1024 - 1, but written
    explicitly to avoid overflow issues. */
 /* 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. */
@@ -40,6 +84,41 @@ struct TapeDevicePrivate_s {
     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,
@@ -53,28 +132,48 @@ 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;
-DevicePropertyBase device_property_read_buffer_size; /* old name for READ_BLOCK_SIZE */
+/* 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_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);
 static void tape_device_class_init (TapeDeviceClass * c);
@@ -164,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);
@@ -182,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);
 
@@ -669,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,
+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_name, 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 = 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->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->bsf = TRUE;
+    g_value_set_boolean(&val, self->bsf);
+    device_set_simple_property(dself, PROPERTY_BSF, &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->fsr = TRUE;
+    g_value_set_boolean(&val, self->fsr);
+    device_set_simple_property(dself, PROPERTY_FSR, &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->bsr = TRUE;
+    g_value_set_boolean(&val, self->bsr);
+    device_set_simple_property(dself, PROPERTY_BSR, &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->eom = TRUE;
+    g_value_set_boolean(&val, self->eom);
+    device_set_simple_property(dself, PROPERTY_EOM, &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->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 = 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->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;
@@ -765,9 +851,6 @@ static DeviceStatusFlags tape_device_read_label(Device * dself) {
 
     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
@@ -799,12 +882,17 @@ static DeviceStatusFlags tape_device_read_label(Device * dself) {
            msg = stralloc(_("no data"));
             new_status = (DEVICE_STATUS_VOLUME_ERROR |
                          DEVICE_STATUS_VOLUME_UNLABELED);
+           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:
@@ -823,6 +911,9 @@ static DeviceStatusFlags tape_device_read_label(Device * dself) {
        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) {
@@ -1130,8 +1221,6 @@ static gboolean tape_device_start_file(Device * d_self,
     char * amanda_header;
     char *msg = NULL;
 
-    d_self->is_eom = FALSE;
-
     self = TAPE_DEVICE(d_self);
 
     g_assert(self->fd >= 0);
@@ -1429,7 +1518,8 @@ tape_device_finish (Device * d_self) {
 
     self = TAPE_DEVICE(d_self);
 
-    if (device_in_error(self)) return FALSE;
+    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 */
@@ -1442,7 +1532,7 @@ tape_device_finish (Device * d_self) {
     /* 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;
     }
 
     /* Straighten out the filemarks.  We already wrote one in finish_file, and
@@ -1464,7 +1554,7 @@ tape_device_finish (Device * d_self) {
            device_set_error(d_self,
                stralloc(_("Amanda file header won't fit in a single block!")),
                DEVICE_STATUS_DEVICE_ERROR);
-           return FALSE;
+           goto finish_error;
        }
 
        result = tape_device_robust_write(self, header, d_self->block_size, &msg);
@@ -1475,7 +1565,7 @@ tape_device_finish (Device * d_self) {
                DEVICE_STATUS_DEVICE_ERROR);
            amfree(header);
            amfree(msg);
-           return FALSE;
+           goto finish_error;
        }
        amfree(header);
     }
@@ -1485,7 +1575,7 @@ tape_device_finish (Device * d_self) {
        device_set_error(d_self,
            vstrallocf(_("Couldn't rewind device to finish: %s"), strerror(errno)),
            DEVICE_STATUS_DEVICE_ERROR);
-        return FALSE;
+       goto finish_error;
     }
 
     d_self->is_eof = FALSE;
@@ -1496,6 +1586,15 @@ tape_device_finish (Device * d_self) {
     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:
@@ -1593,6 +1692,7 @@ 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;
 
@@ -1601,17 +1701,51 @@ tape_device_robust_write (TapeDevice * self, void * buf, int count, char **errms
     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. */
-           *errmsg = g_strdup_printf("Mysterious short write on tape device: Tried %d, got %d",
-                               count, result);
+
+       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
@@ -1839,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;
+    }
+}
+