/*
 * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
 * Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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 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: Carbonite Inc., 756 N Pastoria Ave
 * Sunnyvale, CA 94085, or: http://www.zmanda.com
 */

#include "amanda.h"
#include <string.h> /* memset() */
#include "fsusage.h"
#include "amutil.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. */
#define VFS_DEVICE_FILE_REGEX "^[0-9]+[\\.-]"

/* The name of the volume lockfile. Should be the same as that
   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)

/* 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

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 * dself, DeviceAccessMode mode,
                                 char * label, char * timestamp);
static gboolean vfs_device_finish (Device * dself);
static void vfs_device_open_device (Device * dself, char * device_name,
				char * device_type, char * device_node);
static gboolean vfs_device_start_file (Device * dself, 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_recycle_file (Device * dself, guint filenum);
static gboolean vfs_device_erase (Device * dself);
static Device * vfs_device_factory(char * device_name, char * device_type, char * device_node);
static DeviceStatusFlags vfs_device_read_label(Device * dself);
static DeviceWriteResult vfs_device_write_block(Device * self, guint size,
						gpointer data);
static int vfs_device_read_block(Device * self, gpointer data, int * size_req, int max_block);

/* Various helper functions. */
static gboolean vfs_clear_and_prepare_label(Device *dself, char *label,
	                                    char *timestamp);
static void vfs_release_file(Device *dself);
static void vfs_update_volume_size(Device *dself);
static gboolean vfs_device_start_file_open(Device *dself, dumpfile_t *ji);
static gboolean vfs_validate(Device *dself);

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 *dself,
			    DevicePropertyBase *base, GValue *val,
			    PropertySurety surety, PropertySource source);
static gboolean vfs_device_set_enforce_max_volume_usage_fn(Device *dself,
			    DevicePropertyBase *base, GValue *val,
			    PropertySurety surety, PropertySource source);
static gboolean property_get_monitor_free_space_fn(Device *dself,
			    DevicePropertyBase *base, GValue *val,
			    PropertySurety *surety, PropertySource *source);
static gboolean property_set_monitor_free_space_fn(Device *dself,
			    DevicePropertyBase *base, GValue *val,
			    PropertySurety surety, PropertySource source);
static gboolean property_get_slow_write_fn(Device *dself,
			    DevicePropertyBase *base, GValue *val,
			    PropertySurety *surety, PropertySource *source);
static gboolean property_set_slow_write_fn(Device *dself,
			    DevicePropertyBase *base, GValue *val,
			    PropertySurety surety, PropertySource source);
static gboolean property_get_use_data_fn(Device *dself,
			    DevicePropertyBase *base, GValue *val,
			    PropertySurety *surety, PropertySource *source);
static gboolean property_set_use_data_fn(Device *dself,
			    DevicePropertyBase *base, GValue *val,
			    PropertySurety surety, PropertySource source);
static gboolean property_set_leom_fn(Device *dself,
			    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_functor(const char * filename,
                                         gpointer self);
static gboolean check_dir_empty_functor(const char * filename,
                                        gpointer self);
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)

DevicePropertyBase device_property_slow_write;
#define PROPERTY_SLOW_WRITE (device_property_slow_write.ID)

DevicePropertyBase device_property_use_data;
#define PROPERTY_USE_DATA (device_property_use_data.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?");
    device_property_fill_and_register(&device_property_slow_write,
                                      G_TYPE_BOOLEAN, "slow_write",
      "Write 5 blocks by second, use for testing only");
    device_property_fill_and_register(&device_property_use_data,
                                      G_TYPE_STRING, "use_data",
      "Should VFS device use the data subdir?");

    register_device(vfs_device_factory, device_prefix_list);
}

GType
vfs_device_get_type (void)
{
    static GType type = 0;

    if (G_UNLIKELY(type == 0)) {
        static const GTypeInfo info = {
            sizeof (VfsDeviceClass),
            (GBaseInitFunc) vfs_device_base_init,
            (GBaseFinalizeFunc) NULL,
            (GClassInitFunc) vfs_device_class_init,
            (GClassFinalizeFunc) NULL,
            NULL /* class_data */,
            sizeof (VfsDevice),
            0 /* n_preallocs */,
            (GInstanceInitFunc) vfs_device_init,
            NULL
        };

        type = g_type_register_static (TYPE_DEVICE, "VfsDevice",
                                       &info, (GTypeFlags)0);
    }

    return type;
}

