/*
- * Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved.
- *
- * This library is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License version 2.1 as
- * published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful, but
+ * Copyright (c) 2007, 2008, 2009, 2010, 2011 Zmanda, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- *
- * Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
- * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
+ * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
*/
#include "amanda.h"
#include <string.h> /* memset() */
#include "fsusage.h"
#include "util.h"
-#include "device.h"
#include <regex.h>
-/*
- * Type checking and casting macros
- */
-#define TYPE_VFS_DEVICE (vfs_device_get_type())
-#define VFS_DEVICE(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), vfs_device_get_type(), VfsDevice)
-#define VFS_DEVICE_CONST(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), vfs_device_get_type(), VfsDevice const)
-#define VFS_DEVICE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), vfs_device_get_type(), VfsDeviceClass)
-#define IS_VFS_DEVICE(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), vfs_device_get_type ())
-
-#define VFS_DEVICE_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), vfs_device_get_type(), VfsDeviceClass)
-static GType vfs_device_get_type (void);
-
-/*
- * Main object structure
- */
-typedef struct {
- Device __parent__;
-
- /*< private >*/
- char * dir_name;
- char * file_name;
- int file_lock_fd;
- char * file_lock_name;
- int volume_lock_fd;
- char * volume_lock_name;
- int open_file_fd;
-
- /* Properties */
- guint64 volume_bytes;
- guint64 volume_limit;
-} VfsDevice;
-
-/*
- * Class definition
- */
-typedef struct {
- DeviceClass __parent__;
-} VfsDeviceClass;
-
+#include "vfs-device.h"
/* This regex will match all VfsDevice files in a directory. We use it
for cleanup and verification. Note that this regex does NOT match
#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
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);
/* 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,
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;
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));
&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,
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);
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;
}
{
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) &
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)
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) {
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) {
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;
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)) {
/* 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 */
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);
}
static void update_volume_size(VfsDevice * self) {
+
self->volume_bytes = 0;
search_vfs_directory(self, "^[0-9]+\\.",
update_volume_size_functor, self);
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)
/* 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! */
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;
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!")),
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;
}
}
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. */
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")),
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);
g_assert(self->open_file_fd >= 0);
- if (self->volume_limit > 0 &&
- self->volume_bytes + size > self->volume_limit) {
- /* Simulate EOF. */
- pself->is_eof = TRUE;
+ if (check_at_leom(self, size))
+ pself->is_eom = TRUE;
+
+ if (check_at_peom(self, size)) {
+ pself->is_eom = TRUE;
device_set_error(pself,
stralloc(_("No space left on device")),
DEVICE_STATUS_VOLUME_ERROR);
- return FALSE;
+ return FALSE;
}
result = vfs_device_robust_write(self, data, size);
}
self->volume_bytes += size;
+ self->checked_bytes_used += size;
pself->block ++;
return TRUE;
VfsDevice * self;
int size;
IoResult result;
-
+
self = VFS_DEVICE(pself);
if (device_in_error(self)) 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);
+ VfsDevice * self = VFS_DEVICE(dself);
- if (!check_is_dir(pself, self->dir_name)) {
+ if (!check_is_dir(self, self->dir_name)) {
/* error message set by check_is_dir */
return FALSE;
}
- pself->in_file = FALSE;
+ dself->in_file = FALSE;
if (mode == ACCESS_WRITE) {
promote_volume_lock(self);
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;
}
VfsDevice * self;
self = VFS_DEVICE(pself);
- if (device_in_error(self)) return FALSE;
+ release_file(self);
pself->access_mode = ACCESS_NULL;
+ pself->in_file = FALSE;
+
+ if (device_in_error(self)) return FALSE;
+
return TRUE;
}
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);
} else {
g_assert(data.rval >= 0);
}
-
+
return data.rval;
}
/* 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);
DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR);
return -1;
}
-
+
/* Could be -1. */
return data.best_found;
}
fileno = 1 + get_last_file_number(self);
if (fileno <= 0)
return NULL;
-
+
if (open_lock(self, fileno, TRUE)) {
break;
} else {
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;
* 32k regardless of the block_size setting */
ji->blocksize = 32768;
- if (self->volume_limit > 0 &&
- self->volume_bytes + VFS_DEVICE_LABEL_SIZE > self->volume_limit) {
- device_set_error(pself,
+ if (check_at_leom(self, VFS_DEVICE_LABEL_SIZE))
+ dself->is_eom = TRUE;
+
+ if (check_at_peom(self, VFS_DEVICE_LABEL_SIZE)) {
+ dself->is_eom = TRUE;
+ device_set_error(dself,
stralloc(_("No space left on device")),
DEVICE_STATUS_DEVICE_ERROR);
- return FALSE;
+ return FALSE;
}
/* The basic idea here is thus:
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;
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);
/* handle some accounting business */
self->volume_bytes += VFS_DEVICE_LABEL_SIZE;
- pself->in_file = TRUE;
- pself->block = 0;
+ self->checked_bytes_used += VFS_DEVICE_LABEL_SIZE;
+ dself->in_file = TRUE;
+ dself->block = 0;
/* make_new_file_name set pself->file for us */
return TRUE;
}
-static gboolean
-vfs_device_finish_file (Device * pself) {
- VfsDevice * self;
- self = VFS_DEVICE(pself);
+static gboolean
+vfs_device_finish_file(Device * dself) {
+ VfsDevice * self = VFS_DEVICE(dself);
if (device_in_error(self)) return FALSE;
release_file(self);
- pself->in_file = FALSE;
+ dself->in_file = FALSE;
+
return TRUE;
}
* addition to its documented behavior, we also use it to open the
* volume label for reading at startup. In that second case, we avoid
* FdDevice-related side effects. */
-static dumpfile_t *
-vfs_device_seek_file (Device * pself, guint requested_file) {
- VfsDevice * self;
+static dumpfile_t *
+vfs_device_seek_file (Device * dself, guint requested_file) {
+ VfsDevice *self = VFS_DEVICE(dself);
int file;
dumpfile_t * rval;
char header_buffer[VFS_DEVICE_LABEL_SIZE];
int header_buffer_size = sizeof(header_buffer);
IoResult result;
- self = VFS_DEVICE(pself);
-
if (device_in_error(self)) return NULL;
- pself->in_file = FALSE;
- pself->is_eof = FALSE;
- pself->block = 0;
-
+ dself->in_file = FALSE;
+ dself->is_eof = FALSE;
+ dself->block = 0;
+
release_file(self);
if (requested_file > 0) {
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;
}
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;
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);
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;
/* FALLTHROUGH */
default:
- device_set_error(pself,
+ device_set_error(dself,
stralloc(_("Invalid amanda header while reading file header")),
DEVICE_STATUS_VOLUME_ERROR);
amfree(rval);
}
/* update our state */
- pself->in_file = TRUE;
- pself->file = file;
+ if (requested_file == 0) {
+ dself->header_block_size = header_buffer_size;
+ }
+ dself->in_file = TRUE;
+ dself->file = file;
return rval;
}
-static gboolean
+static gboolean
vfs_device_seek_block (Device * pself, guint64 block) {
VfsDevice * self;
off_t result;
}
}
-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:
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;
}
}
return RESULT_SUCCESS;
}
+
+/* TODO: add prop */