X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=device-src%2Fvfs-device.c;h=9952c39db45c51d13d4d4560f9eee7e54873bd91;hb=HEAD;hp=667c5489bc236e170b0f4f8a23fd3427d400bea2;hpb=afaa71b3866b46b082b6c895772e15b36d8865fe;p=debian%2Famanda diff --git a/device-src/vfs-device.c b/device-src/vfs-device.c index 667c548..9952c39 100644 --- a/device-src/vfs-device.c +++ b/device-src/vfs-device.c @@ -1,69 +1,31 @@ /* - * Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved. - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 2.1 as - * published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, but + * Copyright (c) 2007-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 + * 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300 - * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300 + * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com */ #include "amanda.h" #include /* memset() */ #include "fsusage.h" #include "util.h" -#include "device.h" #include -/* - * Type checking and casting macros - */ -#define TYPE_VFS_DEVICE (vfs_device_get_type()) -#define VFS_DEVICE(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), vfs_device_get_type(), VfsDevice) -#define VFS_DEVICE_CONST(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), vfs_device_get_type(), VfsDevice const) -#define VFS_DEVICE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), vfs_device_get_type(), VfsDeviceClass) -#define IS_VFS_DEVICE(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), vfs_device_get_type ()) - -#define VFS_DEVICE_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), vfs_device_get_type(), VfsDeviceClass) -static GType vfs_device_get_type (void); - -/* - * Main object structure - */ -typedef struct { - Device __parent__; - - /*< private >*/ - char * dir_name; - char * file_name; - int file_lock_fd; - char * file_lock_name; - int volume_lock_fd; - char * volume_lock_name; - int open_file_fd; - - /* Properties */ - guint64 volume_bytes; - guint64 volume_limit; -} VfsDevice; - -/* - * Class definition - */ -typedef struct { - DeviceClass __parent__; -} VfsDeviceClass; - +#include "vfs-device.h" /* This regex will match all VfsDevice files in a directory. We use it for cleanup and verification. Note that this regex does NOT match @@ -79,6 +41,14 @@ typedef struct { #define VFS_DEVICE_DEFAULT_BLOCK_SIZE (DISK_BLOCK_BYTES) #define VFS_DEVICE_LABEL_SIZE (32768) +/* Allow comfortable room for another block and a header before PEOM */ +#define EOM_EARLY_WARNING_ZONE_BLOCKS 4 + +/* Constants for free-space monitoring */ +#define MONITOR_FREE_SPACE_EVERY_SECONDS 5 +#define MONITOR_FREE_SPACE_EVERY_KB 102400 +#define MONITOR_FREE_SPACE_CLOSELY_WITHIN_BLOCKS 128 + /* This looks dangerous, but is actually modified by the umask. */ #define VFS_DEVICE_CREAT_MODE 0666 @@ -107,10 +77,11 @@ static gboolean vfs_device_finish (Device * pself); static void vfs_device_open_device (Device * pself, char * device_name, char * device_type, char * device_node); static gboolean vfs_device_start_file (Device * pself, dumpfile_t * ji); -static gboolean vfs_device_finish_file (Device * pself); +static gboolean vfs_device_finish_file (Device * dself); static dumpfile_t * vfs_device_seek_file (Device * self, guint file); static gboolean vfs_device_seek_block (Device * self, guint64 block); static gboolean vfs_device_recycle_file (Device * pself, guint filenum); +static gboolean vfs_device_erase (Device * pself); static Device * vfs_device_factory(char * device_name, char * device_type, char * device_node); static DeviceStatusFlags vfs_device_read_label(Device * dself); static gboolean vfs_device_write_block(Device * self, guint size, gpointer data); @@ -122,21 +93,29 @@ static IoResult vfs_device_robust_read(VfsDevice * self, char *buf, /* Various helper functions. */ static void release_file(VfsDevice * self); -static gboolean check_is_dir(Device * d_self, const char * name); -static char* file_number_to_file_name(VfsDevice * self, guint file); +static gboolean check_is_dir(VfsDevice * self, const char * name); +static char * file_number_to_file_name(VfsDevice * self, guint file); static gboolean file_number_to_file_name_functor(const char * filename, gpointer datap); static gboolean vfs_device_set_max_volume_usage_fn(Device *p_self, DevicePropertyBase *base, GValue *val, PropertySurety surety, PropertySource source); -gboolean vfs_device_get_free_space_fn(struct Device *p_self, +static gboolean vfs_device_set_enforce_max_volume_usage_fn(Device *p_self, + DevicePropertyBase *base, GValue *val, + PropertySurety surety, PropertySource source); +static gboolean property_get_monitor_free_space_fn(Device *p_self, DevicePropertyBase *base, GValue *val, PropertySurety *surety, PropertySource *source); +static gboolean property_set_monitor_free_space_fn(Device *p_self, + DevicePropertyBase *base, GValue *val, + PropertySurety surety, PropertySource source); +static gboolean property_set_leom_fn(Device *p_self, + DevicePropertyBase *base, GValue *val, + PropertySurety surety, PropertySource source); //static char* lockfile_name(VfsDevice * self, guint file); static gboolean open_lock(VfsDevice * self, int file, gboolean exclusive); static void promote_volume_lock(VfsDevice * self); static void demote_volume_lock(VfsDevice * self); -static void delete_vfs_files(VfsDevice * self); static gboolean delete_vfs_files_functor(const char * filename, gpointer self); static gboolean check_dir_empty_functor(const char * filename, @@ -151,15 +130,32 @@ static gboolean get_last_file_number_functor(const char * filename, static char * make_new_file_name(VfsDevice * self, const dumpfile_t * ji); static gboolean try_unlink(const char * file); +/* return TRUE if the device is going to hit ENOSPC "soon" - this is used to + * detect LEOM as represented by actually running out of space on the + * underlying filesystem. Size is the size of the buffer that is about to + * be written. */ +static gboolean check_at_leom(VfsDevice *self, guint64 size); +/* Similar, but for PEOM */ +static gboolean check_at_peom(VfsDevice *self, guint64 size); + /* pointer to the classes of our parents */ static DeviceClass *parent_class = NULL; +/* device-specific properties */ +DevicePropertyBase device_property_monitor_free_space; +#define PROPERTY_MONITOR_FREE_SPACE (device_property_monitor_free_space.ID) + void vfs_device_register(void) { static const char * device_prefix_list[] = { "file", NULL }; + + device_property_fill_and_register(&device_property_monitor_free_space, + G_TYPE_BOOLEAN, "monitor_free_space", + "Should VFS device monitor the filesystem's available free space?"); + register_device(vfs_device_factory, device_prefix_list); } -static GType +GType vfs_device_get_type (void) { static GType type = 0; @@ -181,20 +177,26 @@ vfs_device_get_type (void) type = g_type_register_static (TYPE_DEVICE, "VfsDevice", &info, (GTypeFlags)0); } - + return type; } -static void +static void vfs_device_init (VfsDevice * self) { Device * dself = DEVICE(self); GValue response; self->dir_name = self->file_name = NULL; - self->file_lock_name = self->volume_lock_name = NULL; - self->file_lock_fd = self->volume_lock_fd = self->open_file_fd = -1; - self->volume_bytes = 0; + self->open_file_fd = -1; + self->volume_bytes = 0; self->volume_limit = 0; + self->leom = TRUE; + self->enforce_volume_limit = TRUE; + + self->monitor_free_space = TRUE; + self->checked_fs_free_bytes = G_MAXUINT64; + self->checked_fs_free_time = 0; + self->checked_fs_free_bytes = G_MAXUINT64; /* Register Properties */ bzero(&response, sizeof(response)); @@ -223,6 +225,24 @@ vfs_device_init (VfsDevice * self) { &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); g_value_unset(&response); + g_value_init(&response, G_TYPE_BOOLEAN); + g_value_set_boolean(&response, TRUE); + device_set_simple_property(dself, PROPERTY_FULL_DELETION, + &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); + g_value_unset(&response); + + g_value_init(&response, G_TYPE_BOOLEAN); + g_value_set_boolean(&response, TRUE); + device_set_simple_property(dself, PROPERTY_LEOM, + &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); + g_value_unset(&response); + + g_value_init(&response, G_TYPE_BOOLEAN); + g_value_set_boolean(&response, TRUE); + device_set_simple_property(dself, PROPERTY_ENFORCE_MAX_VOLUME_USAGE, + &response, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); + g_value_unset(&response); + g_value_init(&response, G_TYPE_BOOLEAN); g_value_set_boolean(&response, FALSE); device_set_simple_property(dself, PROPERTY_COMPRESSION, @@ -236,11 +256,11 @@ vfs_device_init (VfsDevice * self) { g_value_unset(&response); } -static void +static void vfs_device_class_init (VfsDeviceClass * c) { GObjectClass *g_object_class = (GObjectClass*) c; - DeviceClass *device_class = (DeviceClass *)c; + DeviceClass *device_class = DEVICE_CLASS(c); parent_class = g_type_class_ref(TYPE_DEVICE); @@ -254,7 +274,9 @@ vfs_device_class_init (VfsDeviceClass * c) device_class->seek_file = vfs_device_seek_file; device_class->seek_block = vfs_device_seek_block; device_class->recycle_file = vfs_device_recycle_file; + device_class->erase = vfs_device_erase; device_class->finish = vfs_device_finish; + g_object_class->finalize = vfs_device_finalize; } @@ -263,10 +285,10 @@ vfs_device_base_init (VfsDeviceClass * c) { DeviceClass *device_class = (DeviceClass *)c; - device_class_register_property(device_class, PROPERTY_FREE_SPACE, - PROPERTY_ACCESS_GET_MASK, - vfs_device_get_free_space_fn, - NULL); + device_class_register_property(device_class, PROPERTY_MONITOR_FREE_SPACE, + PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK, + property_get_monitor_free_space_fn, + property_set_monitor_free_space_fn); device_class_register_property(device_class, PROPERTY_MAX_VOLUME_USAGE, (PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK) & @@ -274,13 +296,25 @@ vfs_device_base_init (VfsDeviceClass * c) device_simple_property_get_fn, vfs_device_set_max_volume_usage_fn); + device_class_register_property(device_class, PROPERTY_ENFORCE_MAX_VOLUME_USAGE, + (PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_MASK) & + (~ PROPERTY_ACCESS_SET_INSIDE_FILE_WRITE), + device_simple_property_get_fn, + vfs_device_set_enforce_max_volume_usage_fn); + device_class_register_property(device_class, PROPERTY_COMPRESSION, PROPERTY_ACCESS_GET_MASK, device_simple_property_get_fn, NULL); + + /* add the ability to set LEOM to FALSE, for testing purposes */ + device_class_register_property(device_class, PROPERTY_LEOM, + PROPERTY_ACCESS_GET_MASK | PROPERTY_ACCESS_SET_BEFORE_START, + device_simple_property_get_fn, + property_set_leom_fn); } -gboolean +static gboolean vfs_device_set_max_volume_usage_fn(Device *p_self, DevicePropertyBase *base, GValue *val, PropertySurety surety, PropertySource source) @@ -292,58 +326,69 @@ vfs_device_set_max_volume_usage_fn(Device *p_self, return device_simple_property_set_fn(p_self, base, val, surety, source); } -gboolean -vfs_device_get_free_space_fn(struct Device *p_self, - DevicePropertyBase *base G_GNUC_UNUSED, GValue *val, - PropertySurety *surety, PropertySource *source) +static gboolean +vfs_device_set_enforce_max_volume_usage_fn(Device *p_self, + DevicePropertyBase *base, GValue *val, + PropertySurety surety, PropertySource source) { VfsDevice *self = VFS_DEVICE(p_self); - QualifiedSize qsize; - struct fs_usage fsusage; - guint64 bytes_avail; - - if (get_fs_usage(self->dir_name, NULL, &fsusage) == 0) { - if (fsusage.fsu_bavail_top_bit_set) - bytes_avail = 0; - else - bytes_avail = fsusage.fsu_bavail * fsusage.fsu_blocksize; - if (self->volume_limit && (guint64)self->volume_limit < bytes_avail / 1024) - bytes_avail = (guint64)self->volume_limit * 1024; - - qsize.accuracy = SIZE_ACCURACY_REAL; - qsize.bytes = bytes_avail; - if (surety) - *surety = PROPERTY_SURETY_GOOD; - } else { - g_warning(_("get_fs_usage('%s') failed: %s"), self->dir_name, strerror(errno)); - qsize.accuracy = SIZE_ACCURACY_UNKNOWN; - qsize.bytes = 0; - if (surety) - *surety = PROPERTY_SURETY_BAD; - } - g_value_unset_init(val, QUALIFIED_SIZE_TYPE); - g_value_set_boxed(val, &qsize); + self->enforce_volume_limit = g_value_get_boolean(val); + + return device_simple_property_set_fn(p_self, base, val, surety, source); +} + +static gboolean +property_get_monitor_free_space_fn(Device *p_self, DevicePropertyBase *base G_GNUC_UNUSED, + GValue *val, PropertySurety *surety, PropertySource *source) +{ + VfsDevice *self = VFS_DEVICE(p_self); + + g_value_unset_init(val, G_TYPE_BOOLEAN); + g_value_set_boolean(val, self->monitor_free_space); + + if (surety) + *surety = PROPERTY_SURETY_GOOD; if (source) - *source = PROPERTY_SOURCE_DETECTED; + *source = PROPERTY_SOURCE_DEFAULT; return TRUE; } -/* Drops everything associated with the volume file: Its name and fd, - its lock, and its lock's name and fd. */ -static void release_file(VfsDevice * self) { + +static gboolean +property_set_monitor_free_space_fn(Device *p_self, + DevicePropertyBase *base, GValue *val, + PropertySurety surety, PropertySource source) +{ + VfsDevice *self = VFS_DEVICE(p_self); + + self->monitor_free_space = g_value_get_boolean(val); + + return device_simple_property_set_fn(p_self, base, val, surety, source); +} + +static gboolean +property_set_leom_fn(Device *p_self, + DevicePropertyBase *base, GValue *val, + PropertySurety surety, PropertySource source) +{ + VfsDevice *self = VFS_DEVICE(p_self); + + self->leom = g_value_get_boolean(val); + + return device_simple_property_set_fn(p_self, base, val, surety, source); +} + +/* Drops everything associated with the volume file: Its name and fd. */ +void release_file(VfsDevice * self) { /* Doesn't hurt. */ - robust_close(self->open_file_fd); + if (self->open_file_fd != -1) + robust_close(self->open_file_fd); amfree(self->file_name); - if (self->file_lock_fd > 0) { - amfunlock(self->file_lock_fd, self->file_lock_name); - close(self->file_lock_fd); - amfree(self->file_lock_name); - } - self->file_lock_fd = self->open_file_fd = -1; + self->open_file_fd = -1; } static void vfs_device_finalize(GObject * obj_self) { @@ -360,13 +405,6 @@ static void vfs_device_finalize(GObject * obj_self) { amfree(self->dir_name); release_file(self); - - if (self->volume_lock_fd >= 0) { - amfunlock(self->volume_lock_fd, self->volume_lock_name); - close(self->volume_lock_fd); - } - - amfree(self->volume_lock_name); } static Device * vfs_device_factory(char * device_name, char * device_type, char * device_node) { @@ -377,21 +415,22 @@ static Device * vfs_device_factory(char * device_name, char * device_type, char return rval; } -static gboolean check_is_dir(Device * d_self, const char * name) { +static gboolean check_is_dir(VfsDevice * self, const char * name) { + Device *dself = DEVICE(self); struct stat dir_status; - + if (stat(name, &dir_status) < 0) { #ifdef EINTR if (errno == EINTR) { - return check_is_dir(d_self, name); + return check_is_dir(self, name); } #endif /* EINTR */ - device_set_error(d_self, + device_set_error(dself, vstrallocf(_("Error checking directory %s: %s"), name, strerror(errno)), DEVICE_STATUS_DEVICE_ERROR); return FALSE; } else if (!S_ISDIR(dir_status.st_mode)) { - device_set_error(d_self, + device_set_error(dself, vstrallocf(_("VFS Device path %s is not a directory"), name), DEVICE_STATUS_DEVICE_ERROR); return FALSE; @@ -412,9 +451,9 @@ static gboolean file_number_to_file_name_functor(const char * filename, char * result_tmp; struct stat file_status; fnfn_data *data = (fnfn_data*)datap; - - result_tmp = vstralloc(data->self->dir_name, "/", filename, NULL); - + + result_tmp = vstralloc(data->self->dir_name, "/", filename, NULL); + /* Just to be thorough, let's check that it's a real file. */ if (0 != stat(result_tmp, &file_status)) { @@ -486,62 +525,13 @@ static gboolean open_lock(G_GNUC_UNUSED VfsDevice * self, /* At the moment, file locking is horribly broken. */ return TRUE; - -/* - int fd; - char * name; - Device *d_self = DEVICE(self); - if (file < 0) { - if (self->volume_lock_name == NULL) { - self->volume_lock_name = lockfile_name(self, 0); - } else if (self->volume_lock_fd >= 0) { - amfunlock(self->volume_lock_fd, self->volume_lock_name); - close(self->volume_lock_fd); - } - name = self->volume_lock_name; - } else { - if (self->file_lock_fd >= 0 && self->file_lock_name != NULL) { - amfunlock(self->file_lock_fd, self->file_lock_name); - } - amfree(self->file_lock_name); - close(self->file_lock_fd); - name = self->file_lock_name = lockfile_name(self, file); - } - - - fd = robust_open(name, O_CREAT | O_WRONLY, VFS_DEVICE_CREAT_MODE); - - if (fd < 0) { - device_set_error(d_self, - vstrallocf(_("Can't open lock file %s: %s"), name, strerror(errno)), - DEVICE_STATUS_DEVICE_ERROR); - return FALSE; - } - - if (exclusive) { - amflock(fd, name); - } else { - amroflock(fd, name); - } - - if (file < 0) { - self->volume_lock_fd = fd; - } else { - self->file_lock_fd = fd; - } - return TRUE; -*/ } /* For now, does it the bad way. */ -static void promote_volume_lock(VfsDevice * self) { - amfunlock(self->volume_lock_fd, self->volume_lock_name); - amflock(self->volume_lock_fd, self->volume_lock_name); +static void promote_volume_lock(VfsDevice * self G_GNUC_UNUSED) { } -static void demote_volume_lock(VfsDevice * self) { - amfunlock(self->volume_lock_fd, self->volume_lock_name); - amroflock(self->volume_lock_fd, self->volume_lock_name); +static void demote_volume_lock(VfsDevice * self G_GNUC_UNUSED) { } /* A SearchDirectoryFunctor */ @@ -549,7 +539,7 @@ static gboolean update_volume_size_functor(const char * filename, gpointer user_data) { char * full_filename; struct stat stat_buf; - VfsDevice * self = user_data; + VfsDevice * self = VFS_DEVICE(user_data); full_filename = vstralloc(self->dir_name, "/", filename, NULL); @@ -567,6 +557,7 @@ static gboolean update_volume_size_functor(const char * filename, } static void update_volume_size(VfsDevice * self) { + self->volume_bytes = 0; search_vfs_directory(self, "^[0-9]+\\.", update_volume_size_functor, self); @@ -595,11 +586,9 @@ vfs_device_open_device (Device * pself, char * device_name, char * device_type, static gboolean delete_vfs_files_functor(const char * filename, gpointer user_data) { VfsDevice * self; - Device * d_self; char * path_name; self = VFS_DEVICE(user_data); - d_self = DEVICE(self); /* Skip the volume lock. */ if (strcmp(filename, VOLUME_LOCKFILE_NAME) == 0) @@ -615,7 +604,7 @@ static gboolean delete_vfs_files_functor(const char * filename, /* delete_vfs_files deletes all VfsDevice files in the directory except the volume lockfile. */ -static void delete_vfs_files(VfsDevice * self) { +void delete_vfs_files(VfsDevice * self) { g_assert(self != NULL); /* This function assumes that the volume is locked! */ @@ -627,12 +616,8 @@ static void delete_vfs_files(VfsDevice * self) { warning. It also dodges the volume lockfile. */ static gboolean check_dir_empty_functor(const char * filename, gpointer user_data) { - VfsDevice * self; + VfsDevice * self = VFS_DEVICE(user_data); char * path_name; - Device *d_self; - - self = VFS_DEVICE(user_data); - d_self = DEVICE(self); if (strcmp(filename, VOLUME_LOCKFILE_NAME) == 0) return TRUE; @@ -654,8 +639,8 @@ static gboolean write_amanda_header(VfsDevice * self, g_assert(header != NULL); - label_buffer = build_header(header, VFS_DEVICE_LABEL_SIZE); - if (strlen(label_buffer)+1 > VFS_DEVICE_LABEL_SIZE) { + label_buffer = device_build_amanda_header(d_self, header, NULL); + if (!label_buffer) { amfree(label_buffer); device_set_error(d_self, stralloc(_("Amanda file header won't fit in a single block!")), @@ -701,10 +686,12 @@ static gboolean clear_and_prepare_label(VfsDevice * self, char * label, label_header = make_tapestart_header(DEVICE(self), label, timestamp); if (!write_amanda_header(self, label_header)) { /* write_amanda_header sets error status if necessary */ - amfree(label_header); + dumpfile_free(label_header); return FALSE; } - amfree(label_header); + dumpfile_free(d_self->volume_header); + d_self->header_block_size = VFS_DEVICE_LABEL_SIZE; + d_self->volume_header = label_header; self->volume_bytes = VFS_DEVICE_LABEL_SIZE; return TRUE; } @@ -741,24 +728,25 @@ error: } static DeviceStatusFlags vfs_device_read_label(Device * dself) { + VfsDevice * self = VFS_DEVICE(dself); dumpfile_t * amanda_header; - VfsDevice * self; - self = VFS_DEVICE(dself); g_assert(self != NULL); - if (!check_is_dir(dself, self->dir_name)) { + if (!check_is_dir(self, self->dir_name)) { /* error message set by check_is_dir */ - return FALSE; + return dself->status; } amfree(dself->volume_label); amfree(dself->volume_time); - amfree(dself->volume_header); + dumpfile_free(dself->volume_header); + dself->volume_header = NULL; - if (device_in_error(self)) return dself->status; + if (device_in_error(dself)) return dself->status; amanda_header = dself->volume_header = vfs_device_seek_file(dself, 0); + release_file(self); if (amanda_header == NULL) { /* This means an error occured getting locks or opening the header * file. */ @@ -770,7 +758,11 @@ static DeviceStatusFlags vfs_device_read_label(Device * dself) { return dself->status; } - if (amanda_header->type != F_TAPESTART) { + /* close the fd we just opened */ + vfs_device_finish_file(dself); + + if (amanda_header->type != F_TAPESTART && + amanda_header->type != F_EMPTY) { /* This is an error, and should not happen. */ device_set_error(dself, stralloc(_("Got a bad volume label")), @@ -779,11 +771,13 @@ static DeviceStatusFlags vfs_device_read_label(Device * dself) { return dself->status; } - dself->volume_label = g_strdup(amanda_header->name); - dself->volume_time = g_strdup(amanda_header->datestamp); - /* dself->volume_header is already set */ + /* self->volume_header is already set */ - device_set_error(dself, NULL, DEVICE_STATUS_SUCCESS); + if (amanda_header->type == F_TAPESTART) { + dself->volume_label = g_strdup(amanda_header->name); + dself->volume_time = g_strdup(amanda_header->datestamp); + device_set_error(dself, NULL, DEVICE_STATUS_SUCCESS); + } update_volume_size(self); @@ -798,14 +792,16 @@ static gboolean vfs_device_write_block(Device * pself, guint size, gpointer data g_assert(self->open_file_fd >= 0); - if (self->volume_limit > 0 && - self->volume_bytes + size > self->volume_limit) { - /* Simulate EOF. */ - pself->is_eof = TRUE; + if (check_at_leom(self, size)) + pself->is_eom = TRUE; + + if (check_at_peom(self, size)) { + /* check_at_peom() only checks against MAX_VOLUME_USAGE limit */ + pself->is_eom = TRUE; device_set_error(pself, - stralloc(_("No space left on device")), + stralloc(_("No space left on device: more than MAX_VOLUME_USAGE bytes written")), DEVICE_STATUS_VOLUME_ERROR); - return FALSE; + return FALSE; } result = vfs_device_robust_write(self, data, size); @@ -815,7 +811,11 @@ static gboolean vfs_device_write_block(Device * pself, guint size, gpointer data } 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; } @@ -825,7 +825,7 @@ vfs_device_read_block(Device * pself, gpointer data, int * size_req) { VfsDevice * self; int size; IoResult result; - + self = VFS_DEVICE(pself); if (device_in_error(self)) return -1; @@ -842,11 +842,16 @@ vfs_device_read_block(Device * pself, gpointer data, int * size_req) { 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); @@ -861,18 +866,20 @@ vfs_device_read_block(Device * pself, gpointer data, int * size_req) { g_assert_not_reached(); } -static gboolean vfs_device_start(Device * pself, +static gboolean +vfs_device_start(Device * dself, DeviceAccessMode mode, char * label, char * timestamp) { - VfsDevice * self; - self = VFS_DEVICE(pself); + VfsDevice * self = VFS_DEVICE(dself); - if (!check_is_dir(pself, self->dir_name)) { + if (!check_is_dir(self, self->dir_name)) { /* error message set by check_is_dir */ return FALSE; } - pself->in_file = FALSE; + g_mutex_lock(dself->device_mutex); + dself->in_file = FALSE; + g_mutex_unlock(dself->device_mutex); if (mode == ACCESS_WRITE) { promote_volume_lock(self); @@ -882,25 +889,25 @@ static gboolean vfs_device_start(Device * pself, return FALSE; } - pself->volume_label = newstralloc(pself->volume_label, label); - pself->volume_time = newstralloc(pself->volume_time, timestamp); + dself->volume_label = newstralloc(dself->volume_label, label); + dself->volume_time = newstralloc(dself->volume_time, timestamp); /* unset the VOLUME_UNLABELED flag, if it was set */ - device_set_error(pself, NULL, DEVICE_STATUS_SUCCESS); + device_set_error(dself, NULL, DEVICE_STATUS_SUCCESS); demote_volume_lock(self); - pself->access_mode = mode; + dself->access_mode = mode; } else { - if (pself->volume_label == NULL && device_read_label(pself) != DEVICE_STATUS_SUCCESS) { + if (dself->volume_label == NULL && device_read_label(dself) != DEVICE_STATUS_SUCCESS) { /* device_read_label already set our error message */ return FALSE; } else { - pself->access_mode = mode; + dself->access_mode = mode; } } release_file(self); - + return TRUE; } @@ -909,9 +916,15 @@ vfs_device_finish (Device * pself) { VfsDevice * self; self = VFS_DEVICE(pself); - if (device_in_error(self)) return FALSE; + release_file(self); pself->access_mode = ACCESS_NULL; + 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; } @@ -938,13 +951,14 @@ static gboolean get_last_file_number_functor(const char * filename, return TRUE; } -static gint get_last_file_number(VfsDevice * self) { +static gint +get_last_file_number(VfsDevice * self) { glfn_data data; int count; Device *d_self = DEVICE(self); data.self = self; data.rval = -1; - + count = search_vfs_directory(self, "^[0-9]+\\.", get_last_file_number_functor, &data); @@ -957,7 +971,7 @@ static gint get_last_file_number(VfsDevice * self) { } else { g_assert(data.rval >= 0); } - + return data.rval; } @@ -988,14 +1002,15 @@ static gboolean get_next_file_number_functor(const char * filename, /* Returns the file number equal to or greater than the given requested * file number. */ -static gint get_next_file_number(VfsDevice * self, guint request) { +static gint +get_next_file_number(VfsDevice * self, guint request) { gnfn_data data; int count; Device *d_self = DEVICE(self); data.self = self; data.request = request; data.best_found = -1; - + count = search_vfs_directory(self, "^[0-9]+\\.", get_next_file_number_functor, &data); @@ -1006,7 +1021,7 @@ static gint get_next_file_number(VfsDevice * self, guint request) { DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); return -1; } - + /* Could be -1. */ return data.best_found; } @@ -1022,7 +1037,7 @@ char * make_new_file_name(VfsDevice * self, const dumpfile_t * ji) { fileno = 1 + get_last_file_number(self); if (fileno <= 0) return NULL; - + if (open_lock(self, fileno, TRUE)) { break; } else { @@ -1042,10 +1057,11 @@ char * make_new_file_name(VfsDevice * self, const dumpfile_t * ji) { return rval; } -static gboolean -vfs_device_start_file (Device * pself, dumpfile_t * ji) { - VfsDevice * self; - self = VFS_DEVICE(pself); +static gboolean +vfs_device_start_file (Device * dself, dumpfile_t * ji) { + VfsDevice * self = VFS_DEVICE(dself); + + dself->is_eom = FALSE; if (device_in_error(self)) return FALSE; @@ -1053,12 +1069,16 @@ vfs_device_start_file (Device * pself, dumpfile_t * ji) { * 32k regardless of the block_size setting */ ji->blocksize = 32768; - if (self->volume_limit > 0 && - self->volume_bytes + VFS_DEVICE_LABEL_SIZE > self->volume_limit) { - device_set_error(pself, - stralloc(_("No space left on device")), + 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; + return FALSE; } /* The basic idea here is thus: @@ -1070,7 +1090,7 @@ vfs_device_start_file (Device * pself, dumpfile_t * ji) { self->file_name = make_new_file_name(self, ji); if (self->file_name == NULL) { - device_set_error(pself, + device_set_error(dself, stralloc(_("Could not create header filename")), DEVICE_STATUS_DEVICE_ERROR); return FALSE; @@ -1080,14 +1100,14 @@ vfs_device_start_file (Device * pself, dumpfile_t * ji) { O_CREAT | O_EXCL | O_RDWR, VFS_DEVICE_CREAT_MODE); if (self->open_file_fd < 0) { - device_set_error(pself, + device_set_error(dself, vstrallocf(_("Can't create file %s: %s"), self->file_name, strerror(errno)), DEVICE_STATUS_DEVICE_ERROR); release_file(self); return FALSE; } - + if (!write_amanda_header(self, ji)) { /* write_amanda_header sets error status if necessary */ release_file(self); @@ -1096,23 +1116,29 @@ vfs_device_start_file (Device * pself, dumpfile_t * ji) { /* handle some accounting business */ self->volume_bytes += VFS_DEVICE_LABEL_SIZE; - pself->in_file = TRUE; - pself->block = 0; + self->checked_bytes_used += VFS_DEVICE_LABEL_SIZE; + dself->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 */ return TRUE; } -static gboolean -vfs_device_finish_file (Device * pself) { - VfsDevice * self; - self = VFS_DEVICE(pself); +static gboolean +vfs_device_finish_file(Device * dself) { + VfsDevice * self = VFS_DEVICE(dself); if (device_in_error(self)) return FALSE; release_file(self); - pself->in_file = FALSE; + g_mutex_lock(dself->device_mutex); + dself->in_file = FALSE; + g_mutex_unlock(dself->device_mutex); + return TRUE; } @@ -1120,23 +1146,24 @@ vfs_device_finish_file (Device * pself) { * addition to its documented behavior, we also use it to open the * volume label for reading at startup. In that second case, we avoid * FdDevice-related side effects. */ -static dumpfile_t * -vfs_device_seek_file (Device * pself, guint requested_file) { - VfsDevice * self; +static dumpfile_t * +vfs_device_seek_file (Device * dself, guint requested_file) { + VfsDevice *self = VFS_DEVICE(dself); int file; dumpfile_t * rval; char header_buffer[VFS_DEVICE_LABEL_SIZE]; int header_buffer_size = sizeof(header_buffer); IoResult result; - self = VFS_DEVICE(pself); - if (device_in_error(self)) return NULL; - pself->in_file = FALSE; - pself->is_eof = FALSE; - pself->block = 0; - + dself->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); + release_file(self); if (requested_file > 0) { @@ -1151,10 +1178,10 @@ vfs_device_seek_file (Device * pself, guint requested_file) { tmp_file_name = file_number_to_file_name(self, requested_file - 1); if (tmp_file_name != NULL) { free(tmp_file_name); - pself->file = requested_file; /* other attributes are already correct */ + dself->file = requested_file; /* other attributes are already correct */ return make_tapeend_header(); } else { - device_set_error(pself, + device_set_error(dself, stralloc(_("Attempt to read past tape-end file")), DEVICE_STATUS_SUCCESS); return NULL; @@ -1162,7 +1189,7 @@ vfs_device_seek_file (Device * pself, guint requested_file) { } if (!open_lock(self, file, FALSE)) { - device_set_error(pself, + device_set_error(dself, stralloc(_("could not acquire lock")), DEVICE_STATUS_DEVICE_ERROR); return NULL; @@ -1170,16 +1197,19 @@ vfs_device_seek_file (Device * pself, guint requested_file) { self->file_name = file_number_to_file_name(self, file); if (self->file_name == NULL) { - device_set_error(pself, + device_set_error(dself, vstrallocf(_("File %d not found"), file), - DEVICE_STATUS_VOLUME_ERROR); + file == 0 ? DEVICE_STATUS_VOLUME_UNLABELED + : DEVICE_STATUS_VOLUME_ERROR); release_file(self); - return NULL; + rval = g_new(dumpfile_t, 1); + fh_init(rval); + return rval; } self->open_file_fd = robust_open(self->file_name, O_RDONLY, 0); if (self->open_file_fd < 0) { - device_set_error(pself, + device_set_error(dself, vstrallocf(_("Couldn't open file %s: %s"), self->file_name, strerror(errno)), DEVICE_STATUS_DEVICE_ERROR); amfree(self->file_name); @@ -1190,8 +1220,8 @@ vfs_device_seek_file (Device * pself, guint requested_file) { result = vfs_device_robust_read(self, header_buffer, &header_buffer_size); if (result != RESULT_SUCCESS) { - device_set_error(pself, - vstrallocf(_("Problem reading Amanda header: %s"), device_error(pself)), + device_set_error(dself, + vstrallocf(_("Problem reading Amanda header: %s"), device_error(dself)), DEVICE_STATUS_VOLUME_ERROR); release_file(self); return NULL; @@ -1213,7 +1243,7 @@ vfs_device_seek_file (Device * pself, guint requested_file) { /* FALLTHROUGH */ default: - device_set_error(pself, + device_set_error(dself, stralloc(_("Invalid amanda header while reading file header")), DEVICE_STATUS_VOLUME_ERROR); amfree(rval); @@ -1222,13 +1252,18 @@ vfs_device_seek_file (Device * pself, guint requested_file) { } /* update our state */ - pself->in_file = TRUE; - pself->file = file; + if (requested_file == 0) { + dself->header_block_size = header_buffer_size; + } + 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; @@ -1264,14 +1299,80 @@ static gboolean try_unlink(const char * file) { } } -static gboolean -vfs_device_recycle_file (Device * pself, guint filenum) { - VfsDevice * self; +static gboolean +check_at_leom(VfsDevice *self, guint64 size) +{ + gboolean recheck = FALSE; + guint64 est_avail_now; + struct fs_usage fsusage; + guint64 block_size = DEVICE(self)->block_size; + guint64 eom_warning_buffer = EOM_EARLY_WARNING_ZONE_BLOCKS * block_size; + + if (!self->leom || !self->monitor_free_space) + return FALSE; + + /* handle VOLUME_LIMIT */ + if (self->enforce_volume_limit && self->volume_limit && + self->volume_bytes + size + eom_warning_buffer > self->volume_limit) { + return TRUE; + } + + /* handle actual filesystem available space, using some heuristics to avoid polling this + * too frequently */ + est_avail_now = 0; + if (self->checked_fs_free_bytes >= self->checked_bytes_used + size) + est_avail_now = self->checked_fs_free_bytes - self->checked_bytes_used - size; + + /* is it time to check again? */ + if (est_avail_now <= block_size * MONITOR_FREE_SPACE_CLOSELY_WITHIN_BLOCKS) { + recheck = TRUE; + } else if (self->checked_bytes_used > MONITOR_FREE_SPACE_EVERY_KB * 1024) { + recheck = TRUE; + } else if (self->checked_fs_free_time + MONITOR_FREE_SPACE_EVERY_SECONDS <= time(NULL)) { + recheck = TRUE; + } + + if (!recheck) + return FALSE; + + if (get_fs_usage(self->dir_name, NULL, &fsusage) < 0 || fsusage.fsu_bavail_top_bit_set) { + g_warning("Filesystem cannot provide free space: %s; setting MONITOR_FREE_SPACE false", + fsusage.fsu_bavail_top_bit_set? "no result" : strerror(errno)); + self->monitor_free_space = FALSE; + return FALSE; + } + + self->checked_fs_free_bytes = fsusage.fsu_bavail * fsusage.fsu_blocksize; + self->checked_bytes_used = 0; + self->checked_fs_free_time = time(NULL); + + if (self->checked_fs_free_bytes - size <= eom_warning_buffer) { + g_debug("%s: at LEOM", DEVICE(self)->device_name); + return TRUE; + } + + return FALSE; +} + +static gboolean +check_at_peom(VfsDevice *self, guint64 size) +{ + if (self->enforce_volume_limit && (self->volume_limit > 0)) { + guint64 newtotal = self->volume_bytes + size; + if (newtotal > self->volume_limit) { + return TRUE; + } + } + + return FALSE; +} + +static gboolean +vfs_device_recycle_file (Device * dself, guint filenum) { + VfsDevice * self = VFS_DEVICE(dself); struct stat file_status; off_t file_size; - self = VFS_DEVICE(pself); - if (device_in_error(self)) return FALSE; /* Game Plan: @@ -1284,46 +1385,57 @@ vfs_device_recycle_file (Device * pself, guint filenum) { self->file_name = file_number_to_file_name(self, filenum); if (self->file_name == NULL) { - device_set_error(pself, + device_set_error(dself, vstrallocf(_("File %d not found"), filenum), DEVICE_STATUS_VOLUME_ERROR); return FALSE; } if (!open_lock(self, filenum, FALSE)) { - device_set_error(pself, + device_set_error(dself, stralloc(_("could not acquire lock")), DEVICE_STATUS_DEVICE_ERROR); return FALSE; } if (0 != stat(self->file_name, &file_status)) { - device_set_error(pself, + device_set_error(dself, vstrallocf(_("Cannot stat file %s (%s), so not removing"), self->file_name, strerror(errno)), DEVICE_STATUS_VOLUME_ERROR); return FALSE; } file_size = file_status.st_size; - + if (!try_unlink(self->file_name)) { - device_set_error(pself, + device_set_error(dself, vstrallocf(_("Unlink of %s failed: %s"), self->file_name, strerror(errno)), DEVICE_STATUS_VOLUME_ERROR); release_file(self); return FALSE; } - if (!try_unlink(self->file_lock_name)) { - device_set_error(pself, - vstrallocf(_("Unlink of %s failed: %s"), self->file_lock_name, strerror(errno)), - DEVICE_STATUS_VOLUME_ERROR); - release_file(self); + self->volume_bytes -= file_size; + release_file(self); + return TRUE; +} + +static gboolean +vfs_device_erase (Device * dself) { + VfsDevice *self = VFS_DEVICE(dself); + + if (!open_lock(self, 0, TRUE)) return FALSE; - } - self->volume_bytes -= file_size; + delete_vfs_files(self); + release_file(self); + + dumpfile_free(dself->volume_header); + dself->volume_header = NULL; + device_set_error(dself, g_strdup("Unlabeled volume"), + DEVICE_STATUS_VOLUME_UNLABELED); + return TRUE; } @@ -1421,3 +1533,5 @@ vfs_device_robust_write(VfsDevice * self, char *buf, int count) { } return RESULT_SUCCESS; } + +/* TODO: add prop */