static void
vfs_device_init (
    VfsDevice *self)
{
    Device *dself = DEVICE(self);
    GValue response;

    self->dir_name = self->file_name = NULL;
    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->slow_write = FALSE;
    self->slow_count = 0;
    self->use_data = 2;
    self->checked_fs_free_bytes = G_MAXUINT64;
    self->checked_fs_free_time = 0;
    self->checked_fs_free_bytes = G_MAXUINT64;
    self->clear_and_prepare_label = &vfs_clear_and_prepare_label;
    self->release_file = &vfs_release_file;
    self->update_volume_size = &vfs_update_volume_size;
    self->device_start_file_open = &vfs_device_start_file_open;
    self->validate = &vfs_validate;

    /* Register Properties */
    bzero(&response, sizeof(response));

    g_value_init(&response, CONCURRENCY_PARADIGM_TYPE);
    g_value_set_enum(&response, CONCURRENCY_PARADIGM_RANDOM_ACCESS);
    device_set_simple_property(dself, PROPERTY_CONCURRENCY,
	    &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
    g_value_unset(&response);

    g_value_init(&response, STREAMING_REQUIREMENT_TYPE);
    g_value_set_enum(&response, STREAMING_REQUIREMENT_NONE);
    device_set_simple_property(dself, PROPERTY_STREAMING,
	    &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_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);

    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,
	    &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
    g_value_unset(&response);

    g_value_init(&response, MEDIA_ACCESS_MODE_TYPE);
    g_value_set_enum(&response, MEDIA_ACCESS_MODE_READ_WRITE);
    device_set_simple_property(dself, PROPERTY_MEDIUM_ACCESS_TYPE,
	    &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED);
    g_value_unset(&response);
}

static void
vfs_device_class_init(
    VfsDeviceClass *c)
{
    GObjectClass *g_object_class = (GObjectClass*) c;
    DeviceClass *device_class = DEVICE_CLASS(c);

    parent_class = g_type_class_ref(TYPE_DEVICE);

    device_class->open_device = vfs_device_open_device;
    device_class->start = vfs_device_start;
    device_class->start_file = vfs_device_start_file;
    device_class->read_label = vfs_device_read_label;
    device_class->write_block = vfs_device_write_block;
    device_class->read_block = vfs_device_read_block;
    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->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;
}

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_SLOW_WRITE,
	    PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK,
	    property_get_slow_write_fn,
	    property_set_slow_write_fn);

    device_class_register_property(device_class, PROPERTY_USE_DATA,
	    PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK,
	    property_get_use_data_fn,
	    property_set_use_data_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 *dself,
    DevicePropertyBase *base,
    GValue *val,
    PropertySurety surety,
    PropertySource source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    self->volume_limit = g_value_get_uint64(val);

    return device_simple_property_set_fn(dself, base, val, surety, source);
}

static gboolean
vfs_device_set_enforce_max_volume_usage_fn(
    Device *dself,
    DevicePropertyBase *base,
    GValue *val,
    PropertySurety surety,
    PropertySource source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    self->enforce_volume_limit = g_value_get_boolean(val);

    return device_simple_property_set_fn(dself, base, val, surety, source);
}

static gboolean
property_get_monitor_free_space_fn(
    Device *dself,
    DevicePropertyBase *base G_GNUC_UNUSED,
    GValue *val,
    PropertySurety *surety,
    PropertySource *source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    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 *dself,
    DevicePropertyBase *base,
    GValue *val,
    PropertySurety surety,
    PropertySource source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    self->monitor_free_space = g_value_get_boolean(val);

    return device_simple_property_set_fn(dself, base, val, surety, source);
}

static gboolean
property_get_slow_write_fn(
    Device *dself,
    DevicePropertyBase *base G_GNUC_UNUSED,
    GValue *val,
    PropertySurety *surety,
    PropertySource *source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    g_value_unset_init(val, G_TYPE_BOOLEAN);
    g_value_set_boolean(val, self->slow_write);

    if (surety)
	*surety = PROPERTY_SURETY_GOOD;

    if (source)
	*source = PROPERTY_SOURCE_DEFAULT;

    return TRUE;
}


static gboolean
property_set_slow_write_fn(
    Device *dself,
    DevicePropertyBase *base,
    GValue *val,
    PropertySurety surety,
    PropertySource source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    self->slow_write = g_value_get_boolean(val);

    return device_simple_property_set_fn(dself, base, val, surety, source);
}

static gboolean
property_get_use_data_fn(
    Device *dself,
    DevicePropertyBase *base G_GNUC_UNUSED,
    GValue *val,
    PropertySurety *surety,
    PropertySource *source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    g_value_unset_init(val, G_TYPE_STRING);
    switch (self->use_data) {
	case 0: g_value_set_string(val, "NO"); break;
	case 1: g_value_set_string(val, "YES"); break;
	case 2: g_value_set_string(val, "EXIST"); break;
    }
    if (surety)
	*surety = PROPERTY_SURETY_GOOD;

    if (source)
	*source = PROPERTY_SOURCE_DEFAULT;

    return TRUE;
}


static gboolean
property_set_use_data_fn(
    Device *dself,
    DevicePropertyBase *base,
    GValue *val,
    PropertySurety surety,
    PropertySource source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    const char *value = g_value_get_string(val);

    if (g_strcasecmp(value, "NO") == 0 ||
	g_strcasecmp(value, "FALSE") == 0) {
	self->use_data = 0;
    } else if (g_strcasecmp(value, "YES") == 0 ||
	g_strcasecmp(value, "TRUE") == 0) {
	self->use_data = 1;
    } else if (g_strcasecmp(value, "EXIST") == 0) {
	self->use_data = 2;
    } else {
	g_warning(_("Illegal USE-DATA value (%s), using 'EXIST'."), value);
    }

    return device_simple_property_set_fn(dself, base, val, surety, source);
}

