Imported Upstream version 3.3.1
[debian/amanda] / device-src / vfs-device.c
index 667c5489bc236e170b0f4f8a23fd3427d400bea2..122a6efb8e479cdf21713b93c31a13af2d9e5d7c 100644 (file)
@@ -1,69 +1,30 @@
 /*
- * 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, 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., 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 <string.h> /* memset() */
 #include "fsusage.h"
 #include "util.h"
-#include "device.h"
 #include <regex.h>
 
-/*
- * Type checking and casting macros
- */
-#define TYPE_VFS_DEVICE        (vfs_device_get_type())
-#define VFS_DEVICE(obj)        G_TYPE_CHECK_INSTANCE_CAST((obj), vfs_device_get_type(), VfsDevice)
-#define VFS_DEVICE_CONST(obj)  G_TYPE_CHECK_INSTANCE_CAST((obj), vfs_device_get_type(), VfsDevice const)
-#define VFS_DEVICE_CLASS(klass)        G_TYPE_CHECK_CLASS_CAST((klass), vfs_device_get_type(), VfsDeviceClass)
-#define IS_VFS_DEVICE(obj)     G_TYPE_CHECK_INSTANCE_TYPE((obj), vfs_device_get_type ())
-
-#define VFS_DEVICE_GET_CLASS(obj)      G_TYPE_INSTANCE_GET_CLASS((obj), vfs_device_get_type(), VfsDeviceClass)
-static GType   vfs_device_get_type     (void);
-
-/*
- * Main object structure
- */
-typedef struct {
-    Device __parent__;
-
-    /*< private >*/
-    char * dir_name;
-    char * file_name;
-    int file_lock_fd;
-    char * file_lock_name;
-    int volume_lock_fd;
-    char * volume_lock_name;
-    int open_file_fd;
-
-    /* Properties */
-    guint64 volume_bytes;
-    guint64 volume_limit;
-} VfsDevice;
-
-/*
- * Class definition
- */
-typedef struct {
-    DeviceClass __parent__;
-} VfsDeviceClass;
-
+#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
@@ -79,6 +40,14 @@ typedef struct {
 #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
 
@@ -107,10 +76,11 @@ 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 * pself);
+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_recycle_file (Device * pself, guint filenum);
+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);
@@ -122,21 +92,29 @@ static IoResult vfs_device_robust_read(VfsDevice * self, char *buf,
 
 /* Various helper functions. */
 static void release_file(VfsDevice * self);
-static gboolean check_is_dir(Device * d_self, const char * name);
-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);
-gboolean vfs_device_get_free_space_fn(struct Device *p_self,
+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 void 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,
@@ -151,15 +129,32 @@ static gboolean get_last_file_number_functor(const char * filename,
 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);
 }
 
-static GType
+GType
 vfs_device_get_type (void)
 {
     static GType type = 0;
@@ -181,20 +176,26 @@ 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 * dself = DEVICE(self);
     GValue response;
 
     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->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 */
     bzero(&response, sizeof(response));
@@ -223,6 +224,24 @@ vfs_device_init (VfsDevice * self) {
            &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_FULL_DELETION,
+           &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_LEOM,
+           &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_ENFORCE_MAX_VOLUME_USAGE,
+           &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(dself, PROPERTY_COMPRESSION,
@@ -236,11 +255,11 @@ vfs_device_init (VfsDevice * self) {
     g_value_unset(&response);
 }
 
-static void 
+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);
 
@@ -254,7 +273,9 @@ vfs_device_class_init (VfsDeviceClass * c)
     device_class->seek_file = vfs_device_seek_file;
     device_class->seek_block = vfs_device_seek_block;
     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;
 }
 
@@ -263,10 +284,10 @@ vfs_device_base_init (VfsDeviceClass * c)
 {
     DeviceClass *device_class = (DeviceClass *)c;
 
-    device_class_register_property(device_class, PROPERTY_FREE_SPACE,
-           PROPERTY_ACCESS_GET_MASK,
-           vfs_device_get_free_space_fn,
-           NULL);
+    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) &
@@ -274,13 +295,25 @@ vfs_device_base_init (VfsDeviceClass * c)
            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);
 }
 
-gboolean
+static gboolean
 vfs_device_set_max_volume_usage_fn(Device *p_self,
     DevicePropertyBase *base, GValue *val,
     PropertySurety surety, PropertySource source)
