/* * Amanda, The Advanced Maryland Automatic Network Disk Archiver * Copyright (c) 2009 University of Maryland at College Park * Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved. * Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved. * All Rights Reserved. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of U.M. not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. U.M. makes no representations about the * suitability of this software for any purpose. It is provided "as is" * without express or implied warranty. * * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M. * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Author: Sam Couter */ #include "amanda.h" #include "vfs-device.h" /* * Type checking and casting macros */ #define TYPE_DISKFLAT_DEVICE (diskflat_device_get_type()) #define DISKFLAT_DEVICE(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_DISKFLAT_DEVICE, DiskflatDevice) #define DISKFLAT_DEVICE_CONST(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_DISKFLAT_DEVICE, DiskflatDevice const) #define DISKFLAT_DEVICE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), TYPE_DISKFLAT_DEVICE, DiskflatDeviceClass) #define IS_DISKFLAT_DEVICE(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_DISKFLAT_DEVICE) #define DISKFLAT_DEVICE_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_DISKFLAT_DEVICE, DiskflatDeviceClass) /* Forward declaration */ static GType diskflat_device_get_type(void); /* * Main object structure */ typedef struct _DiskflatDevice DiskflatDevice; struct _DiskflatDevice { VfsDevice __parent__; char *filename; }; /* * Class definition */ typedef struct _DiskflatDeviceClass DiskflatDeviceClass; struct _DiskflatDeviceClass { VfsDeviceClass __parent__; }; /* GObject housekeeping */ void diskflat_device_register(void); static Device* diskflat_device_factory(char *device_name, char *device_type, char *device_node); static void diskflat_device_class_init (DiskflatDeviceClass *c); static void diskflat_device_init (DiskflatDevice *self); /* Methods */ static void diskflat_device_open_device(Device *dself, char *device_name, char *device_type, char *device_node); static dumpfile_t * diskflat_device_seek_file (Device * dself, guint requested_file); static gboolean diskflat_device_seek_block (Device * dself, guint64 block); static gboolean diskflat_device_erase (Device * dself); static gboolean diskflat_device_finish(Device *dself); static void diskflat_device_finalize(GObject *gself); static gboolean diskflat_device_start_file_open(Device *dself, dumpfile_t *ji); static void diskflat_update_volume_size(Device *dself); static void diskflat_release_file(Device *dself); static gboolean diskflat_clear_and_prepare_label(Device *dself, char *label, char *timestamp); static gboolean diskflat_validate(Device *dself); static GType diskflat_device_get_type (void) { static GType type = 0; if (G_UNLIKELY(type == 0)) { static const GTypeInfo info = { sizeof (DiskflatDeviceClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) diskflat_device_class_init, (GClassFinalizeFunc) NULL, NULL /* class_data */, sizeof (DiskflatDevice), 0 /* n_preallocs */, (GInstanceInitFunc) diskflat_device_init, NULL }; type = g_type_register_static (TYPE_VFS_DEVICE, "DiskflatDevice", &info, (GTypeFlags)0); } return type; } void diskflat_device_register(void) { const char *device_prefix_list[] = { "diskflat", NULL }; register_device(diskflat_device_factory, device_prefix_list); } static Device * diskflat_device_factory( char *device_name, char *device_type, char *device_node) { Device *device; g_assert(g_str_has_prefix(device_type, "diskflat")); device = DEVICE(g_object_new(TYPE_DISKFLAT_DEVICE, NULL)); device_open_device(device, device_name, device_type, device_node); return device; } static void diskflat_device_class_init ( DiskflatDeviceClass *c) { DeviceClass *device_class = DEVICE_CLASS(c); GObjectClass *g_object_class = G_OBJECT_CLASS(c); device_class->open_device = diskflat_device_open_device; device_class->seek_file = diskflat_device_seek_file; device_class->seek_block = diskflat_device_seek_block; device_class->erase = diskflat_device_erase; device_class->finish = diskflat_device_finish; g_object_class->finalize = diskflat_device_finalize; } static void diskflat_device_init ( DiskflatDevice *self) { Device *dself = DEVICE(self); VfsDevice *vself = VFS_DEVICE(self); GValue val; vself->device_start_file_open = &diskflat_device_start_file_open; vself->update_volume_size = &diskflat_update_volume_size; vself->release_file = &diskflat_release_file; vself->clear_and_prepare_label = &diskflat_clear_and_prepare_label; vself->validate = &diskflat_validate; bzero(&val, sizeof(val)); g_value_init(&val, G_TYPE_BOOLEAN); g_value_set_boolean(&val, FALSE); device_set_simple_property(dself, PROPERTY_APPENDABLE, &val, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); g_value_unset(&val); g_value_init(&val, G_TYPE_BOOLEAN); g_value_set_boolean(&val, FALSE); device_set_simple_property(dself, PROPERTY_PARTIAL_DELETION, &val, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); g_value_unset(&val); g_value_init(&val, G_TYPE_BOOLEAN); g_value_set_boolean(&val, TRUE); device_set_simple_property(dself, PROPERTY_FULL_DELETION, &val, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); g_value_unset(&val); g_value_init(&val, G_TYPE_BOOLEAN); g_value_set_boolean(&val, TRUE); device_set_simple_property(dself, PROPERTY_LEOM, &val, PROPERTY_SURETY_GOOD, PROPERTY_SOURCE_DETECTED); g_value_unset(&val); } static void diskflat_device_open_device( Device *dself, char *device_name, char *device_type, char *device_node) { DiskflatDevice *self = DISKFLAT_DEVICE(dself); VfsDevice *vself = VFS_DEVICE(dself); char *d; DeviceClass *parent_class = DEVICE_CLASS(g_type_class_peek_parent(DISKFLAT_DEVICE_GET_CLASS(dself))); self->filename = g_strdup(device_node); g_debug("device_node: %s", self->filename); parent_class->open_device(dself, device_name, device_type, device_node); /* retrieve the directory */ if ((d = rindex(vself->dir_name, '/')) != NULL) { *d = '\0'; if ((d = rindex(vself->dir_name, '/')) != NULL) { *d = '\0'; } } } static gboolean diskflat_device_start_file_open( Device *dself, dumpfile_t *ji G_GNUC_UNUSED) { if (dself->file >= 1) { device_set_error(dself, g_strdup_printf(_("Can't write more than one file to the diskflat device")), DEVICE_STATUS_VOLUME_ERROR); return FALSE; } dself->file++; return TRUE; } static dumpfile_t * diskflat_device_seek_file( Device *dself, guint requested_file) { VfsDevice *vself = VFS_DEVICE(dself); DiskflatDevice *self = DISKFLAT_DEVICE(dself); dumpfile_t *rval; char header_buffer[VFS_DEVICE_LABEL_SIZE]; int header_buffer_size = sizeof(header_buffer); IoResult result; off_t result_seek; if (device_in_error(dself)) return NULL; if (requested_file > 1) { device_set_error(dself, g_strdup_printf(_("Can't seek to file number above 1")), DEVICE_STATUS_VOLUME_ERROR); return NULL; } /* read_label before start */ if (requested_file == 0 && vself->open_file_fd == -1) { vself->open_file_fd = robust_open(self->filename, O_RDONLY, 0); if (vself->open_file_fd < 0) { if (errno == ENOENT) { device_set_error(dself, g_strdup_printf(_("Couldn't open file %s: %s (unlabeled)"), self->filename, strerror(errno)), DEVICE_STATUS_VOLUME_UNLABELED); rval = g_new(dumpfile_t, 1); fh_init(rval); return rval; } else { device_set_error(dself, g_strdup_printf(_("Couldn't open file %s: %s"), self->filename, strerror(errno)), DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); 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); result_seek = lseek(vself->open_file_fd, requested_file * DISK_BLOCK_BYTES, SEEK_SET); if (result_seek == -1) { device_set_error(dself, g_strdup_printf(_("Error seeking within file: %s"), strerror(errno)), DEVICE_STATUS_DEVICE_ERROR); return NULL; } result = vfs_device_robust_read(vself, header_buffer, &header_buffer_size); if (result == RESULT_NO_DATA) { device_set_error(dself, g_strdup_printf(_("Problem reading Amanda header: empty file")), DEVICE_STATUS_VOLUME_UNLABELED); return NULL; } else if (result != RESULT_SUCCESS) { device_set_error(dself, g_strdup_printf(_("Problem reading Amanda header: %s"), device_error(dself)), DEVICE_STATUS_VOLUME_ERROR); return NULL; } rval = g_new(dumpfile_t, 1); parse_file_header(header_buffer, rval, header_buffer_size); switch (rval->type) { case F_DUMPFILE: case F_CONT_DUMPFILE: case F_SPLIT_DUMPFILE: break; case F_TAPESTART: /* file 0 should have a TAPESTART header; diskflat_device_read_label * uses this */ if (requested_file == 0) break; /* FALLTHROUGH */ default: device_set_error(dself, g_strdup(_("Invalid amanda header while reading file header")), DEVICE_STATUS_VOLUME_ERROR); amfree(rval); return NULL; } /* update our state */ if (requested_file == 0) { dself->header_block_size = header_buffer_size; } else { g_mutex_lock(dself->device_mutex); dself->in_file = TRUE; g_mutex_unlock(dself->device_mutex); } dself->file = requested_file; return rval; } static gboolean diskflat_device_seek_block( Device *dself, guint64 block) { DiskflatDevice *self; VfsDevice *vself; off_t result; self = DISKFLAT_DEVICE(dself); vself = VFS_DEVICE(dself); g_assert(vself->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(vself->open_file_fd, (block) * dself->block_size + 2 * VFS_DEVICE_LABEL_SIZE, SEEK_SET); dself->block = block; if (result == (off_t)(-1)) { device_set_error(dself, g_strdup_printf(_("Error seeking within file: %s"), strerror(errno)), DEVICE_STATUS_DEVICE_ERROR); return FALSE; } return TRUE; } static gboolean diskflat_device_erase( Device *dself) { DiskflatDevice *self = DISKFLAT_DEVICE(dself); VfsDevice *vself = VFS_DEVICE(dself); if (vself->open_file_fd >= 0) { robust_close(vself->open_file_fd); vself->open_file_fd = -1; } if (unlink(self->filename) == -1 && errno != ENOENT) { device_set_error(dself, g_strdup_printf(_("Can't unlink file %s: %s"), self->filename, strerror(errno)), DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); return FALSE; } vself->release_file(dself); dumpfile_free(dself->volume_header); dself->volume_header = NULL; device_set_error(dself, g_strdup("Unlabeled volume"), DEVICE_STATUS_VOLUME_UNLABELED); return TRUE; } static gboolean diskflat_device_finish( Device *dself) { VfsDevice *vself = VFS_DEVICE(dself); gboolean result; DeviceClass *parent_class = DEVICE_CLASS(g_type_class_peek_parent(DISKFLAT_DEVICE_GET_CLASS(dself))); g_debug("Finish DISKFLAT device"); /* Save access mode before parent class messes with it */ if (vself->open_file_fd != -1) { robust_close(vself->open_file_fd); vself->open_file_fd = -1; } result = parent_class->finish(dself); if (!result || device_in_error(dself)) { return FALSE; } return TRUE; } static void diskflat_device_finalize( GObject *gself) { DiskflatDevice *self = DISKFLAT_DEVICE(gself); GObjectClass *parent_class = G_OBJECT_CLASS(g_type_class_peek_parent(DISKFLAT_DEVICE_GET_CLASS(gself))); if (parent_class->finalize) { parent_class->finalize(gself); } amfree(self->filename); } static void diskflat_release_file( Device *dselfi G_GNUC_UNUSED) { return; } void diskflat_update_volume_size( Device *dself) { VfsDevice *vself = VFS_DEVICE(dself); DiskflatDevice *self = DISKFLAT_DEVICE(dself); struct stat stat_buf; /* stat the file */ if (stat(self->filename, &stat_buf) < 0) { g_warning("Couldn't stat file %s: %s", self->filename, strerror(errno)); return; } vself->volume_bytes += stat_buf.st_size; return; } static gboolean diskflat_clear_and_prepare_label( Device *dself, char *label, char *timestamp) { dumpfile_t *label_header; VfsDevice *vself = VFS_DEVICE(dself); DiskflatDevice *self = DISKFLAT_DEVICE(dself); vself->open_file_fd = robust_open(self->filename, O_CREAT | O_WRONLY, VFS_DEVICE_CREAT_MODE); if (vself->open_file_fd < 0) { device_set_error(dself, g_strdup_printf(_("Can't open file %s: %s"), self->filename, strerror(errno)), DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); return FALSE; } label_header = make_tapestart_header(dself, label, timestamp); if (!vfs_write_amanda_header(vself, label_header)) { /* vfs_write_amanda_header sets error status if necessary */ dumpfile_free(label_header); return FALSE; } dumpfile_free(dself->volume_header); if (ftruncate(vself->open_file_fd, VFS_DEVICE_LABEL_SIZE) == -1) { device_set_error(dself, g_strdup_printf("ftruncate of '%s' failed: %s", self->filename, strerror(errno)), DEVICE_STATUS_DEVICE_ERROR | DEVICE_STATUS_VOLUME_ERROR); return FALSE; } dself->header_block_size = VFS_DEVICE_LABEL_SIZE; dself->volume_header = label_header; dself->file = 0; vself->volume_bytes = VFS_DEVICE_LABEL_SIZE; return TRUE; } static gboolean diskflat_validate( Device *dself G_GNUC_UNUSED) { return TRUE; }