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