@@ -292,58 +325,69 @@ vfs_device_set_max_volume_usage_fn(Device *p_self,
     return device_simple_property_set_fn(p_self, base, val, surety, source);
 }
 
-gboolean
-vfs_device_get_free_space_fn(struct Device *p_self,
-    DevicePropertyBase *base G_GNUC_UNUSED, 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)
 {
     VfsDevice *self = VFS_DEVICE(p_self);
-    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;
-       if (surety)
-           *surety = PROPERTY_SURETY_GOOD;
-    } else {
-       g_warning(_("get_fs_usage('%s') failed: %s"), self->dir_name, strerror(errno));
-       qsize.accuracy = SIZE_ACCURACY_UNKNOWN;
-       qsize.bytes = 0;
-       if (surety)
-           *surety = PROPERTY_SURETY_BAD;
-    }
 
-    g_value_unset_init(val, QUALIFIED_SIZE_TYPE);
-    g_value_set_boxed(val, &qsize);
+    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_DETECTED;
+       *source = PROPERTY_SOURCE_DEFAULT;
 
     return TRUE;
 }
 
-/* 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 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) {
@@ -360,13 +404,6 @@ static void vfs_device_finalize(GObject * obj_self) {
     amfree(self->dir_name);
 
     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_name, char * device_type, char * device_node) {
@@ -377,21 +414,22 @@ static Device * vfs_device_factory(char * device_name, char * device_type, char
     return rval;
 }
 
-static gboolean check_is_dir(Device * d_self, const char * name) {
+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(d_self, name);
+            return check_is_dir(self, name);
         }
 #endif /* EINTR */
