Imported Upstream version 3.3.1
[debian/amanda] / device-src / vfs-device.c
index a69b584f69943c85a7489f99caa5c3a42ad70e66..122a6efb8e479cdf21713b93c31a13af2d9e5d7c 100644 (file)
@@ -1,31 +1,31 @@
 /*
- * Copyright (c) 2005 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, 2011 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., 505 N Mathlida Ave, Suite 120
+ * 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 <string.h> /* memset() */
-
 #include "amanda.h"
-#include "vfs-device.h"
+#include <string.h> /* memset() */
 #include "fsusage.h"
 #include "util.h"
 #include <regex.h>
 
+#include "vfs-device.h"
+
 /* This regex will match all VfsDevice files in a directory. We use it
    for cleanup and verification. Note that this regex does NOT match
    the volume label. */
    generated by lockfile_name(0). */
 #define VOLUME_LOCKFILE_NAME "00000-lock"
 
+#define VFS_DEVICE_MIN_BLOCK_SIZE (1)
+#define VFS_DEVICE_MAX_BLOCK_SIZE (INT_MAX)
+#define VFS_DEVICE_DEFAULT_BLOCK_SIZE (DISK_BLOCK_BYTES)
+#define VFS_DEVICE_LABEL_SIZE (32768)
+
+/* Allow comfortable room for another block and a header before PEOM */
+#define EOM_EARLY_WARNING_ZONE_BLOCKS 4
+
+/* Constants for free-space monitoring */
+#define MONITOR_FREE_SPACE_EVERY_SECONDS 5
+#define MONITOR_FREE_SPACE_EVERY_KB 102400
+#define MONITOR_FREE_SPACE_CLOSELY_WITHIN_BLOCKS 128
+
+/* This looks dangerous, but is actually modified by the umask. */
+#define VFS_DEVICE_CREAT_MODE 0666
+
 /* Possible (abstracted) results from a system I/O operation. */
 typedef enum {
     RESULT_SUCCESS,
@@ -46,29 +62,28 @@ typedef enum {
     RESULT_MAX
 } IoResult;
 
+void vfs_device_register(void);
+
 /* here are local prototypes */
 static void vfs_device_init (VfsDevice * o);
 static void vfs_device_class_init (VfsDeviceClass * c);
+static void vfs_device_base_init (VfsDeviceClass * c);
 static void vfs_device_finalize (GObject * o);
 
 static gboolean vfs_device_start(Device * pself, DeviceAccessMode mode,
                                  char * label, char * timestamp);
-static gboolean vfs_device_open_device (Device * pself,
-                                        char * device_name);
-static gboolean vfs_device_start_file (Device * pself, const dumpfile_t * ji);
-static gboolean vfs_device_finish_file (Device * pself);
+static gboolean vfs_device_finish (Device * pself);
+static void vfs_device_open_device (Device * pself, char * device_name,
+                               char * device_type, char * device_node);
+static gboolean vfs_device_start_file (Device * pself, dumpfile_t * ji);
+static gboolean vfs_device_finish_file (Device * dself);
 static dumpfile_t * vfs_device_seek_file (Device * self, guint file);
 static gboolean vfs_device_seek_block (Device * self, guint64 block);
-static gboolean vfs_device_property_get (Device * pself, DevicePropertyId ID,
-                                         GValue * val);
-static gboolean vfs_device_property_set (Device * pself, DevicePropertyId ID,
-                                         GValue * val);
 static gboolean vfs_device_recycle_file (Device * pself, guint filenum);
-static Device * vfs_device_factory(char * device_type,
-                                   char * device_name);
-static ReadLabelStatusFlags vfs_device_read_label(Device * dself);
-static gboolean vfs_device_write_block(Device * self, guint size,
-                                       gpointer data, gboolean last_block);
+static gboolean vfs_device_erase (Device * pself);
+static Device * vfs_device_factory(char * device_name, char * device_type, char * device_node);
+static DeviceStatusFlags vfs_device_read_label(Device * dself);
+static gboolean vfs_device_write_block(Device * self, guint size, gpointer data);
 static int vfs_device_read_block(Device * self, gpointer data, int * size_req);
 static IoResult vfs_device_robust_write(VfsDevice * self,  char *buf,
                                               int count);
@@ -77,32 +92,65 @@ static IoResult vfs_device_robust_read(VfsDevice * self, char *buf,
 
 /* Various helper functions. */
 static void release_file(VfsDevice * self);
-static gboolean check_is_dir(const char * name, gboolean printmsg);
-static char* file_number_to_file_name(VfsDevice * self, guint file);
+static gboolean check_is_dir(VfsDevice * self, const char * name);
+static char * file_number_to_file_name(VfsDevice * self, guint file);
 static gboolean file_number_to_file_name_functor(const char * filename,
                                                  gpointer datap);
+static gboolean vfs_device_set_max_volume_usage_fn(Device *p_self,
+                           DevicePropertyBase *base, GValue *val,
+                           PropertySurety surety, PropertySource source);
+static gboolean vfs_device_set_enforce_max_volume_usage_fn(Device *p_self,
+                           DevicePropertyBase *base, GValue *val,
+                           PropertySurety surety, PropertySource source);
+static gboolean property_get_monitor_free_space_fn(Device *p_self,
+                           DevicePropertyBase *base, GValue *val,
+                           PropertySurety *surety, PropertySource *source);
+static gboolean property_set_monitor_free_space_fn(Device *p_self,
+                           DevicePropertyBase *base, GValue *val,
+                           PropertySurety surety, PropertySource source);
+static gboolean property_set_leom_fn(Device *p_self,
+                           DevicePropertyBase *base, GValue *val,
+                           PropertySurety surety, PropertySource source);
 //static char* lockfile_name(VfsDevice * self, guint file);
 static gboolean open_lock(VfsDevice * self, int file, gboolean exclusive);
 static void promote_volume_lock(VfsDevice * self);
 static void demote_volume_lock(VfsDevice * self);
-static gboolean delete_vfs_files(VfsDevice * self);
 static gboolean delete_vfs_files_functor(const char * filename,
                                          gpointer self);
 static gboolean check_dir_empty_functor(const char * filename,
                                         gpointer self);
 static gboolean clear_and_prepare_label(VfsDevice * self, char * label,
                                         char * timestamp);
+static int search_vfs_directory(VfsDevice *self, const char * regex,
+                       SearchDirectoryFunctor functor, gpointer user_data);
 static gint get_last_file_number(VfsDevice * self);
 static gboolean get_last_file_number_functor(const char * filename,
                                              gpointer datap);
 static char * make_new_file_name(VfsDevice * self, const dumpfile_t * ji);
 static gboolean try_unlink(const char * file);
 
+/* return TRUE if the device is going to hit ENOSPC "soon" - this is used to
+ * detect LEOM as represented by actually running out of space on the
+ * underlying filesystem.  Size is the size of the buffer that is about to
+ * be written. */
+static gboolean check_at_leom(VfsDevice *self, guint64 size);
+/* Similar, but for PEOM */
+static gboolean check_at_peom(VfsDevice *self, guint64 size);
+
 /* pointer to the classes of our parents */
 static DeviceClass *parent_class = NULL;
 
+/* device-specific properties */
+DevicePropertyBase device_property_monitor_free_space;
+#define PROPERTY_MONITOR_FREE_SPACE (device_property_monitor_free_space.ID)
+
 void vfs_device_register(void) {
     static const char * device_prefix_list[] = { "file", NULL };
+
+    device_property_fill_and_register(&device_property_monitor_free_space,
+                                      G_TYPE_BOOLEAN, "monitor_free_space",
+      "Should VFS device monitor the filesystem's available free space?");
+
     register_device(vfs_device_factory, device_prefix_list);
 }
 
@@ -114,7 +162,7 @@ vfs_device_get_type (void)
     if G_UNLIKELY(type == 0) {
         static const GTypeInfo info = {
             sizeof (VfsDeviceClass),
-            (GBaseInitFunc) NULL,
+            (GBaseInitFunc) vfs_device_base_init,
             (GBaseFinalizeFunc) NULL,
             (GClassInitFunc) vfs_device_class_init,
             (GClassFinalizeFunc) NULL,
@@ -128,86 +176,90 @@ vfs_device_get_type (void)
         type = g_type_register_static (TYPE_DEVICE, "VfsDevice",
                                        &info, (GTypeFlags)0);
     }
-    
+
     return type;
 }
 
-static void 
+static void
 vfs_device_init (VfsDevice * self) {
-    Device * o;
-    DeviceProperty prop;
+    Device * dself = DEVICE(self);
     GValue response;
 
-    self->dir_handle = NULL;
     self->dir_name = self->file_name = NULL;
-    self->file_lock_name = self->volume_lock_name = NULL;
-    self->file_lock_fd = self->volume_lock_fd = self->open_file_fd = -1;
-    self->block_size = VFS_DEVICE_DEFAULT_BLOCK_SIZE;
-    self->volume_bytes = 0; 
+    self->open_file_fd = -1;
+    self->volume_bytes = 0;
     self->volume_limit = 0;
+    self->leom = TRUE;
+    self->enforce_volume_limit = TRUE;
+
+    self->monitor_free_space = TRUE;
+    self->checked_fs_free_bytes = G_MAXUINT64;
+    self->checked_fs_free_time = 0;
+    self->checked_fs_free_bytes = G_MAXUINT64;
 
     /* Register Properties */
-    o = DEVICE(self);
     bzero(&response, sizeof(response));
-    prop.base = &device_property_concurrency;
-    prop.access = PROPERTY_ACCESS_GET_MASK;
+
     g_value_init(&response, CONCURRENCY_PARADIGM_TYPE);
     g_value_set_enum(&response, CONCURRENCY_PARADIGM_RANDOM_ACCESS);
-    device_add_property(o, &prop, &response);
+    device_set_simple_property(dself, PROPERTY_CONCURRENCY,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
     g_value_unset(&response);
 
-    prop.base = &device_property_streaming;
     g_value_init(&response, STREAMING_REQUIREMENT_TYPE);
     g_value_set_enum(&response, STREAMING_REQUIREMENT_NONE);
-    device_add_property(o, &prop, &response);
+    device_set_simple_property(dself, PROPERTY_STREAMING,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
     g_value_unset(&response);
 
-    prop.base = &device_property_min_block_size;
-    g_value_init(&response, G_TYPE_UINT);
-    g_value_set_uint(&response, VFS_DEVICE_MIN_BLOCK_SIZE);
-    device_add_property(o, &prop, &response);
+    g_value_init(&response, G_TYPE_BOOLEAN);
+    g_value_set_boolean(&response, TRUE);
+    device_set_simple_property(dself, PROPERTY_APPENDABLE,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
+    g_value_unset(&response);
+
+    g_value_init(&response, G_TYPE_BOOLEAN);
+    g_value_set_boolean(&response, TRUE);
+    device_set_simple_property(dself, PROPERTY_PARTIAL_DELETION,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
+    g_value_unset(&response);
 
-    prop.base = &device_property_max_block_size;
-    g_value_set_uint(&response, VFS_DEVICE_MAX_BLOCK_SIZE);
-    device_add_property(o, &prop, &response);
+    g_value_init(&response, G_TYPE_BOOLEAN);
+    g_value_set_boolean(&response, TRUE);
+    device_set_simple_property(dself, PROPERTY_FULL_DELETION,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
     g_value_unset(&response);
 
-    prop.base = &device_property_appendable;
     g_value_init(&response, G_TYPE_BOOLEAN);
     g_value_set_boolean(&response, TRUE);
-    device_add_property(o, &prop, &response);
+    device_set_simple_property(dself, PROPERTY_LEOM,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
+    g_value_unset(&response);
 
-    prop.base = &device_property_partial_deletion;
-    device_add_property(o, &prop, &response);
+    g_value_init(&response, G_TYPE_BOOLEAN);
+    g_value_set_boolean(&response, TRUE);
+    device_set_simple_property(dself, PROPERTY_ENFORCE_MAX_VOLUME_USAGE,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
     g_value_unset(&response);
 
-    /* This one is handled by Device's get_property handler. */
-    prop.base = &device_property_canonical_name;
-    device_add_property(o, &prop, NULL);
+    g_value_init(&response, G_TYPE_BOOLEAN);
+    g_value_set_boolean(&response, FALSE);
+    device_set_simple_property(dself, PROPERTY_COMPRESSION,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
+    g_value_unset(&response);
 
-    prop.base = &device_property_medium_access_type;
     g_value_init(&response, MEDIA_ACCESS_MODE_TYPE);
     g_value_set_enum(&response, MEDIA_ACCESS_MODE_READ_WRITE);
-    device_add_property(o, &prop, &response);
+    device_set_simple_property(dself, PROPERTY_MEDIUM_ACCESS_TYPE,
+           &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
     g_value_unset(&response);
-
-    /* These are dynamic, handled in vfs_device_property_xxx */
-    prop.base = &device_property_block_size;
-    prop.access = PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_BEFORE_START;
-    device_add_property(o, &prop, NULL);
-
-    prop.base = &device_property_max_volume_usage;
-    prop.access =
-        (PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK) &
-        (~ PROPERTY_ACCESS_SET_INSIDE_FILE_WRITE);
-    device_add_property(o, &prop, NULL);
 }
 
-static void 
-vfs_device_class_init (VfsDeviceClass * c G_GNUC_UNUSED)
+static void
+vfs_device_class_init (VfsDeviceClass * c)
 {
     GObjectClass *g_object_class = (GObjectClass*) c;
-    DeviceClass *device_class = (DeviceClass *)c;
+    DeviceClass *device_class = DEVICE_CLASS(c);
 
     parent_class = g_type_class_ref(TYPE_DEVICE);
 
@@ -220,25 +272,122 @@ vfs_device_class_init (VfsDeviceClass * c G_GNUC_UNUSED)
     device_class->finish_file = vfs_device_finish_file;
     device_class->seek_file = vfs_device_seek_file;
     device_class->seek_block = vfs_device_seek_block;
-    device_class->property_get = vfs_device_property_get;
-    device_class->property_set = vfs_device_property_set;
     device_class->recycle_file = vfs_device_recycle_file;
+    device_class->erase = vfs_device_erase;
+    device_class->finish = vfs_device_finish;
+
     g_object_class->finalize = vfs_device_finalize;
 }
 
-/* Drops everything associated with the volume file: Its name and fd,
-   its lock, and its lock's name and fd. */
-static void release_file(VfsDevice * self) {
+static void
+vfs_device_base_init (VfsDeviceClass * c)
+{
+    DeviceClass *device_class = (DeviceClass *)c;
+
+    device_class_register_property(device_class, PROPERTY_MONITOR_FREE_SPACE,
+           PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK,
+           property_get_monitor_free_space_fn,
+           property_set_monitor_free_space_fn);
+
+    device_class_register_property(device_class, PROPERTY_MAX_VOLUME_USAGE,
+           (PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK) &
+                       (~ PROPERTY_ACCESS_SET_INSIDE_FILE_WRITE),
+           device_simple_property_get_fn,
+           vfs_device_set_max_volume_usage_fn);
+
+    device_class_register_property(device_class, PROPERTY_ENFORCE_MAX_VOLUME_USAGE,
+            (PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK) &
+                        (~ PROPERTY_ACCESS_SET_INSIDE_FILE_WRITE),
+            device_simple_property_get_fn,
+            vfs_device_set_enforce_max_volume_usage_fn);
+
+    device_class_register_property(device_class, PROPERTY_COMPRESSION,
+           PROPERTY_ACCESS_GET_MASK,
+           device_simple_property_get_fn,
+           NULL);
+
+    /* add the ability to set LEOM to FALSE, for testing purposes */
+    device_class_register_property(device_class, PROPERTY_LEOM,
+           PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_BEFORE_START,
+           device_simple_property_get_fn,
+           property_set_leom_fn);
+}
+
+static gboolean
+vfs_device_set_max_volume_usage_fn(Device *p_self,
+    DevicePropertyBase *base, GValue *val,
+    PropertySurety surety, PropertySource source)
+{
+    VfsDevice *self = VFS_DEVICE(p_self);
+
+    self->volume_limit = g_value_get_uint64(val);
+
+    return device_simple_property_set_fn(p_self, base, val, surety, source);
+}
+
+static gboolean
+vfs_device_set_enforce_max_volume_usage_fn(Device *p_self,
+    DevicePropertyBase *base, GValue *val,
+    PropertySurety surety, PropertySource source)
+{
+    VfsDevice *self = VFS_DEVICE(p_self);
+
+    self->enforce_volume_limit = g_value_get_boolean(val);
+
+    return device_simple_property_set_fn(p_self, base, val, surety, source);
+}
+
+static gboolean
+property_get_monitor_free_space_fn(Device *p_self, DevicePropertyBase *base G_GNUC_UNUSED,
+    GValue *val, PropertySurety *surety, PropertySource *source)
+{
+    VfsDevice *self = VFS_DEVICE(p_self);
+
+    g_value_unset_init(val, G_TYPE_BOOLEAN);
+    g_value_set_boolean(val, self->monitor_free_space);
+
+    if (surety)
+       *surety = PROPERTY_SURETY_GOOD;
+
+    if (source)
+       *source = PROPERTY_SOURCE_DEFAULT;
+
+    return TRUE;
+}
+
+
+static gboolean
+property_set_monitor_free_space_fn(Device *p_self,
+    DevicePropertyBase *base, GValue *val,
+    PropertySurety surety, PropertySource source)
+{
+    VfsDevice *self = VFS_DEVICE(p_self);
+
+    self->monitor_free_space = g_value_get_boolean(val);
+
+    return device_simple_property_set_fn(p_self, base, val, surety, source);
+}
+
+static gboolean
+property_set_leom_fn(Device *p_self,
+    DevicePropertyBase *base, GValue *val,
+    PropertySurety surety, PropertySource source)
+{
+    VfsDevice *self = VFS_DEVICE(p_self);
+
+    self->leom = g_value_get_boolean(val);
+
+    return device_simple_property_set_fn(p_self, base, val, surety, source);
+}
+
+/* Drops everything associated with the volume file: Its name and fd. */
+void release_file(VfsDevice * self) {
     /* Doesn't hurt. */
-    robust_close(self->open_file_fd);
+    if (self->open_file_fd != -1)
+       robust_close(self->open_file_fd);
     amfree(self->file_name);
 
-    if (self->file_lock_fd > 0) {
-        amfunlock(self->file_lock_fd, self->file_lock_name);
-        close(self->file_lock_fd);
-        amfree(self->file_lock_name);
-    }
-    self->file_lock_fd = self->open_file_fd = -1;
+    self->open_file_fd = -1;
 }
 
 static void vfs_device_finalize(GObject * obj_self) {
@@ -254,53 +403,35 @@ static void vfs_device_finalize(GObject * obj_self) {
 
     amfree(self->dir_name);
 
-    if(self->dir_handle) {
-        closedir (self->dir_handle);
-        self->dir_handle = NULL;
-    }
-
     release_file(self);
-
-    if (self->volume_lock_fd >= 0) {
-        amfunlock(self->volume_lock_fd, self->volume_lock_name);
-        close(self->volume_lock_fd);
-    }
-
-    amfree(self->volume_lock_name);
 }
 
-static Device * vfs_device_factory(char * device_type,
-                                   char * device_name) {
+static Device * vfs_device_factory(char * device_name, char * device_type, char * device_node) {
     Device * rval;
     g_assert(0 == strcmp(device_type, "file"));
     rval = DEVICE(g_object_new(TYPE_VFS_DEVICE, NULL));
-    if (!device_open_device(rval, device_name)) {
-        g_object_unref(rval);
-        return NULL;
-    } else {
-        return rval;
-    }
+    device_open_device(rval, device_name, device_type, device_node);
+    return rval;
 }
 
-static gboolean check_is_dir(const char * name, gboolean printmsg) {
+static gboolean check_is_dir(VfsDevice * self, const char * name) {
+    Device *dself = DEVICE(self);
     struct stat dir_status;
-    
+
     if (stat(name, &dir_status) < 0) {
 #ifdef EINTR
         if (errno == EINTR) {
-            return check_is_dir(name, printmsg);
+            return check_is_dir(self, name);
         }
 #endif /* EINTR */
-        if (printmsg) {
-            g_fprintf(stderr, "Error checking directory %s: %s\n",
-                    name, strerror(errno));
-        }
+       device_set_error(dself,
+           vstrallocf(_("Error checking directory %s: %s"), name, strerror(errno)),
+           DEVICE_STATUS_DEVICE_ERROR);
         return FALSE;
     } else if (!S_ISDIR(dir_status.st_mode)) {
-        if (printmsg) {
-            g_fprintf(stderr, "VFS Device path %s is not a directory.\n",
-                    name);
-        }
+       device_set_error(dself,
+                   vstrallocf(_("VFS Device path %s is not a directory"), name),
+                   DEVICE_STATUS_DEVICE_ERROR);
         return FALSE;
     } else {
         return TRUE;
@@ -319,17 +450,15 @@ static gboolean file_number_to_file_name_functor(const char * filename,
     char * result_tmp;
     struct stat file_status;
     fnfn_data *data = (fnfn_data*)datap;
-    
-    result_tmp = vstralloc(data->self->dir_name, "/", filename, NULL);    
-    
+
+    result_tmp = vstralloc(data->self->dir_name, "/", filename, NULL);
+
     /* Just to be thorough, let's check that it's a real
        file. */
     if (0 != stat(result_tmp, &file_status)) {
-        g_fprintf(stderr, "Cannot stat file %s (%s), ignoring it.\n", 
-                result_tmp, strerror(errno));
+       g_warning(_("Cannot stat file %s (%s), ignoring it"), result_tmp, strerror(errno));
     } else if (!S_ISREG(file_status.st_mode)) {
-        g_fprintf(stderr, "%s is not a regular file, ignoring it.\n",
-                result_tmp);
+       g_warning(_("%s is not a regular file, ignoring it"), result_tmp);
     } else {
         data->count ++;
         if (data->result == NULL) {
@@ -349,15 +478,14 @@ static char * file_number_to_file_name(VfsDevice * self, guint device_file) {
     char * regex;
     fnfn_data data;
 
-    g_return_val_if_fail(self != NULL, NULL);
     data.self = self;
     data.count = 0;
     data.result = NULL;
 
     regex = g_strdup_printf("^0*%u\\.", device_file);
 
-    search_directory(self->dir_handle, regex,
-                     file_number_to_file_name_functor, &data);
+    search_vfs_directory(self, regex,
+                         file_number_to_file_name_functor, &data);
 
     amfree(regex);
 
@@ -365,8 +493,7 @@ static char * file_number_to_file_name(VfsDevice * self, guint device_file) {
         g_assert(data.result == NULL);
         return NULL;
     } else if (data.count > 1) {
-        g_fprintf(stderr,
-                "Found multiple names for file number %d, choosing file %s.\n",
+       g_warning("Found multiple names for file number %d, choosing file %s",
                 device_file, data.result);
         return data.result;
     } else {
@@ -397,60 +524,13 @@ static gboolean open_lock(G_GNUC_UNUSED VfsDevice * self,
 
     /* At the moment, file locking is horribly broken. */
     return TRUE;
-
-/*
-    int fd;
-    char * name;
-    if (file < 0) {
-        if (self->volume_lock_name == NULL) {
-            self->volume_lock_name = lockfile_name(self, 0);
-        } else if (self->volume_lock_fd >= 0) {
-            amfunlock(self->volume_lock_fd, self->volume_lock_name);
-            close(self->volume_lock_fd);
-        }
-        name = self->volume_lock_name;
-    } else {
-        if (self->file_lock_fd >= 0 && self->file_lock_name != NULL) {
-            amfunlock(self->file_lock_fd, self->file_lock_name);
-        }
-        amfree(self->file_lock_name);
-        close(self->file_lock_fd);
-        name = self->file_lock_name = lockfile_name(self, file);
-    }
-        
-
-    fd = robust_open(name, O_CREAT | O_WRONLY, VFS_DEVICE_CREAT_MODE);
-
-    if (fd < 0) {
-        g_fprintf(stderr, "Can't open lock file %s: %s\n",
-                name, strerror(errno));
-        return FALSE;
-    }
-
-    if (exclusive) {
-        amflock(fd, name);
-    } else {
-        amroflock(fd, name);
-    }
-
-    if (file < 0) {
-        self->volume_lock_fd = fd;
-    } else {
-        self->file_lock_fd = fd;
-    }
-    return TRUE;
-*/
 }
 
 /* For now, does it the bad way. */
-static void promote_volume_lock(VfsDevice * self) {
-    amfunlock(self->volume_lock_fd, self->volume_lock_name);
-    amflock(self->volume_lock_fd, self->volume_lock_name);
+static void promote_volume_lock(VfsDevice * self G_GNUC_UNUSED) {
 }
 
-static void demote_volume_lock(VfsDevice * self) {
-    amfunlock(self->volume_lock_fd, self->volume_lock_name);
-    amroflock(self->volume_lock_fd, self->volume_lock_name);
+static void demote_volume_lock(VfsDevice * self G_GNUC_UNUSED) {
 }
 
 /* A SearchDirectoryFunctor */
@@ -458,15 +538,13 @@ static gboolean update_volume_size_functor(const char * filename,
                                            gpointer user_data) {
     char * full_filename;
     struct stat stat_buf;
-    VfsDevice * self = user_data;
-    g_return_val_if_fail(IS_VFS_DEVICE(self), FALSE);
-    
+    VfsDevice * self = VFS_DEVICE(user_data);
+
     full_filename = vstralloc(self->dir_name, "/", filename, NULL);
 
     if (stat(full_filename, &stat_buf) < 0) {
         /* Log it and keep going. */
-        g_fprintf(stderr, "Couldn't stat file %s: %s\n",
-                full_filename, strerror(errno));
+       g_warning(_("Couldn't stat file %s: %s"), full_filename, strerror(errno));
         amfree(full_filename);
         return TRUE;
     }
@@ -478,49 +556,28 @@ static gboolean update_volume_size_functor(const char * filename,
 }
 
 static void update_volume_size(VfsDevice * self) {
+
     self->volume_bytes = 0;
-    search_directory(self->dir_handle, "^[0-9]+\\.",
-                     update_volume_size_functor, self);
+    search_vfs_directory(self, "^[0-9]+\\.",
+                         update_volume_size_functor, self);
 
 }
 
-static gboolean 
-vfs_device_open_device (Device * pself, char * device_name) {
+static void
+vfs_device_open_device (Device * pself, char * device_name, char * device_type, char * device_node) {
     VfsDevice * self;
-    dumpfile_t * rval;
-    
     self = VFS_DEVICE(pself);
-    g_return_val_if_fail (self != NULL, FALSE);
-    g_return_val_if_fail (device_name != NULL, FALSE);
+
+    pself->min_block_size = VFS_DEVICE_MIN_BLOCK_SIZE;
+    pself->max_block_size = VFS_DEVICE_MAX_BLOCK_SIZE;
+    pself->block_size = VFS_DEVICE_DEFAULT_BLOCK_SIZE;
 
     /* We don't have to free this ourselves; it will be freed by
      * vfs_device_finalize whether we succeed here or not. */
-    self->dir_name = g_strconcat(device_name, "/data/", NULL);
-    if (!check_is_dir(self->dir_name, TRUE)) {
-        return FALSE;
-    }
-
-    /* Next open the directory itself. */
-    self->dir_handle = opendir(self->dir_name);
-    if (self->dir_handle == NULL) {
-        g_fprintf(stderr, "Couldn't open directory %s for reading: %s\n",
-                device_name, strerror(errno));
-        return FALSE;
-    }
-
-    if (!open_lock(self, -1, FALSE))
-        return FALSE;
-
-    /* Not an error if this fails. Note that we ignore the class hierarchy.
-     */
-    rval = vfs_device_seek_file(pself, 0);
-    amfree(rval);
+    self->dir_name = g_strconcat(device_node, "/data/", NULL);
 
     if (parent_class->open_device) {
-        /* Will call vfs_device_read_label. */
-        return (parent_class->open_device)(pself, device_name);
-    } else {
-        return TRUE;
+        parent_class->open_device(pself, device_name, device_type, device_node);
     }
 }
 
@@ -531,7 +588,6 @@ static gboolean delete_vfs_files_functor(const char * filename,
     char * path_name;
 
     self = VFS_DEVICE(user_data);
-    g_return_val_if_fail(self != NULL, FALSE);
 
     /* Skip the volume lock. */
     if (strcmp(filename, VOLUME_LOCKFILE_NAME) == 0)
@@ -539,8 +595,7 @@ static gboolean delete_vfs_files_functor(const char * filename,
 
     path_name = vstralloc(self->dir_name, "/", filename, NULL);
     if (unlink(path_name) != 0) {
-        g_fprintf(stderr, "Error unlinking %s: %s\n", path_name,
-                strerror(errno));
+       g_warning(_("Error unlinking %s: %s"), path_name, strerror(errno));
     }
     amfree(path_name);
     return TRUE;
@@ -548,32 +603,27 @@ static gboolean delete_vfs_files_functor(const char * filename,
 
 /* delete_vfs_files deletes all VfsDevice files in the directory except the
    volume lockfile. */
-static gboolean delete_vfs_files(VfsDevice * self) {
+void delete_vfs_files(VfsDevice * self) {
     g_assert(self != NULL);
-    g_assert(self->dir_handle != NULL);
 
     /* This function assumes that the volume is locked! */
-    search_directory(self->dir_handle, VFS_DEVICE_FILE_REGEX,
-                     delete_vfs_files_functor, self);
-    return TRUE;
+    search_vfs_directory(self, VFS_DEVICE_FILE_REGEX,
+                         delete_vfs_files_functor, self);
 }
 
 /* This is a functor suitable for search_directory. It simply prints a
    warning. It also dodges the volume lockfile. */
 static gboolean check_dir_empty_functor(const char * filename,
                                         gpointer user_data) {
-    VfsDevice * self;
+    VfsDevice * self = VFS_DEVICE(user_data);
     char * path_name;
 
-    self = VFS_DEVICE(user_data);
-    g_return_val_if_fail(self != NULL, FALSE);
-
     if (strcmp(filename, VOLUME_LOCKFILE_NAME) == 0)
         return TRUE;
 
     path_name = vstralloc(self->dir_name, "/", filename, NULL);
 
-    g_fprintf(stderr, "Found spurious storage file %s\n", path_name);
+    g_warning(_("Found spurious storage file %s"), path_name);
 
     amfree(path_name);
     return TRUE;
@@ -584,17 +634,21 @@ static gboolean write_amanda_header(VfsDevice * self,
                                     const dumpfile_t * header) {
     char * label_buffer;
     IoResult result;
-    
-    g_return_val_if_fail(header != NULL, FALSE);
-    g_return_val_if_fail(self != NULL, FALSE);
-    label_buffer = build_header(header, VFS_DEVICE_LABEL_SIZE);
-    if (strlen(label_buffer)+1 > VFS_DEVICE_LABEL_SIZE) {
+    Device *d_self = DEVICE(self);
+
+    g_assert(header != NULL);
+
+    label_buffer = device_build_amanda_header(d_self, header, NULL);
+    if (!label_buffer) {
         amfree(label_buffer);
-        g_fprintf(stderr, "Amanda header header won't fit on VFS device!\n");
+       device_set_error(d_self,
+           stralloc(_("Amanda file header won't fit in a single block!")),
+           DEVICE_STATUS_DEVICE_ERROR);
         return FALSE;
     }
 
     result = vfs_device_robust_write(self, label_buffer, VFS_DEVICE_LABEL_SIZE);
+    /* vfs_device_robust_write sets error status if necessary */
     amfree(label_buffer);
     return (result == RESULT_SUCCESS);
 }
@@ -605,17 +659,16 @@ static gboolean write_amanda_header(VfsDevice * self,
 static gboolean clear_and_prepare_label(VfsDevice * self, char * label,
                                         char * timestamp) {
     dumpfile_t * label_header;
+    Device *d_self = DEVICE(self);
 
     release_file(self);
 
     /* Delete any extant data, except our volume lock. */
-    if (!delete_vfs_files(self)) {
-        return FALSE;
-    }
+    delete_vfs_files(self);
 
     /* Print warnings about any remaining files. */
-    search_directory(self->dir_handle, VFS_DEVICE_FILE_REGEX,
-                     check_dir_empty_functor, self);
+    search_vfs_directory(self, VFS_DEVICE_FILE_REGEX,
+                         check_dir_empty_functor, self);
 
     self->file_name = g_strdup_printf("%s/00000.%s", self->dir_name, label);
 
@@ -623,84 +676,143 @@ static gboolean clear_and_prepare_label(VfsDevice * self, char * label,
                                      O_CREAT | O_EXCL | O_WRONLY,
                                      VFS_DEVICE_CREAT_MODE);
     if (self->open_file_fd < 0) {
-        g_fprintf(stderr, "Can't open file %s: %s\n", self->file_name,
-                strerror(errno));
+       device_set_error(d_self,
+           vstrallocf(_("Can't open file %s: %s"), self->file_name, strerror(errno)),
+           DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);
         return FALSE;
     }
 
     label_header = make_tapestart_header(DEVICE(self), label, timestamp);
-    if (write_amanda_header(self, label_header)) {
-        amfree(label_header);
-        self->volume_bytes = VFS_DEVICE_LABEL_SIZE;
-        return TRUE;
-    } else {
-        amfree(label_header);
+    if (!write_amanda_header(self, label_header)) {
+       /* write_amanda_header sets error status if necessary */
+        dumpfile_free(label_header);
         return FALSE;
     }
+    dumpfile_free(d_self->volume_header);
+    d_self->header_block_size = VFS_DEVICE_LABEL_SIZE;
+    d_self->volume_header = label_header;
+    self->volume_bytes = VFS_DEVICE_LABEL_SIZE;
+    return TRUE;
+}
+
+/* Just like search_directory, but returns -1 in the event of an error */
+static int
+search_vfs_directory(
+    VfsDevice *self,
+    const char * regex,
+    SearchDirectoryFunctor functor,
+    gpointer user_data)
+{
+    Device *dself = DEVICE(self);
+    DIR *dir_handle;
+    int result = -1;
+
+    dir_handle = opendir(self->dir_name);
+    if (dir_handle == NULL) {
+       device_set_error(dself,
+               vstrallocf(_("Couldn't open device %s (directory %s) for reading: %s"),
+                       dself->device_name, self->dir_name, strerror(errno)),
+               DEVICE_STATUS_DEVICE_ERROR);
+        goto error;
+    }
+
+    /* TODO: is this the right moment to acquire a lock?? */
+
+    result = search_directory(dir_handle, regex, functor, user_data);
+
+error:
+    if (dir_handle)
+       closedir(dir_handle);
+    return result;
 }
 
-static ReadLabelStatusFlags vfs_device_read_label(Device * dself) {
+static DeviceStatusFlags vfs_device_read_label(Device * dself) {
+    VfsDevice * self = VFS_DEVICE(dself);
     dumpfile_t * amanda_header;
-    VfsDevice * self;
 
-    self = VFS_DEVICE(dself);
-    g_return_val_if_fail(self != NULL, ~READ_LABEL_STATUS_SUCCESS);
+    g_assert(self != NULL);
+
+    if (!check_is_dir(self, self->dir_name)) {
+       /* error message set by check_is_dir */
+        return dself->status;
+    }
+
+    amfree(dself->volume_label);
+    amfree(dself->volume_time);
+    dumpfile_free(dself->volume_header);
+    dself->volume_header = NULL;
 
-    amanda_header = vfs_device_seek_file(dself, 0);
+    if (device_in_error(dself)) return dself->status;
+
+    amanda_header = dself->volume_header = vfs_device_seek_file(dself, 0);
+    release_file(self);
     if (amanda_header == NULL) {
         /* This means an error occured getting locks or opening the header
          * file. */
-        return (READ_LABEL_STATUS_DEVICE_ERROR |
-                READ_LABEL_STATUS_VOLUME_ERROR |
-                READ_LABEL_STATUS_VOLUME_UNLABELED);
+       device_set_error(dself,
+               stralloc("Error loading device header -- unlabeled volume?"),
+                 DEVICE_STATUS_DEVICE_ERROR
+               | DEVICE_STATUS_VOLUME_ERROR
+               | DEVICE_STATUS_VOLUME_UNLABELED);
+       return dself->status;
     }
 
-    if (amanda_header->type != F_TAPESTART) {
+    /* close the fd we just opened */
+    vfs_device_finish_file(dself);
+
+    if (amanda_header->type != F_TAPESTART &&
+       amanda_header->type != F_EMPTY) {
         /* This is an error, and should not happen. */
-        g_fprintf(stderr, "Got a bad volume label\n");
+       device_set_error(dself,
+               stralloc(_("Got a bad volume label")),
+               DEVICE_STATUS_VOLUME_ERROR);
         amfree(amanda_header);
-        return READ_LABEL_STATUS_VOLUME_ERROR;
+        return dself->status;
     }
 
-    dself->volume_label = g_strdup(amanda_header->name);
-    dself->volume_time = g_strdup(amanda_header->datestamp);
-    amfree(amanda_header);
+    /* self->volume_header is already set */
+
+    if (amanda_header->type == F_TAPESTART) {
+       dself->volume_label = g_strdup(amanda_header->name);
+       dself->volume_time = g_strdup(amanda_header->datestamp);
+       device_set_error(dself, NULL, DEVICE_STATUS_SUCCESS);
+    }
 
     update_volume_size(self);
 
-    if (parent_class->read_label) {
-        return (parent_class->read_label)(dself);
-    } else {
-        return READ_LABEL_STATUS_SUCCESS;
-    }
+    return dself->status;
 }
 
-static gboolean vfs_device_write_block(Device * pself, guint size,
-                                       gpointer data, gboolean last_block) {
+static gboolean vfs_device_write_block(Device * pself, guint size, gpointer data) {
     VfsDevice * self = VFS_DEVICE(pself);
     IoResult result;
-    g_return_val_if_fail(self != NULL, FALSE);
-    g_return_val_if_fail(last_block || size >= (guint)self->block_size, FALSE);
-    g_return_val_if_fail(pself->in_file, FALSE);
+
+    if (device_in_error(self)) return FALSE;
+
     g_assert(self->open_file_fd >= 0);
 
-    if (self->volume_limit > 0 &&
-        self->volume_bytes + size > self->volume_limit) {
-        /* Simulate EOF. */
-        pself->is_eof = TRUE;
-        return FALSE;
+    if (check_at_leom(self, size))
+       pself->is_eom = TRUE;
+
+    if (check_at_peom(self, size)) {
+       pself->is_eom = TRUE;
+       device_set_error(pself,
+           stralloc(_("No space left on device")),
+           DEVICE_STATUS_VOLUME_ERROR);
+       return FALSE;
     }
 
     result = vfs_device_robust_write(self, data, size);
-    if (result == RESULT_SUCCESS) {
-        self->volume_bytes += size;
-        if (parent_class->write_block) {
-            (parent_class->write_block)(pself, size, data, last_block);
-        }
-        return TRUE;
-    } else {
+    if (result != RESULT_SUCCESS) {
+       /* vfs_device_robust_write set error status appropriately */
         return FALSE;
     }
+
+    self->volume_bytes += size;
+    self->checked_bytes_used += size;
+    pself->block ++;
+
+    return TRUE;
 }
 
 static int
@@ -708,57 +820,98 @@ vfs_device_read_block(Device * pself, gpointer data, int * size_req) {
     VfsDevice * self;
     int size;
     IoResult result;
-    
+
     self = VFS_DEVICE(pself);
-    g_return_val_if_fail (self != NULL, -1);
 
-    if (data == NULL || *size_req < self->block_size) {
+    if (device_in_error(self)) return -1;
+
+    if (data == NULL || (gsize)*size_req < pself->block_size) {
         /* Just a size query. */
-        *size_req = self->block_size;
+       g_assert(pself->block_size < INT_MAX);
+        *size_req = (int)pself->block_size;
         return 0;
     }
 
-    size = self->block_size;
+    size = pself->block_size;
     result = vfs_device_robust_read(self, data, &size);
     switch (result) {
     case RESULT_SUCCESS:
         *size_req = size;
+       pself->block++;
         return size;
     case RESULT_NO_DATA:
         pself->is_eof = TRUE;
         pself->in_file = FALSE;
+       device_set_error(pself,
+           stralloc(_("EOF")),
+           DEVICE_STATUS_SUCCESS);
         return -1;
     default:
+       device_set_error(pself,
+           vstrallocf(_("Error reading from data file: %s"), strerror(errno)),
+           DEVICE_STATUS_DEVICE_ERROR);
         return -1;
     }
 
     g_assert_not_reached();
 }
 
-static gboolean        vfs_device_start(Device * pself,
+static gboolean
+vfs_device_start(Device * dself,
                                  DeviceAccessMode mode, char * label,
                                  char * timestamp) {
-    VfsDevice * self;
-    self = VFS_DEVICE(pself);
-    g_return_val_if_fail(self != NULL, FALSE);
-    g_return_val_if_fail(parent_class->start != NULL, FALSE);
-    
+    VfsDevice * self = VFS_DEVICE(dself);
+
+    if (!check_is_dir(self, self->dir_name)) {
+       /* error message set by check_is_dir */
+        return FALSE;
+    }
+
+    dself->in_file = FALSE;
+
     if (mode == ACCESS_WRITE) {
         promote_volume_lock(self);
         if (!clear_and_prepare_label(self, label, timestamp)) {
+           /* clear_and_prepare_label sets error status if necessary */
             demote_volume_lock(self);
             return FALSE;
         }
+
+        dself->volume_label = newstralloc(dself->volume_label, label);
+        dself->volume_time = newstralloc(dself->volume_time, timestamp);
+
+       /* unset the VOLUME_UNLABELED flag, if it was set */
+       device_set_error(dself, NULL, DEVICE_STATUS_SUCCESS);
+
         demote_volume_lock(self);
+        dself->access_mode = mode;
+    } else {
+       if (dself->volume_label == NULL && device_read_label(dself) != DEVICE_STATUS_SUCCESS) {
+           /* device_read_label already set our error message */
+            return FALSE;
+       } else {
+            dself->access_mode = mode;
+        }
     }
 
     release_file(self);
-    if (parent_class->start) {
-        return parent_class->start(pself, mode, label, timestamp);
-    } else {
-        return TRUE;
-    }
+
+    return TRUE;
+}
+
+static gboolean
+vfs_device_finish (Device * pself) {
+    VfsDevice * self;
+    self = VFS_DEVICE(pself);
+
+    release_file(self);
+
+    pself->access_mode = ACCESS_NULL;
+    pself->in_file = FALSE;
+
+    if (device_in_error(self)) return FALSE;
+
+    return TRUE;
 }
 
 typedef struct {
@@ -771,11 +924,10 @@ static gboolean get_last_file_number_functor(const char * filename,
                                              gpointer datap) {
     guint64 file;
     glfn_data * data = (glfn_data*)datap;
-    g_return_val_if_fail(IS_VFS_DEVICE(data->self), FALSE);
+
     file = g_ascii_strtoull(filename, NULL, 10); /* Guaranteed to work. */
     if (file > G_MAXINT) {
-        g_fprintf(stderr, "Super-large device file %s found, ignoring.\n",
-               filename);
+       g_warning(_("Super-large device file %s found, ignoring"), filename);
         return TRUE;
     }
     /* This condition is needlessly complex due to sign issues. */
@@ -785,23 +937,27 @@ static gboolean get_last_file_number_functor(const char * filename,
     return TRUE;
 }
 
-static gint get_last_file_number(VfsDevice * self) {
+static gint
+get_last_file_number(VfsDevice * self) {
     glfn_data data;
     int count;
+    Device *d_self = DEVICE(self);
     data.self = self;
     data.rval = -1;
-    
-    count = search_directory(self->dir_handle, "^[0-9]+\\.",
-                             get_last_file_number_functor, &data);
+
+    count = search_vfs_directory(self, "^[0-9]+\\.",
+                                 get_last_file_number_functor, &data);
 
     if (count <= 0) {
         /* Somebody deleted something important while we weren't looking. */
-        g_fprintf(stderr, "Error identifying VFS device contents!\n");
+       device_set_error(d_self,
+           stralloc(_("Error identifying VFS device contents!")),
+           DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);
         return -1;
     } else {
         g_assert(data.rval >= 0);
     }
-    
+
     return data.rval;
 }
 
@@ -816,11 +972,10 @@ static gboolean get_next_file_number_functor(const char * filename,
                                              gpointer datap) {
     guint file;
     gnfn_data * data = (gnfn_data*)datap;
-    g_return_val_if_fail(IS_VFS_DEVICE(data->self), FALSE);
+
     file = g_ascii_strtoull(filename, NULL, 10); /* Guaranteed to work. */
     if (file > G_MAXINT) {
-        g_fprintf(stderr, "Super-large device file %s found, ignoring.\n",
-               filename);
+       g_warning(_("Super-large device file %s found, ignoring"), filename);
         return TRUE;
     }
     /* This condition is needlessly complex due to sign issues. */
@@ -833,22 +988,26 @@ static gboolean get_next_file_number_functor(const char * filename,
 
 /* Returns the file number equal to or greater than the given requested
  * file number. */
-static gint get_next_file_number(VfsDevice * self, guint request) {
+static gint
+get_next_file_number(VfsDevice * self, guint request) {
     gnfn_data data;
     int count;
+    Device *d_self = DEVICE(self);
     data.self = self;
     data.request = request;
     data.best_found = -1;
-    
-    count = search_directory(self->dir_handle, "^[0-9]+\\.",
-                             get_next_file_number_functor, &data);
+
+    count = search_vfs_directory(self, "^[0-9]+\\.",
+                                 get_next_file_number_functor, &data);
 
     if (count <= 0) {
         /* Somebody deleted something important while we weren't looking. */
-        g_fprintf(stderr, "Error identifying VFS device contents!\n");
+       device_set_error(d_self,
+           stralloc(_("Error identifying VFS device contents!")),
+           DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);
         return -1;
     }
-    
+
     /* Could be -1. */
     return data.best_found;
 }
@@ -864,7 +1023,7 @@ char * make_new_file_name(VfsDevice * self, const dumpfile_t * ji) {
         fileno = 1 + get_last_file_number(self);
         if (fileno <= 0)
             return NULL;
-    
+
         if (open_lock(self, fileno, TRUE)) {
             break;
         } else {
@@ -884,18 +1043,27 @@ char * make_new_file_name(VfsDevice * self, const dumpfile_t * ji) {
     return rval;
 }
 
-static gboolean 
-vfs_device_start_file (Device * pself, const dumpfile_t * ji) {
-    VfsDevice * self;
+static gboolean
+vfs_device_start_file (Device * dself, dumpfile_t * ji) {
+    VfsDevice * self = VFS_DEVICE(dself);
 
-    self = VFS_DEVICE(pself);
-    g_return_val_if_fail (self != NULL, FALSE);
-    g_return_val_if_fail (ji != NULL, FALSE);
+    dself->is_eom = FALSE;
 
-    if (self->volume_limit > 0 &&
-        self->volume_bytes + VFS_DEVICE_LABEL_SIZE > self->volume_limit) {
-        /* No more room. */
-        return FALSE;
+    if (device_in_error(self)) return FALSE;
+
+    /* set the blocksize in the header to 32k, since the VFS header is always
+     * 32k regardless of the block_size setting */
+    ji->blocksize = 32768;
+
+    if (check_at_leom(self, VFS_DEVICE_LABEL_SIZE))
+       dself->is_eom = TRUE;
+
+    if (check_at_peom(self, VFS_DEVICE_LABEL_SIZE)) {
+       dself->is_eom = TRUE;
+       device_set_error(dself,
+               stralloc(_("No space left on device")),
+               DEVICE_STATUS_DEVICE_ERROR);
+       return FALSE;
     }
 
     /* The basic idea here is thus:
@@ -906,69 +1074,73 @@ vfs_device_start_file (Device * pself, const dumpfile_t * ji) {
        5) Chain up. */
 
     self->file_name = make_new_file_name(self, ji);
-    if (self->file_name == NULL)
+    if (self->file_name == NULL) {
+       device_set_error(dself,
+               stralloc(_("Could not create header filename")),
+               DEVICE_STATUS_DEVICE_ERROR);
         return FALSE;
+    }
 
     self->open_file_fd = robust_open(self->file_name,
                                      O_CREAT | O_EXCL | O_RDWR,
                                      VFS_DEVICE_CREAT_MODE);
     if (self->open_file_fd < 0) {
-        g_fprintf(stderr, "Can't create file %s: %s\n", self->file_name,
-                strerror(errno));
+       device_set_error(dself,
+               vstrallocf(_("Can't create file %s: %s"), self->file_name, strerror(errno)),
+               DEVICE_STATUS_DEVICE_ERROR);
         release_file(self);
         return FALSE;
     }
 
-    
+
     if (!write_amanda_header(self, ji)) {
+       /* write_amanda_header sets error status if necessary */
         release_file(self);
         return FALSE;
     }
 
+    /* handle some accounting business */
     self->volume_bytes += VFS_DEVICE_LABEL_SIZE;
-    /* make_new_file_name set pself->file for us, but the parent class will increment it, so decrement it now */
-    pself->file--;
+    self->checked_bytes_used += VFS_DEVICE_LABEL_SIZE;
+    dself->in_file = TRUE;
+    dself->block = 0;
+    /* make_new_file_name set pself->file for us */
 
-    if (parent_class->start_file) {
-        parent_class->start_file(pself, ji);
-    }
     return TRUE;
 }
 
-static gboolean 
-vfs_device_finish_file (Device * pself) {
-    VfsDevice * self;
-    self = VFS_DEVICE(pself);
-    g_return_val_if_fail(self != NULL, FALSE);
+static gboolean
+vfs_device_finish_file(Device * dself) {
+    VfsDevice * self = VFS_DEVICE(dself);
+
+    if (device_in_error(self)) return FALSE;
 
     release_file(self);
-    
-    if (parent_class->finish_file) {
-        return parent_class->finish_file(pself);
-    } else {
-        return TRUE;
-    }
-    g_assert_not_reached();
+
+    dself->in_file = FALSE;
+
+    return TRUE;
 }
 
 /* This function is used for two purposes, rather than one. In
  * addition to its documented behavior, we also use it to open the
  * volume label for reading at startup. In that second case, we avoid
  * FdDevice-related side effects. */
-static dumpfile_t * 
-vfs_device_seek_file (Device * pself, guint requested_file) {
-    VfsDevice * self;
+static dumpfile_t *
+vfs_device_seek_file (Device * dself, guint requested_file) {
+    VfsDevice *self = VFS_DEVICE(dself);
     int file;
     dumpfile_t * rval;
     char header_buffer[VFS_DEVICE_LABEL_SIZE];
     int header_buffer_size = sizeof(header_buffer);
     IoResult result;
 
-    self = VFS_DEVICE(pself);
-    g_return_val_if_fail (self != NULL, NULL);
+    if (device_in_error(self)) return NULL;
+
+    dself->in_file = FALSE;
+    dself->is_eof = FALSE;
+    dself->block = 0;
 
-    pself->in_file = FALSE;
-    
     release_file(self);
 
     if (requested_file > 0) {
@@ -983,26 +1155,40 @@ vfs_device_seek_file (Device * pself, guint requested_file) {
         tmp_file_name = file_number_to_file_name(self, requested_file - 1);
         if (tmp_file_name != NULL) {
             free(tmp_file_name);
+           dself->file = requested_file; /* other attributes are already correct */
             return make_tapeend_header();
         } else {
+           device_set_error(dself,
+               stralloc(_("Attempt to read past tape-end file")),
+               DEVICE_STATUS_SUCCESS);
             return NULL;
         }
     }
 
     if (!open_lock(self, file, FALSE)) {
+       device_set_error(dself,
+           stralloc(_("could not acquire lock")),
+           DEVICE_STATUS_DEVICE_ERROR);
         return NULL;
     }
 
     self->file_name = file_number_to_file_name(self, file);
     if (self->file_name == NULL) {
+       device_set_error(dself,
+           vstrallocf(_("File %d not found"), file),
+           file == 0 ? DEVICE_STATUS_VOLUME_UNLABELED
+                     : DEVICE_STATUS_VOLUME_ERROR);
         release_file(self);
-        return NULL;
+       rval = g_new(dumpfile_t, 1);
+       fh_init(rval);
+        return rval;
     }
 
     self->open_file_fd = robust_open(self->file_name, O_RDONLY, 0);
-    if (self->open_file_fd <= 0) {
-        g_fprintf(stderr, "Couldn't open file %s: %s\n", self->file_name,
-                strerror(errno));
+    if (self->open_file_fd < 0) {
+       device_set_error(dself,
+           vstrallocf(_("Couldn't open file %s: %s"), self->file_name, strerror(errno)),
+           DEVICE_STATUS_DEVICE_ERROR);
         amfree(self->file_name);
         release_file(self);
         return NULL;
@@ -1011,140 +1197,158 @@ vfs_device_seek_file (Device * pself, guint requested_file) {
     result = vfs_device_robust_read(self, header_buffer,
                                     &header_buffer_size);
     if (result != RESULT_SUCCESS) {
-        g_fprintf(stderr, "Problem reading Amanda header.\n");
+       device_set_error(dself,
+           vstrallocf(_("Problem reading Amanda header: %s"), device_error(dself)),
+           DEVICE_STATUS_VOLUME_ERROR);
         release_file(self);
         return NULL;
     }
 
-    rval = malloc(sizeof(*rval));
+    rval = g_new(dumpfile_t, 1);
     parse_file_header(header_buffer, rval, header_buffer_size);
-    if (file > 0) {
-        switch (rval->type) {
+    switch (rval->type) {
         case F_DUMPFILE:
         case F_CONT_DUMPFILE:
         case F_SPLIT_DUMPFILE:
-            /* Chain up. */
-            if (parent_class->seek_file) {
-                parent_class->seek_file(pself, file);
-            }
-            return rval;
+           break;
+
+       case F_TAPESTART:
+           /* file 0 should have a TAPESTART header; vfs_device_read_label
+            * uses this */
+           if (requested_file == 0)
+               break;
+           /* FALLTHROUGH */
+
         default:
+           device_set_error(dself,
+               stralloc(_("Invalid amanda header while reading file header")),
+               DEVICE_STATUS_VOLUME_ERROR);
             amfree(rval);
             release_file(self);
             return NULL;
-        }
-    } else if (file == 0) {
-        return rval;
-    } else {
-        amfree(rval);
-        return NULL;
     }
+
+    /* update our state */
+    if (requested_file == 0) {
+       dself->header_block_size = header_buffer_size;
+    }
+    dself->in_file = TRUE;
+    dself->file = file;
+
+    return rval;
 }
 
-static gboolean 
+static gboolean
 vfs_device_seek_block (Device * pself, guint64 block) {
     VfsDevice * self;
     off_t result;
 
     self = VFS_DEVICE(pself);
-    g_return_val_if_fail (self != NULL, FALSE);
-    g_return_val_if_fail (self->open_file_fd >= 0, FALSE);
+
+    g_assert(self->open_file_fd >= 0);
     g_assert(sizeof(off_t) >= sizeof(guint64));
+    if (device_in_error(self)) return FALSE;
 
     /* Pretty simple. We figure out the blocksize and use that. */
     result = lseek(self->open_file_fd,
-                   (block) * self->block_size + VFS_DEVICE_LABEL_SIZE,
+                   (block) * pself->block_size + VFS_DEVICE_LABEL_SIZE,
                    SEEK_SET);
-    return (result != (off_t)(-1));
-}
 
-static gboolean
-vfs_device_property_get (Device * pself, DevicePropertyId ID, GValue * val) {
-    VfsDevice * self;
-    self = VFS_DEVICE(pself);
-    g_return_val_if_fail(self != NULL, FALSE);
-    if (ID == PROPERTY_BLOCK_SIZE) {
-        g_value_unset_init(val, G_TYPE_INT);
-        g_value_set_int(val, self->block_size);
-        return TRUE;
-    } else if (ID == PROPERTY_MAX_VOLUME_USAGE) {
-        g_value_unset_init(val, G_TYPE_UINT64);
-        g_value_set_uint64(val, self->volume_limit);
-        return TRUE;
-    } else if (ID == PROPERTY_FREE_SPACE) {
-       QualifiedSize qsize;
-       struct fs_usage fsusage;
-       guint64 bytes_avail;
-
-       if (get_fs_usage(self->dir_name, NULL, &fsusage) == 0) {
-           if (fsusage.fsu_bavail_top_bit_set)
-               bytes_avail = 0;
-           else
-               bytes_avail = fsusage.fsu_bavail * fsusage.fsu_blocksize;
-           if (self->volume_limit && (guint64)self->volume_limit < bytes_avail / 1024)
-               bytes_avail = (guint64)self->volume_limit * 1024;
-
-           qsize.accuracy = SIZE_ACCURACY_REAL;
-           qsize.bytes = bytes_avail;
-       } else {
-           g_warning(_("get_fs_usage('%s') failed: %s"), self->dir_name, strerror(errno));
-           qsize.accuracy = SIZE_ACCURACY_UNKNOWN;
-           qsize.bytes = 0;
-       }
-       g_value_unset_init(val, QUALIFIED_SIZE_TYPE);
-       g_value_set_boxed(val, &qsize);
-       return TRUE;
-    } else {
-        if (parent_class->property_get) {
-            return parent_class->property_get(pself, ID, val);
-        } else {
-            return FALSE;
-        }
-    }
-    g_assert_not_reached();
-}
+    pself->block = block;
 
-static gboolean 
-vfs_device_property_set (Device * pself, DevicePropertyId ID, GValue * val) {
-    VfsDevice * self;
-    self = VFS_DEVICE(pself);
-    g_return_val_if_fail(self != NULL, FALSE);
-    if (ID == PROPERTY_BLOCK_SIZE) {
-        int block_size = g_value_get_int(val);
-        g_return_val_if_fail(block_size > 0, FALSE);
-        self->block_size = block_size;
-        return TRUE;
-    } else if (ID == PROPERTY_MAX_VOLUME_USAGE) {
-        self->volume_limit = g_value_get_uint64(val);
-        return TRUE;
-    } else {
-        if (parent_class->property_set) {
-            return parent_class->property_set(pself, ID, val);
-        } else {
-            return FALSE;
-        }
+    if (result == (off_t)(-1)) {
+       device_set_error(pself,
+           vstrallocf(_("Error seeking within file: %s"), strerror(errno)),
+           DEVICE_STATUS_DEVICE_ERROR);
+       return FALSE;
     }
-    g_assert_not_reached();
+
+    return TRUE;
 }
 
 static gboolean try_unlink(const char * file) {
     if (unlink(file) < 0) {
-        g_fprintf(stderr, "Can't unlink file %s: %s\n", file, strerror(errno));
         return FALSE;
     } else {
         return TRUE;
     }
 }
 
-static gboolean 
-vfs_device_recycle_file (Device * pself, guint filenum) {
-    VfsDevice * self;
+static gboolean
+check_at_leom(VfsDevice *self, guint64 size)
+{
+    gboolean recheck = FALSE;
+    guint64 est_avail_now;
+    struct fs_usage fsusage;
+    guint64 block_size = DEVICE(self)->block_size;
+    guint64 eom_warning_buffer = EOM_EARLY_WARNING_ZONE_BLOCKS * block_size;
+
+    if (!self->leom || !self->monitor_free_space)
+       return FALSE;
+
+    /* handle VOLUME_LIMIT */
+    if (self->enforce_volume_limit && self->volume_limit &&
+           self->volume_bytes + size + eom_warning_buffer > self->volume_limit) {
+       return TRUE;
+    }
+
+    /* handle actual filesystem available space, using some heuristics to avoid polling this
+     * too frequently */
+    est_avail_now = 0;
+    if (self->checked_fs_free_bytes >= self->checked_bytes_used + size)
+       est_avail_now = self->checked_fs_free_bytes - self->checked_bytes_used - size;
+
+    /* is it time to check again? */
+    if (est_avail_now <= block_size * MONITOR_FREE_SPACE_CLOSELY_WITHIN_BLOCKS) {
+       recheck = TRUE;
+    } else if (self->checked_bytes_used > MONITOR_FREE_SPACE_EVERY_KB * 1024) {
+       recheck = TRUE;
+    } else if (self->checked_fs_free_time + MONITOR_FREE_SPACE_EVERY_SECONDS <= time(NULL)) {
+       recheck = TRUE;
+    }
+
+    if (!recheck)
+       return FALSE;
+
+    if (get_fs_usage(self->dir_name, NULL, &fsusage) < 0 || fsusage.fsu_bavail_top_bit_set) {
+       g_warning("Filesystem cannot provide free space: %s; setting MONITOR_FREE_SPACE false",
+               fsusage.fsu_bavail_top_bit_set? "no result" : strerror(errno));
+       self->monitor_free_space = FALSE;
+       return FALSE;
+    }
+
+    self->checked_fs_free_bytes = fsusage.fsu_bavail * fsusage.fsu_blocksize;
+    self->checked_bytes_used = 0;
+    self->checked_fs_free_time = time(NULL);
+
+    if (self->checked_fs_free_bytes - size <= eom_warning_buffer) {
+       g_debug("%s: at LEOM", DEVICE(self)->device_name);
+       return TRUE;
+    }
+
+    return FALSE;
+}
+
+static gboolean
+check_at_peom(VfsDevice *self, guint64 size)
+{
+    if (self->enforce_volume_limit && (self->volume_limit > 0)) {
+       guint64 newtotal = self->volume_bytes + size;
+        if (newtotal > self->volume_limit) {
+           return TRUE;
+       }
+    }
+
+    return FALSE;
+}
+
+static gboolean
+vfs_device_recycle_file (Device * dself, guint filenum) {
+    VfsDevice * self = VFS_DEVICE(dself);
     struct stat file_status;
     off_t file_size;
 
-    self = VFS_DEVICE(pself);
-    g_return_val_if_fail(self != NULL, FALSE);
-    g_return_val_if_fail(!(pself->in_file), FALSE);
+    if (device_in_error(self)) return FALSE;
 
     /* Game Plan:
      * 1) Get a write lock on the file in question.
@@ -1155,22 +1359,33 @@ vfs_device_recycle_file (Device * pself, guint filenum) {
      */
 
     self->file_name = file_number_to_file_name(self, filenum);
-
-    if (self->file_name == NULL)
+    if (self->file_name == NULL) {
+       device_set_error(dself,
+           vstrallocf(_("File %d not found"), filenum),
+           DEVICE_STATUS_VOLUME_ERROR);
         return FALSE;
+    }
 
-    if (!open_lock(self, filenum, TRUE))
+    if (!open_lock(self, filenum, FALSE)) {
+       device_set_error(dself,
+           stralloc(_("could not acquire lock")),
+           DEVICE_STATUS_DEVICE_ERROR);
         return FALSE;
+    }
 
     if (0 != stat(self->file_name, &file_status)) {
-        fprintf(stderr, "Cannot stat file %s (%s), so not removing.\n",
-                self->file_name, strerror(errno));
+       device_set_error(dself,
+           vstrallocf(_("Cannot stat file %s (%s), so not removing"),
+                                   self->file_name, strerror(errno)),
+           DEVICE_STATUS_VOLUME_ERROR);
         return FALSE;
     }
     file_size = file_status.st_size;
-    
-    if (!try_unlink(self->file_name) ||
-        !try_unlink(self->file_lock_name)) {
+
+    if (!try_unlink(self->file_name)) {
+       device_set_error(dself,
+           vstrallocf(_("Unlink of %s failed: %s"), self->file_name, strerror(errno)),
+           DEVICE_STATUS_VOLUME_ERROR);
         release_file(self);
         return FALSE;
     }
@@ -1180,9 +1395,29 @@ vfs_device_recycle_file (Device * pself, guint filenum) {
     return TRUE;
 }
 
+static gboolean
+vfs_device_erase (Device * dself) {
+    VfsDevice *self = VFS_DEVICE(dself);
+
+    if (!open_lock(self, 0, TRUE))
+        return FALSE;
+
+    delete_vfs_files(self);
+
+    release_file(self);
+
+    dumpfile_free(dself->volume_header);
+    dself->volume_header = NULL;
+    device_set_error(dself, g_strdup("Unlabeled volume"),
+                    DEVICE_STATUS_VOLUME_UNLABELED);
+
+    return TRUE;
+}
+
 static IoResult vfs_device_robust_read(VfsDevice * self, char *buf,
                                              int *count) {
     int fd = self->open_file_fd;
+    Device *d_self = DEVICE(self);
     int want = *count, got = 0;
 
     while (got < want) {
@@ -1213,9 +1448,11 @@ static IoResult vfs_device_robust_read(VfsDevice * self, char *buf,
             continue;
         } else {
             /* Error occured. */
-            g_fprintf(stderr, "Error reading fd %d: %s\n", fd, strerror(errno));
+           device_set_error(d_self,
+               vstrallocf(_("Error reading fd %d: %s"), fd, strerror(errno)),
+               DEVICE_STATUS_VOLUME_ERROR);
             *count = got;
-            return -1;
+            return RESULT_ERROR;
         }
     }
 
@@ -1226,6 +1463,7 @@ static IoResult vfs_device_robust_read(VfsDevice * self, char *buf,
 static IoResult
 vfs_device_robust_write(VfsDevice * self,  char *buf, int count) {
     int fd = self->open_file_fd;
+    Device *d_self = DEVICE(self);
     int rval = 0;
 
     while (rval < count) {
@@ -1256,14 +1494,19 @@ vfs_device_robust_write(VfsDevice * self,  char *buf, int count) {
 #endif
                    ) {
             /* We are definitely out of space. */
+           device_set_error(d_self,
+                   vstrallocf(_("No space left on device: %s"), strerror(errno)),
+                   DEVICE_STATUS_VOLUME_ERROR);
             return RESULT_NO_SPACE;
         } else {
             /* Error occured. Note that here we handle EIO as an error. */
-            g_fprintf(stderr, "Error writing device fd %d: %s\n",
-                    fd, strerror(errno));
-            
+           device_set_error(d_self,
+                   vstrallocf(_("Error writing device fd %d: %s"), fd, strerror(errno)),
+                   DEVICE_STATUS_VOLUME_ERROR);
             return RESULT_ERROR;
         }
     }
     return RESULT_SUCCESS;
 }
+
+/* TODO: add prop */