static gboolean
property_set_leom_fn(
    Device *dself,
    DevicePropertyBase *base,
    GValue *val,
    PropertySurety surety,
    PropertySource source)
{
    VfsDevice *self = VFS_DEVICE(dself);

    self->leom = g_value_get_boolean(val);

    return device_simple_property_set_fn(dself, base, val, surety, source);
}

/* Drops everything associated with the volume file: Its name and fd. */
void
vfs_release_file(
    Device *dself)
{
    VfsDevice *self = VFS_DEVICE(dself);

    /* Doesn't hurt. */
    if (self->open_file_fd != -1) {
	robust_close(self->open_file_fd);
	self->open_file_fd = -1;
    }
    amfree(self->file_name);

}

static void vfs_device_finalize(
    GObject *obj_self)
{
    VfsDevice *self = VFS_DEVICE (obj_self);
    Device    *dself = (Device*)self;

    if (dself->access_mode != ACCESS_NULL) {
        device_finish(dself);
    }

    if(G_OBJECT_CLASS(parent_class)->finalize)
        (* G_OBJECT_CLASS(parent_class)->finalize)(obj_self);

    amfree(self->dir_name);

    self->release_file(dself);
}

static Device *vfs_device_factory(
    char *device_name,
    char *device_type,
    char *device_node)
{
    Device * rval;
    g_assert(g_str_equal(device_type, "file"));
    rval = DEVICE(g_object_new(TYPE_VFS_DEVICE, NULL));
    device_open_device(rval, device_name, device_type, device_node);
    return rval;
}

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(self, name);
        }
#endif /* EINTR */
	device_set_error(dself,
	    g_strdup_printf(_("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(dself,
		    g_strdup_printf(_("VFS Device path %s is not a directory"), name),
		    DEVICE_STATUS_DEVICE_ERROR);
        return FALSE;
    } else {
        return TRUE;
    }
}

typedef struct {
    VfsDevice * self;
    int count;
    char * result;
} fnfn_data;

/* A SearchDirectoryFunctor. */
static gboolean
file_number_to_file_name_functor(const char *filename,
                                 gpointer datap) {
    char *result_tmp;
    struct stat file_status;
    fnfn_data *data = (fnfn_data*)datap;

    result_tmp = g_strjoin(NULL, 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_warning(_("Cannot stat file %s (%s), ignoring it"), result_tmp, strerror(errno));
    } else if (!S_ISREG(file_status.st_mode)) {
	g_warning(_("%s is not a regular file, ignoring it"), result_tmp);
    } else {
        data->count ++;
        if (data->result == NULL) {
            data->result = result_tmp;
            result_tmp = NULL;
        }
    }
    amfree(result_tmp);
    return TRUE;
}

/* This function finds the filename for a given file number. We search
 * for a filesystem file matching the regex /^0*$device_file\./; if
 * there is more than one such file we make a warning and take an
 * arbitrary one. */
static char *
file_number_to_file_name(
    VfsDevice *self,
    guint device_file)
{
    char *regex;
    fnfn_data data;

    data.self = self;
    data.count = 0;
    data.result = NULL;

    regex = g_strdup_printf("^0*%u\\.", device_file);

    search_vfs_directory(self, regex,
                         file_number_to_file_name_functor, &data);

    amfree(regex);

    if (data.count == 0) {
        g_assert(data.result == NULL);
        return NULL;
    } else if (data.count > 1) {
	g_warning("Found multiple names for file number %d, choosing file %s",
                device_file, data.result);
        return data.result;
    } else {
        g_assert(data.result != NULL);
        return data.result;
    }
    g_assert_not_reached();
}

/* This function returns the dynamically-allocated lockfile name for a
   given file number. */
/*
static char * lockfile_name(VfsDevice * self, guint number) {
    return g_strdup_printf("%s/%05d-lock", self->dir_name, number);
}
*/

/* Does what you expect. If the lock already exists, it is released
 * and regained, in case the mode is changing.
 * The file field has several options:
 * - file > 0: Open a lock on a real volume file.
 * - file = 0: Open the volume lock as a volume file (for setup).
 * - file < 0: Open the volume lock as a volume lock (persistantly).
 */
static gboolean open_lock(G_GNUC_UNUSED VfsDevice * self,
			  G_GNUC_UNUSED int file,
			  G_GNUC_UNUSED gboolean exclusive)
{
    /* At the moment, file locking is horribly broken. */
    return TRUE;
}

/* For now, does it the bad way. */
static void promote_volume_lock(VfsDevice * self G_GNUC_UNUSED) {
}