-       device_set_error(d_self,
+       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)) {
-       device_set_error(d_self,
+       device_set_error(dself,
                    vstrallocf(_("VFS Device path %s is not a directory"), name),
                    DEVICE_STATUS_DEVICE_ERROR);
         return FALSE;
@@ -412,9 +450,9 @@ 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)) {
@@ -486,62 +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;
-    Device *d_self = DEVICE(self);
-    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) {
-       device_set_error(d_self,
-           vstrallocf(_("Can't open lock file %s: %s"), name, strerror(errno)),
-           DEVICE_STATUS_DEVICE_ERROR);
-        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 */
@@ -549,7 +538,7 @@ static gboolean update_volume_size_functor(const char * filename,
                                            gpointer user_data) {
     char * full_filename;
     struct stat stat_buf;
-    VfsDevice * self = user_data;
+    VfsDevice * self = VFS_DEVICE(user_data);
 
     full_filename = vstralloc(self->dir_name, "/", filename, NULL);
 
@@ -567,6 +556,7 @@ static gboolean update_volume_size_functor(const char * filename,
 }
 
 static void update_volume_size(VfsDevice * self) {
+
     self->volume_bytes = 0;
     search_vfs_directory(self, "^[0-9]+\\.",
                          update_volume_size_functor, self);
@@ -595,11 +585,9 @@ vfs_device_open_device (Device * pself, char * device_name, char * device_type,
 static gboolean delete_vfs_files_functor(const char * filename,
                                          gpointer user_data) {
     VfsDevice * self;
-    Device * d_self;
     char * path_name;
 
     self = VFS_DEVICE(user_data);
-    d_self = DEVICE(self);
 
     /* Skip the volume lock. */
     if (strcmp(filename, VOLUME_LOCKFILE_NAME) == 0)
@@ -615,7 +603,7 @@ static gboolean delete_vfs_files_functor(const char * filename,
 
 /* delete_vfs_files deletes all VfsDevice files in the directory except the
    volume lockfile. */
-static void delete_vfs_files(VfsDevice * self) {
+void delete_vfs_files(VfsDevice * self) {
     g_assert(self != NULL);
 
     /* This function assumes that the volume is locked! */
@@ -627,12 +615,8 @@ static void delete_vfs_files(VfsDevice * self) {
    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;
-    Device *d_self;
-
-    self = VFS_DEVICE(user_data);
-    d_self = DEVICE(self);
 
     if (strcmp(filename, VOLUME_LOCKFILE_NAME) == 0)
         return TRUE;
@@ -654,8 +638,8 @@ static gboolean write_amanda_header(VfsDevice * self,
 
     g_assert(header != NULL);
 
-    label_buffer = build_header(header, VFS_DEVICE_LABEL_SIZE);
-    if (strlen(label_buffer)+1 > VFS_DEVICE_LABEL_SIZE) {
+    label_buffer = device_build_amanda_header(d_self, header, NULL);
+    if (!label_buffer) {
         amfree(label_buffer);
        device_set_error(d_self,
            stralloc(_("Amanda file header won't fit in a single block!")),
@@ -701,10 +685,12 @@ static gboolean clear_and_prepare_label(VfsDevice * self, char * label,
     label_header = make_tapestart_header(DEVICE(self), label, timestamp);
     if (!write_amanda_header(self, label_header)) {
        /* write_amanda_header sets error status if necessary */
-        amfree(label_header);
+        dumpfile_free(label_header);
         return FALSE;
     }
-    amfree(label_header);
+    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;
 }
@@ -741,24 +727,25 @@ error:
 }
 
 static DeviceStatusFlags vfs_device_read_label(Device * dself) {
+    VfsDevice * self = VFS_DEVICE(dself);
     dumpfile_t * amanda_header;
-    VfsDevice * self;
 
-    self = VFS_DEVICE(dself);
     g_assert(self != NULL);
 
-    if (!check_is_dir(dself, self->dir_name)) {
+    if (!check_is_dir(self, self->dir_name)) {
        /* error message set by check_is_dir */
-        return FALSE;
+        return dself->status;
     }
 
     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;
+    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. */
@@ -770,7 +757,11 @@ static DeviceStatusFlags vfs_device_read_label(Device * dself) {
        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. */
        device_set_error(dself,
                stralloc(_("Got a bad volume label")),
@@ -779,11 +770,13 @@ static DeviceStatusFlags vfs_device_read_label(Device * dself) {
         return dself->status;
     }
 
-    dself->volume_label = g_strdup(amanda_header->name);
-    dself->volume_time = g_strdup(amanda_header->datestamp);
-    /* dself->volume_header is already set */
+    /* self->volume_header is already set */
 
-    device_set_error(dself, NULL, DEVICE_STATUS_SUCCESS);
+    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);
 
@@ -798,14 +791,15 @@ static gboolean vfs_device_write_block(Device * pself, guint size, gpointer data
 
     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;
+    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;
+       return FALSE;
     }
 
     result = vfs_device_robust_write(self, data, size);
@@ -815,6 +809,7 @@ static gboolean vfs_device_write_block(Device * pself, guint size, gpointer data
     }
 
     self->volume_bytes += size;
+    self->checked_bytes_used += size;
     pself->block ++;
 
     return TRUE;
@@ -825,7 +820,7 @@ vfs_device_read_block(Device * pself, gpointer data, int * size_req) {
     VfsDevice * self;
     int size;
     IoResult result;
-    
+
     self = VFS_DEVICE(pself);
 
     if (device_in_error(self)) return -1;
@@ -861,18 +856,18 @@ vfs_device_read_block(Device * pself, gpointer data, int * size_req) {
     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);
+    VfsDevice * self = VFS_DEVICE(dself);
 
-    if (!check_is_dir(pself, self->dir_name)) {
+    if (!check_is_dir(self, self->dir_name)) {
        /* error message set by check_is_dir */
         return FALSE;
     }
 
-    pself->in_file = FALSE;
+    dself->in_file = FALSE;
 
     if (mode == ACCESS_WRITE) {
         promote_volume_lock(self);
@@ -882,25 +877,25 @@ static gboolean   vfs_device_start(Device * pself,
             return FALSE;
         }
 
-        pself->volume_label = newstralloc(pself->volume_label, label);
-        pself->volume_time = newstralloc(pself->volume_time, timestamp);
+        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(pself, NULL, DEVICE_STATUS_SUCCESS);
+       device_set_error(dself, NULL, DEVICE_STATUS_SUCCESS);
 
         demote_volume_lock(self);
-        pself->access_mode = mode;
+        dself->access_mode = mode;
     } else {
-       if (pself->volume_label == NULL && device_read_label(pself) != DEVICE_STATUS_SUCCESS) {
+       if (dself->volume_label == NULL && device_read_label(dself) != DEVICE_STATUS_SUCCESS) {
            /* device_read_label already set our error message */
             return FALSE;
        } else {
-            pself->access_mode = mode;
+            dself->access_mode = mode;
         }
     }
 
     release_file(self);
+
     return TRUE;
 }
 
@@ -909,9 +904,13 @@ vfs_device_finish (Device * pself) {
     VfsDevice * self;
     self = VFS_DEVICE(pself);
 
-    if (device_in_error(self)) return FALSE;
+    release_file(self);
 
     pself->access_mode = ACCESS_NULL;
+    pself->in_file = FALSE;
+
+    if (device_in_error(self)) return FALSE;
+
     return TRUE;
 }
 
@@ -938,13 +937,14 @@ 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_vfs_directory(self, "^[0-9]+\\.",
                                  get_last_file_number_functor, &data);
 
@@ -957,7 +957,7 @@ static gint get_last_file_number(VfsDevice * self) {
     } else {
         g_assert(data.rval >= 0);
     }
-    
+
     return data.rval;
 }
 
@@ -988,14 +988,15 @@ 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_vfs_directory(self, "^[0-9]+\\.",
                                  get_next_file_number_functor, &data);
 
@@ -1006,7 +1007,7 @@ static gint get_next_file_number(VfsDevice * self, guint request) {
            DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);
         return -1;
     }
-    
+
     /* Could be -1. */
     return data.best_found;
 }
@@ -1022,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 {
@@ -1042,10 +1043,11 @@ char * make_new_file_name(VfsDevice * self, const dumpfile_t * ji) {
     return rval;
 }
 
-static gboolean 
-vfs_device_start_file (Device * pself, dumpfile_t * ji) {
-    VfsDevice * self;
-    self = VFS_DEVICE(pself);
+static gboolean
+vfs_device_start_file (Device * dself, dumpfile_t * ji) {
+    VfsDevice * self = VFS_DEVICE(dself);
+
+    dself->is_eom = FALSE;
 
     if (device_in_error(self)) return FALSE;
 
@@ -1053,12 +1055,15 @@ vfs_device_start_file (Device * pself, dumpfile_t * ji) {
      * 32k regardless of the block_size setting */
     ji->blocksize = 32768;
 
-    if (self->volume_limit > 0 &&
-        self->volume_bytes + VFS_DEVICE_LABEL_SIZE > self->volume_limit) {
-       device_set_error(pself,
+    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;
+       return FALSE;
     }
 
     /* The basic idea here is thus:
@@ -1070,7 +1075,7 @@ vfs_device_start_file (Device * pself, dumpfile_t * ji) {
 
     self->file_name = make_new_file_name(self, ji);
     if (self->file_name == NULL) {
-       device_set_error(pself,
+       device_set_error(dself,
                stralloc(_("Could not create header filename")),
                DEVICE_STATUS_DEVICE_ERROR);
         return FALSE;
@@ -1080,14 +1085,14 @@ vfs_device_start_file (Device * pself, dumpfile_t * ji) {
                                      O_CREAT | O_EXCL | O_RDWR,
                                      VFS_DEVICE_CREAT_MODE);
     if (self->open_file_fd < 0) {
-       device_set_error(pself,
+       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);
@@ -1096,23 +1101,24 @@ vfs_device_start_file (Device * pself, dumpfile_t * ji) {
 
     /* handle some accounting business */
     self->volume_bytes += VFS_DEVICE_LABEL_SIZE;
-    pself->in_file = TRUE;
-    pself->block = 0;
+    self->checked_bytes_used += VFS_DEVICE_LABEL_SIZE;
+    dself->in_file = TRUE;
+    dself->block = 0;
     /* make_new_file_name set pself->file for us */
 
     return TRUE;
 }
 
-static gboolean 
-vfs_device_finish_file (Device * pself) {
-    VfsDevice * self;
-    self = VFS_DEVICE(pself);
+static gboolean
+vfs_device_finish_file(Device * dself) {
+    VfsDevice * self = VFS_DEVICE(dself);
 
     if (device_in_error(self)) return FALSE;
 
     release_file(self);
 
-    pself->in_file = FALSE;
+    dself->in_file = FALSE;
+
     return TRUE;
 }
 
@@ -1120,23 +1126,21 @@ vfs_device_finish_file (Device * pself) {
  * 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);
-
     if (device_in_error(self)) return NULL;
 
-    pself->in_file = FALSE;
-    pself->is_eof = FALSE;
-    pself->block = 0;
-    
+    dself->in_file = FALSE;
+    dself->is_eof = FALSE;
+    dself->block = 0;
+
     release_file(self);
 
     if (requested_file > 0) {
@@ -1151,10 +1155,10 @@ 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);
-           pself->file = requested_file; /* other attributes are already correct */
+           dself->file = requested_file; /* other attributes are already correct */
             return make_tapeend_header();
         } else {
-           device_set_error(pself,
+           device_set_error(dself,
                stralloc(_("Attempt to read past tape-end file")),
                DEVICE_STATUS_SUCCESS);
             return NULL;
@@ -1162,7 +1166,7 @@ vfs_device_seek_file (Device * pself, guint requested_file) {
     }
 
     if (!open_lock(self, file, FALSE)) {
-       device_set_error(pself,
+       device_set_error(dself,
            stralloc(_("could not acquire lock")),
            DEVICE_STATUS_DEVICE_ERROR);
         return NULL;
@@ -1170,16 +1174,19 @@ vfs_device_seek_file (Device * pself, guint requested_file) {
 
     self->file_name = file_number_to_file_name(self, file);
     if (self->file_name == NULL) {
-       device_set_error(pself,
+       device_set_error(dself,
            vstrallocf(_("File %d not found"), file),
-           DEVICE_STATUS_VOLUME_ERROR);
+           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) {
-       device_set_error(pself,
+       device_set_error(dself,
            vstrallocf(_("Couldn't open file %s: %s"), self->file_name, strerror(errno)),
            DEVICE_STATUS_DEVICE_ERROR);
         amfree(self->file_name);
@@ -1190,8 +1197,8 @@ vfs_device_seek_file (Device * pself, guint requested_file) {
     result = vfs_device_robust_read(self, header_buffer,
                                     &header_buffer_size);
     if (result != RESULT_SUCCESS) {
-       device_set_error(pself,
-           vstrallocf(_("Problem reading Amanda header: %s"), device_error(pself)),
+       device_set_error(dself,
+           vstrallocf(_("Problem reading Amanda header: %s"), device_error(dself)),
            DEVICE_STATUS_VOLUME_ERROR);
         release_file(self);
         return NULL;
@@ -1213,7 +1220,7 @@ vfs_device_seek_file (Device * pself, guint requested_file) {
            /* FALLTHROUGH */
 
         default:
-           device_set_error(pself,
+           device_set_error(dself,
                stralloc(_("Invalid amanda header while reading file header")),
                DEVICE_STATUS_VOLUME_ERROR);
             amfree(rval);
@@ -1222,13 +1229,16 @@ vfs_device_seek_file (Device * pself, guint requested_file) {
     }
 
     /* update our state */
-    pself->in_file = TRUE;
-    pself->file = file;
+    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;
@@ -1264,14 +1274,80 @@ static gboolean try_unlink(const char * file) {
     }
 }
 
-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);
-
     if (device_in_error(self)) return FALSE;
 
     /* Game Plan:
@@ -1284,46 +1360,57 @@ vfs_device_recycle_file (Device * pself, guint filenum) {
 
     self->file_name = file_number_to_file_name(self, filenum);
     if (self->file_name == NULL) {
-       device_set_error(pself,
+       device_set_error(dself,
            vstrallocf(_("File %d not found"), filenum),
            DEVICE_STATUS_VOLUME_ERROR);
         return FALSE;
     }
 
     if (!open_lock(self, filenum, FALSE)) {
-       device_set_error(pself,
+       device_set_error(dself,
            stralloc(_("could not acquire lock")),
            DEVICE_STATUS_DEVICE_ERROR);
         return FALSE;
     }
 
     if (0 != stat(self->file_name, &file_status)) {
-       device_set_error(pself,
+       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)) {
-       device_set_error(pself,
+       device_set_error(dself,
            vstrallocf(_("Unlink of %s failed: %s"), self->file_name, strerror(errno)),
            DEVICE_STATUS_VOLUME_ERROR);
         release_file(self);
         return FALSE;
     }
 
-    if (!try_unlink(self->file_lock_name)) {
-       device_set_error(pself,
-           vstrallocf(_("Unlink of %s failed: %s"), self->file_lock_name, strerror(errno)),
-           DEVICE_STATUS_VOLUME_ERROR);
-        release_file(self);
+    self->volume_bytes -= file_size;
+    release_file(self);
+    return TRUE;
+}
+
+static gboolean
+vfs_device_erase (Device * dself) {
+    VfsDevice *self = VFS_DEVICE(dself);
+
+    if (!open_lock(self, 0, TRUE))
         return FALSE;
-    }
 
-    self->volume_bytes -= file_size;
+    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;
 }
 
@@ -1421,3 +1508,5 @@ vfs_device_robust_write(VfsDevice * self,  char *buf, int count) {
     }
     return RESULT_SUCCESS;
 }
+
+/* TODO: add prop */