/*
* 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 <sam@couter.id.au>
*/
#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;
}