static void demote_volume_lock(VfsDevice * self G_GNUC_UNUSED) {
}

/* A SearchDirectoryFunctor */
static gboolean
update_volume_size_functor(const char *filename,
                           gpointer user_data)
{
    char *full_filename;
    struct stat stat_buf;
    VfsDevice *self = VFS_DEVICE(user_data);

    full_filename = g_strjoin(NULL, self->dir_name, "/", filename, NULL);

    if (stat(full_filename, &stat_buf) < 0) {
        /* Log it and keep going. */
	g_warning(_("Couldn't stat file %s: %s"), full_filename, strerror(errno));
        amfree(full_filename);
        return TRUE;
    }

    amfree(full_filename);
    self->volume_bytes += stat_buf.st_size;

    return TRUE;
}

static void
vfs_update_volume_size(
    Device *dself)
{
    VfsDevice *self = VFS_DEVICE(dself);
    self->volume_bytes = 0;
    search_vfs_directory(self, "^[0-9]+\\.",
                         update_volume_size_functor, self);

}

static void
vfs_device_open_device(
    Device *dself,
    char   *device_name,
    char   *device_type,
    char   *device_node)
{
    VfsDevice *self = VFS_DEVICE(dself);
    struct stat dir_status;

    dself->min_block_size = VFS_DEVICE_MIN_BLOCK_SIZE;
    dself->max_block_size = VFS_DEVICE_MAX_BLOCK_SIZE;
    dself->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. */
    switch (self->use_data) {
	case 0: self->dir_name = g_strconcat(device_node, "/", NULL);
		break;
	case 1: self->dir_name = g_strconcat(device_node, "/data/", NULL);
		break;
	case 2: self->dir_name = g_strconcat(device_node, "/data/", NULL);
		if (stat(self->dir_name, &dir_status) != 0) {
		    g_free(self->dir_name);
		    self->dir_name = g_strconcat(device_node, "/", NULL);
		}
		break;
    }
    g_debug("dir_name: %s", self->dir_name);

    if (parent_class->open_device) {
        parent_class->open_device(dself, device_name, device_type, device_node);
    }
}

/* A SearchDirectoryFunctor */
static gboolean
delete_vfs_files_functor(
    const char *filename,
    gpointer user_data)
{
    VfsDevice *self = VFS_DEVICE(user_data);
    Device *dself = DEVICE(self);
    char *path_name;
    gboolean ret = TRUE;

    /* Skip the volume lock. */
    if (g_str_equal(filename, VOLUME_LOCKFILE_NAME))
        return TRUE;

    path_name = g_strjoin(NULL, self->dir_name, "/", filename, NULL);
    if (unlink(path_name) != 0) {
	device_set_error(dself,
	    g_strdup_printf("Error unlinking %s: %s", path_name, strerror(errno)),
	    DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);
	ret = FALSE;
    }
    amfree(path_name);
    return ret;
}

/* delete_vfs_files deletes all VfsDevice files in the directory except the
   volume lockfile. */
void
delete_vfs_files(
    VfsDevice *self)
{
    g_assert(self != NULL);

    /* This function assumes that the volume is locked! */
    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 = VFS_DEVICE(user_data);
    Device *dself = DEVICE(self);
    char * path_name;

    if (g_str_equal(filename, VOLUME_LOCKFILE_NAME))
        return TRUE;

    path_name = g_strjoin(NULL, self->dir_name, "/", filename, NULL);

    device_set_error(dself,
	    g_strdup_printf("Found spurious storage file %s", path_name),
	    DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);

    amfree(path_name);
    return FALSE;
}

/* This function is used to write volume and dump headers. */
gboolean
vfs_write_amanda_header(
    VfsDevice *self,
    const dumpfile_t *header)
{
    char *label_buffer;
    IoResult result;
    Device *dself = DEVICE(self);

    g_assert(header != NULL);

    label_buffer = device_build_amanda_header(dself, header, NULL);
    if (!label_buffer) {
        amfree(label_buffer);
	device_set_error(dself,
	    g_strdup(_("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);
}

/* clear_and_label will erase the contents of the directory, and write
 * this label in its place. This function assumes we already have a volume
 * label write lock in place (e.g., promote_lock() has been called.) */
static gboolean
vfs_clear_and_prepare_label(
    Device *dself,
    char *label,
    char *timestamp)
{
    dumpfile_t *label_header;
    VfsDevice *self = VFS_DEVICE(dself);

    self->release_file(dself);

    /* Delete any extant data, except our volume lock. */
    delete_vfs_files(self);
    if (device_in_error(dself)) return FALSE;

    /* Print warnings about any remaining files. */
    search_vfs_directory(self, VFS_DEVICE_FILE_REGEX,
                         check_dir_empty_functor, self);
    if (device_in_error(dself)) return FALSE;

    self->file_name = g_strdup_printf("%s/00000.%s", self->dir_name, label);

    self->open_file_fd = robust_open(self->file_name,
                                     O_CREAT | O_EXCL | O_WRONLY,
                                     VFS_DEVICE_CREAT_MODE);
    if (self->open_file_fd < 0) {
	device_set_error(dself,
	    g_strdup_printf(_("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 (!vfs_write_amanda_header(self, label_header)) {
	/* vfs_write_amanda_header sets error status if necessary */
        dumpfile_free(label_header);
	aclose(self->open_file_fd);
        return FALSE;
    }
    dumpfile_free(dself->volume_header);
    dself->header_block_size = VFS_DEVICE_LABEL_SIZE;
    dself->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,
		g_strdup_printf(_("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 gboolean
vfs_validate(
    Device *dself)
{
    VfsDevice  *self = VFS_DEVICE(dself);
    return check_is_dir(self, self->dir_name);
}

static DeviceStatusFlags
vfs_device_read_label(
    Device *dself)
{
    VfsDevice  *self = VFS_DEVICE(dself);
    dumpfile_t *amanda_header;

    g_assert(self != NULL);
    g_assert(!dself->in_file);

    if (self->dir_name && !self->validate(dself)) {
	/* error message set by validate */
        return dself->status;
    }

    amfree(dself->volume_label);
    amfree(dself->volume_time);
    dumpfile_free(dself->volume_header);
    dself->volume_header = NULL;

    if (device_in_error(dself)) return dself->status;

    amanda_header = dself->volume_header = device_seek_file(dself, 0);
    self->release_file(dself);
    if (amanda_header == NULL) {
        /* This means an error occured getting locks or opening the header
         * file. */
//	device_set_error(dself,
//		g_strdup("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 &&
	amanda_header->type != F_EMPTY) {
        /* This is an error, and should not happen. */
	device_set_error(dself,
		g_strdup(_("Got a bad volume label")),
		DEVICE_STATUS_VOLUME_ERROR);
        amfree(amanda_header);
        return dself->status;
    }

    /* 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);
    }

    self->update_volume_size(dself);

    return dself->status;
}

static DeviceWriteResult
vfs_device_write_block(
    Device * dself,
    guint size,
    gpointer data)
{
    VfsDevice * self = VFS_DEVICE(dself);
    IoResult result;

    if (device_in_error(self)) return WRITE_FAILED;

    g_assert(self->open_file_fd >= 0);

    if (check_at_leom(self, size))
	dself->is_eom = TRUE;

    if (check_at_peom(self, size)) {
	/* check_at_peom() only checks against MAX_VOLUME_USAGE limit */
	DeviceWriteResult dwr = self->leom ? WRITE_FULL : WRITE_FAILED;
	dself->is_eom = TRUE;
	device_set_error(dself,
	    g_strdup(_("No space left on device: more than MAX_VOLUME_USAGE bytes written")),
	    DEVICE_STATUS_VOLUME_ERROR);
	if (fsync(self->open_file_fd) == -1) {
	    g_debug("fsync failed: %s", strerror(errno));
	    dwr = WRITE_FAILED;
	}
	return dwr;
    }

    if (self->slow_write) {
	self->slow_count++;
	if (self->slow_count >= 2) {
	    sleep(1);
	    self->slow_count = 0;
	}
    }

    result = vfs_device_robust_write(self, data, size);

    if (result == RESULT_NO_SPACE) {
	DeviceWriteResult dwr = self->leom ? WRITE_SPACE : WRITE_FAILED;
	if (ftruncate(self->open_file_fd,
		      dself->bytes_written + VFS_DEVICE_LABEL_SIZE) == -1) {
	    g_debug("ftruncate failed: %s", strerror(errno));
	    dwr = WRITE_FAILED;
	}
	if (lseek(self->open_file_fd,
		  dself->bytes_written + VFS_DEVICE_LABEL_SIZE,
		  SEEK_SET) == -1) {
	    g_debug("ftruncate failed: %s", strerror(errno));
	    dwr = WRITE_FAILED;
	}
	if (fsync(self->open_file_fd) == -1) {
	    g_debug("fsync failed: %s", strerror(errno));
	    dwr = WRITE_FAILED;
	}
	return dwr;
    } else if (result != RESULT_SUCCESS) {
	/* vfs_device_robust_write set error status appropriately */
        return WRITE_FAILED;
    }

    self->volume_bytes += size;
    self->checked_bytes_used += size;
    dself->block++;
    g_mutex_lock(dself->device_mutex);
    dself->bytes_written += size;
    g_mutex_unlock(dself->device_mutex);

    return WRITE_SUCCEED;
}

static int
vfs_device_read_block(
    Device   *dself,
    gpointer  data,
    int      *size_req,
    int       max_block G_GNUC_UNUSED)
{
    VfsDevice * self;
    int size;
    IoResult result;

    self = VFS_DEVICE(dself);

    if (device_in_error(self)) return -1;

    if (data == NULL || (gsize)*size_req < dself->block_size) {
        /* Just a size query. */
	g_assert(dself->block_size < INT_MAX);
        *size_req = (int)dself->block_size;
        return 0;
    }

    size = dself->block_size;
    result = vfs_device_robust_read(self, data, &size);
    switch (result) {
    case RESULT_SUCCESS:
        *size_req = size;
	g_mutex_lock(dself->device_mutex);
	dself->bytes_read += size;
	g_mutex_unlock(dself->device_mutex);
	dself->block++;
        return size;
    case RESULT_NO_DATA:
        dself->is_eof = TRUE;
	g_mutex_lock(dself->device_mutex);
        dself->in_file = FALSE;
	g_mutex_unlock(dself->device_mutex);
	device_set_error(dself,
	    g_strdup(_("EOF")),
	    DEVICE_STATUS_SUCCESS);
        return -1;
    default:
	device_set_error(dself,
	    g_strdup_printf(_("Error reading from data file: %s"), strerror(errno)),
	    DEVICE_STATUS_DEVICE_ERROR);
        return -1;
    }

    g_assert_not_reached();
}

static gboolean
vfs_device_start(
    Device *dself,
    DeviceAccessMode mode,
    char *label,
    char *timestamp)
{
    VfsDevice * self = VFS_DEVICE(dself);

    if (!self->validate(dself)) {
	/* error message set by validate */
        return FALSE;
    }

    g_mutex_lock(dself->device_mutex);
    dself->in_file = FALSE;
    g_mutex_unlock(dself->device_mutex);

    if (mode == ACCESS_WRITE) {
        promote_volume_lock(self);
        if (!self->clear_and_prepare_label(dself, label, timestamp)) {
	    /* clear_and_prepare_label sets error status if necessary */
            demote_volume_lock(self);
            return FALSE;
        }

        g_free(dself->volume_label);
        dself->volume_label = g_strdup(label);
        g_free(dself->volume_time);
        dself->volume_time = g_strdup(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;
        }
    }

    self->release_file(dself);

    return TRUE;
}

static gboolean
vfs_device_finish(
    Device *dself)
{
    VfsDevice *self = VFS_DEVICE(dself);

    self->release_file(dself);

    dself->access_mode = ACCESS_NULL;
    g_mutex_lock(dself->device_mutex);
    dself->in_file = FALSE;
    g_mutex_unlock(dself->device_mutex);

    if (device_in_error(self)) return FALSE;

    return TRUE;
}

typedef struct {
    VfsDevice *self;
    int rval;
} glfn_data;

/* A SearchDirectoryFunctor. */
static gboolean
get_last_file_number_functor(
    const char *filename,
    gpointer datap)
{
    guint64 file;
    glfn_data *data = (glfn_data *)datap;

    file = g_ascii_strtoull(filename, NULL, 10); /* Guaranteed to work. */
    if (file > G_MAXINT) {
	g_warning(_("Super-large device file %s found, ignoring"), filename);
        return TRUE;
    }
    /* This condition is needlessly complex due to sign issues. */
    if (data->rval < 0 || ((guint)data->rval) < file) {
        data->rval = file;
    }
    return TRUE;
}

static gint
get_last_file_number(
    VfsDevice *self)
{
    glfn_data data;
    int count;
    Device *dself = DEVICE(self);
    data.self = self;
    data.rval = -1;

    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. */
	device_set_error(dself,
	    g_strdup(_("Error identifying VFS device contents!")),
	    DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);
        return -1;
    } else {
        g_assert(data.rval >= 0);
    }

    return data.rval;
}

typedef struct {
    VfsDevice * self;
    guint request;
    int best_found;
} gnfn_data;

/* A SearchDirectoryFunctor. */
static gboolean
get_next_file_number_functor(
    const char *filename,
    gpointer datap)
{
    guint      file;
    gnfn_data *data = (gnfn_data*)datap;

    file = g_ascii_strtoull(filename, NULL, 10); /* Guaranteed to work. */
    if (file > G_MAXINT) {
	g_warning(_("Super-large device file %s found, ignoring"), filename);
        return TRUE;
    }
    /* This condition is needlessly complex due to sign issues. */
    if (file >= data->request &&
        (data->best_found < 0 || file < (guint)data->best_found)) {
        data->best_found = file;
    }
    return TRUE;
}

/* Returns the file number equal to or greater than the given requested
 * file number. */
static gint
get_next_file_number(
    VfsDevice *self,
    guint request)
{
    gnfn_data data;
    int count;
    Device *dself = 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);

    if (count <= 0) {
        /* Somebody deleted something important while we weren't looking. */
	device_set_error(dself,
	    g_strdup(_("Error identifying VFS device contents!")),
	    DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);
        return -1;
    }

    /* Could be -1. */
    return data.best_found;
}

/* Finds the file number, acquires a lock, and returns the new file name. */
static char *
make_new_file_name(
    VfsDevice *self,
    const dumpfile_t *ji)
{
    char * rval;
    char *base, *sanitary_base;
    int fileno;

    for (;;) {
        fileno = 1 + get_last_file_number(self);
        if (fileno <= 0)
            return NULL;

        if (open_lock(self, fileno, TRUE)) {
            break;
        } else {
            continue;
        }
    }

    /* record that we're at this filenum now */
    DEVICE(self)->file = fileno;

    base = g_strdup_printf("%05d.%s.%s.%d", fileno, ji->name, ji->disk,
                           ji->dumplevel);
    sanitary_base = sanitise_filename(base);
    amfree(base);
    rval = g_strdup_printf("%s/%s", self->dir_name, sanitary_base);
    amfree(sanitary_base);
    return rval;
}

static gboolean
vfs_device_start_file_open(
    Device *dself,
    dumpfile_t *ji)
{
    VfsDevice *self = VFS_DEVICE(dself);

    self->file_name = make_new_file_name(self, ji);
    if (self->file_name == NULL) {
	device_set_error(dself,
		g_strdup(_("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) {
	device_set_error(dself,
		g_strdup_printf(_("Can't create file %s: %s"), self->file_name, strerror(errno)),
		DEVICE_STATUS_DEVICE_ERROR);
        self->release_file(dself);
        return FALSE;
    }

    return TRUE;
}


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;

    /* 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)) {
	/* check_at_peom() only checks against MAX_VOLUME_USAGE limit */
	dself->is_eom = TRUE;
	device_set_error(dself,
		g_strdup(_("No space left on device: more than MAX_VOLUME_USAGE bytes written")),
		DEVICE_STATUS_DEVICE_ERROR);
	return FALSE;
    }

    /* The basic idea here is thus:
       1) Try to get a lock on the next filenumber.
       2) If that fails, update our idea of "next filenumber" and try again.
       3) Then open the file itself.
       4) Write the label.
       5) Chain up. */

    if (!self->device_start_file_open(dself, ji)) {
	return FALSE;
    }

    if (!vfs_write_amanda_header(self, ji)) {
	/* vfs_write_amanda_header sets error status if necessary */
        self->release_file(dself);
        return FALSE;
    }

    /* handle some accounting business */
    self->volume_bytes += VFS_DEVICE_LABEL_SIZE;
    self->checked_bytes_used += VFS_DEVICE_LABEL_SIZE;
    dself->block = 0;
    g_mutex_lock(dself->device_mutex);
    dself->in_file = TRUE;
    dself->bytes_written = 0;
    g_mutex_unlock(dself->device_mutex);
    /* make_new_file_name set dself->file for us */

    return TRUE;
}

static gboolean
vfs_device_finish_file(
    Device *dself)
{
    VfsDevice *self = VFS_DEVICE(dself);

    if (!dself->in_file)
	return TRUE;

    g_mutex_lock(dself->device_mutex);
    dself->in_file = FALSE;
    g_mutex_unlock(dself->device_mutex);

    self->release_file(dself);

    if (device_in_error(self)) return 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 *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;

    if (device_in_error(self)) return NULL;

    dself->is_eof = FALSE;
    dself->block = 0;
    g_mutex_lock(dself->device_mutex);
    dself->in_file = FALSE;
    dself->bytes_read = 0;
    g_mutex_unlock(dself->device_mutex);

    self->release_file(dself);

    if (requested_file > 0) {
        file = get_next_file_number(self, requested_file);
    } else {
        file = requested_file;
    }

    if (file < 0) {
        /* Did they request one past the end? */
        char * tmp_file_name;
        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,
		g_strdup(_("Attempt to read past tape-end file")),
		DEVICE_STATUS_SUCCESS);
            return NULL;
        }
    }

    if (!open_lock(self, file, FALSE)) {
	device_set_error(dself,
	    g_strdup(_("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,
	    g_strdup_printf(_("File %d not found"), file),
	    file == 0 ? DEVICE_STATUS_VOLUME_UNLABELED
		      : DEVICE_STATUS_VOLUME_ERROR);
        self->release_file(dself);
	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(dself,
	    g_strdup_printf(_("Couldn't open file %s: %s"), self->file_name, strerror(errno)),
	    DEVICE_STATUS_DEVICE_ERROR);
        amfree(self->file_name);
        self->release_file(dself);
        return NULL;
    }

    result = vfs_device_robust_read(self, header_buffer,
                                    &header_buffer_size);
    if (result != RESULT_SUCCESS) {
	device_set_error(dself,
	    g_strdup_printf(_("Problem reading Amanda header: %s"), device_error(dself)),
	    DEVICE_STATUS_VOLUME_ERROR);
        self->release_file(dself);
        return NULL;
    }

    rval = g_new(dumpfile_t, 1);
    parse_file_header(header_buffer, rval, header_buffer_size);
    switch (rval->type) {
        case F_DUMPFILE:
        case F_CONT_DUMPFILE:
        case F_SPLIT_DUMPFILE:
	    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,
		g_strdup(_("Invalid amanda header while reading file header")),
		DEVICE_STATUS_VOLUME_ERROR);
            amfree(rval);
            self->release_file(dself);
            return NULL;
    }

    /* update our state */
    if (requested_file == 0) {
	dself->header_block_size = header_buffer_size;
    }
    g_mutex_lock(dself->device_mutex);
    if (file > 0) {
	dself->in_file = TRUE;
    }
    dself->file = file;
    g_mutex_unlock(dself->device_mutex);

    return rval;
}

static gboolean
vfs_device_seek_block(
    Device *dself,
    guint64 block)
{
    VfsDevice * self = VFS_DEVICE(dself);
    off_t result;

    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) * dself->block_size + VFS_DEVICE_LABEL_SIZE,
                   SEEK_SET);

    dself->block = block;

    if (result == (off_t)(-1)) {
	device_set_error(dself,
	    g_strdup_printf(_("Error seeking within file: %s"), strerror(errno)),
	    DEVICE_STATUS_DEVICE_ERROR);
	return FALSE;
    }

    return TRUE;
}

static gboolean
try_unlink(
    const char *file)
{
    if (unlink(file) < 0) {
        return FALSE;
    } else {
        return TRUE;
    }
}

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;

    if (device_in_error(self)) return FALSE;

    /* Game Plan:
     * 1) Get a write lock on the file in question.
     * 2) Unlink the file in question.
     * 3) Unlink the lock.
     * 4) Release the lock.
     * FIXME: Is it OK to unlink the lockfile?
     */

    self->file_name = file_number_to_file_name(self, filenum);
    if (self->file_name == NULL) {
	device_set_error(dself,
	    g_strdup_printf(_("File %d not found"), filenum),
	    DEVICE_STATUS_VOLUME_ERROR);
        return FALSE;
    }

    if (!open_lock(self, filenum, FALSE)) {
	device_set_error(dself,
	    g_strdup(_("could not acquire lock")),
	    DEVICE_STATUS_DEVICE_ERROR);
        return FALSE;
    }

    if (0 != stat(self->file_name, &file_status)) {
	device_set_error(dself,
	    g_strdup_printf(_("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(dself,
	    g_strdup_printf(_("Unlink of %s failed: %s"), self->file_name, strerror(errno)),
	    DEVICE_STATUS_VOLUME_ERROR);
        self->release_file(dself);
        return FALSE;
    }

    self->volume_bytes -= file_size;
    self->release_file(dself);
    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);
    if (device_in_error(dself)) return FALSE;

    self->release_file(dself);

    dumpfile_free(dself->volume_header);
    dself->volume_header = NULL;
    device_set_error(dself, g_strdup("Unlabeled volume"),
		     DEVICE_STATUS_VOLUME_UNLABELED);

    return TRUE;
}

IoResult
vfs_device_robust_read(
    VfsDevice *self,
    char *buf,
    int  *count)
{
    int fd = self->open_file_fd;
    Device *dself = DEVICE(self);
    int want = *count, got = 0;

    while (got < want) {
        int result;
        result = read(fd, buf + got, want - got);
        if (result > 0) {
            got += result;
        } else if (result == 0) {
            /* end of file */
            if (got == 0) {
                return RESULT_NO_DATA;
            } else {
                *count = got;
                return RESULT_SUCCESS;
            }
        } else if (0
#ifdef EAGAIN
                || errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
                || errno == EWOULDBLOCK
#endif
#ifdef EINTR
                || errno == EINTR
#endif
                   ) {
            /* Try again. */
            continue;
        } else {
            /* Error occured. */
	    device_set_error(dself,
		g_strdup_printf(_("Error reading fd %d: %s"), fd, strerror(errno)),
		DEVICE_STATUS_VOLUME_ERROR);
            *count = got;
            return RESULT_ERROR;
        }
    }

    *count = got;
    return RESULT_SUCCESS;
}

IoResult
vfs_device_robust_write(
    VfsDevice *self,
    char *buf,
    int count)
{
    int fd = self->open_file_fd;
    Device *dself = DEVICE(self);
    int rval = 0;

    while (rval < count) {
        int result;
        result = write(fd, buf + rval, count - rval);
        if (result > 0) {
            rval += result;
            continue;
        } else if (0
#ifdef EAGAIN
                || errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
                || errno == EWOULDBLOCK
#endif
#ifdef EINTR
                || errno == EINTR
#endif
                   ) {
            /* Try again. */
            continue;
        } else if (0
#ifdef EFBIG
                   || errno == EFBIG
#endif
#ifdef ENOSPC
                   || errno == ENOSPC
#endif
                   ) {
            /* We are definitely out of space. */
	    device_set_error(dself,
		    g_strdup_printf(_("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. */
	    device_set_error(dself,
		    g_strdup_printf(_("Error writing device fd %d: %s"), fd, strerror(errno)),
		    DEVICE_STATUS_VOLUME_ERROR);
            return RESULT_ERROR;
        }
    }
    return RESULT_SUCCESS;
}

/* TODO: add prop */
