#include "dm-log.h"
#include "dm-io.h"
+#define DM_MSG_PREFIX "mirror log"
+
static LIST_HEAD(_log_types);
static DEFINE_SPINLOCK(_lock);
spin_lock(&_lock);
if (type->use_count)
- DMWARN("Attempt to unregister a log type that is still in use");
+ DMWARN("Unregister failed: log type '%s' still in use",
+ type->name);
else
list_del(&type->list);
unsigned bitset_uint32_count;
uint32_t *clean_bits;
uint32_t *sync_bits;
- uint32_t *recovering_bits; /* FIXME: this seems excessive */
+ uint32_t *recovering_bits;
int sync_search;
FORCESYNC, /* Force a sync to happen */
} sync;
+ int failure_response;
+
/*
* Disk log fields
*/
+ int log_dev_failed;
struct dm_dev *log_dev;
struct log_header header;
struct io_region header_location;
struct log_header *disk_header;
-
- struct io_region bits_location;
};
/*
log->disk_header, &ebits);
}
-/*----------------------------------------------------------------
- * Bits IO
- *--------------------------------------------------------------*/
-static int read_bits(struct log_c *log)
-{
- int r;
- unsigned long ebits;
-
- r = dm_io_sync_vm(1, &log->bits_location, READ,
- log->clean_bits, &ebits);
- if (r)
- return r;
-
- return 0;
-}
-
-static int write_bits(struct log_c *log)
-{
- unsigned long ebits;
- return dm_io_sync_vm(1, &log->bits_location, WRITE,
- log->clean_bits, &ebits);
-}
-
/*----------------------------------------------------------------
* core log constructor/destructor
*
- * argv contains region_size followed optionally by [no]sync
+ * argv contains 1 - 3 arguments:
+ * <region_size> [[no]sync] [block_on_error]
*--------------------------------------------------------------*/
#define BYTE_SHIFT 3
-static int core_ctr(struct dirty_log *log, struct dm_target *ti,
- unsigned int argc, char **argv)
+static int create_log_context(struct dirty_log *log, struct dm_target *ti,
+ unsigned int argc, char **argv,
+ struct dm_dev *dev)
{
+ int i;
enum sync sync = DEFAULTSYNC;
-
+ int failure_response = DMLOG_IOERR_IGNORE;
struct log_c *lc;
uint32_t region_size;
unsigned int region_count;
- size_t bitset_size;
+ size_t bitset_size, buf_size;
- if (argc < 1 || argc > 2) {
+ if (argc < 1 || argc > 3) {
DMWARN("wrong number of arguments to mirror log");
return -EINVAL;
}
- if (argc > 1) {
- if (!strcmp(argv[1], "sync"))
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "sync"))
sync = FORCESYNC;
- else if (!strcmp(argv[1], "nosync"))
+ else if (!strcmp(argv[i], "nosync"))
sync = NOSYNC;
+ else if (!strcmp(argv[i], "block_on_error"))
+ failure_response = DMLOG_IOERR_BLOCK;
else {
DMWARN("unrecognised sync argument to mirror log: %s",
- argv[1]);
+ argv[i]);
return -EINVAL;
}
}
lc->region_size = region_size;
lc->region_count = region_count;
lc->sync = sync;
+ lc->failure_response = failure_response;
/*
* Work out how many "unsigned long"s we need to hold the bitset.
*/
bitset_size = dm_round_up(region_count,
- sizeof(unsigned long) << BYTE_SHIFT);
+ sizeof(*lc->clean_bits) << BYTE_SHIFT);
bitset_size >>= BYTE_SHIFT;
- lc->bitset_uint32_count = bitset_size / 4;
- lc->clean_bits = vmalloc(bitset_size);
- if (!lc->clean_bits) {
- DMWARN("couldn't allocate clean bitset");
- kfree(lc);
- return -ENOMEM;
+ lc->bitset_uint32_count = bitset_size / sizeof(*lc->clean_bits);
+
+ /*
+ * Disk log?
+ */
+ if (!dev) {
+ lc->clean_bits = vmalloc(bitset_size);
+ if (!lc->clean_bits) {
+ DMWARN("couldn't allocate clean bitset");
+ kfree(lc);
+ return -ENOMEM;
+ }
+ lc->disk_header = NULL;
+ } else {
+ lc->log_dev = dev;
+ lc->log_dev_failed = 0;
+ lc->header_location.bdev = lc->log_dev->bdev;
+ lc->header_location.sector = 0;
+
+ /*
+ * Buffer holds both header and bitset.
+ */
+ buf_size = dm_round_up((LOG_OFFSET << SECTOR_SHIFT) +
+ bitset_size, ti->limits.hardsect_size);
+ lc->header_location.count = buf_size >> SECTOR_SHIFT;
+
+ lc->disk_header = vmalloc(buf_size);
+ if (!lc->disk_header) {
+ DMWARN("couldn't allocate disk log buffer");
+ kfree(lc);
+ return -ENOMEM;
+ }
+
+ lc->clean_bits = (void *)lc->disk_header +
+ (LOG_OFFSET << SECTOR_SHIFT);
}
+
memset(lc->clean_bits, -1, bitset_size);
lc->sync_bits = vmalloc(bitset_size);
if (!lc->sync_bits) {
DMWARN("couldn't allocate sync bitset");
- vfree(lc->clean_bits);
+ if (!dev)
+ vfree(lc->clean_bits);
+ vfree(lc->disk_header);
kfree(lc);
return -ENOMEM;
}
lc->recovering_bits = vmalloc(bitset_size);
if (!lc->recovering_bits) {
- DMWARN("couldn't allocate sync bitset");
+ DMWARN("couldn't allocate recovering bitset");
vfree(lc->sync_bits);
- vfree(lc->clean_bits);
+ if (!dev)
+ vfree(lc->clean_bits);
+ vfree(lc->disk_header);
kfree(lc);
return -ENOMEM;
}
memset(lc->recovering_bits, 0, bitset_size);
lc->sync_search = 0;
log->context = lc;
+
return 0;
}
-static void core_dtr(struct dirty_log *log)
+static int core_ctr(struct dirty_log *log, struct dm_target *ti,
+ unsigned int argc, char **argv)
+{
+ return create_log_context(log, ti, argc, argv, NULL);
+}
+
+static void destroy_log_context(struct log_c *lc)
{
- struct log_c *lc = (struct log_c *) log->context;
- vfree(lc->clean_bits);
vfree(lc->sync_bits);
vfree(lc->recovering_bits);
kfree(lc);
}
+static void core_dtr(struct dirty_log *log)
+{
+ struct log_c *lc = (struct log_c *) log->context;
+
+ vfree(lc->clean_bits);
+ destroy_log_context(lc);
+}
+
/*----------------------------------------------------------------
* disk log constructor/destructor
*
- * argv contains log_device region_size followed optionally by [no]sync
+ * argv contains 2 - 4 arguments:
+ * <log_device> <region_size> [[no]sync] [block_on_error]
*--------------------------------------------------------------*/
static int disk_ctr(struct dirty_log *log, struct dm_target *ti,
unsigned int argc, char **argv)
{
int r;
- size_t size;
- struct log_c *lc;
struct dm_dev *dev;
- if (argc < 2 || argc > 3) {
+ if (argc < 2 || argc > 4) {
DMWARN("wrong number of arguments to disk mirror log");
return -EINVAL;
}
if (r)
return r;
- r = core_ctr(log, ti, argc - 1, argv + 1);
+ r = create_log_context(log, ti, argc - 1, argv + 1, dev);
if (r) {
dm_put_device(ti, dev);
return r;
}
- lc = (struct log_c *) log->context;
- lc->log_dev = dev;
-
- /* setup the disk header fields */
- lc->header_location.bdev = lc->log_dev->bdev;
- lc->header_location.sector = 0;
- lc->header_location.count = 1;
-
- /*
- * We can't read less than this amount, even though we'll
- * not be using most of this space.
- */
- lc->disk_header = vmalloc(1 << SECTOR_SHIFT);
- if (!lc->disk_header)
- goto bad;
-
- /* setup the disk bitset fields */
- lc->bits_location.bdev = lc->log_dev->bdev;
- lc->bits_location.sector = LOG_OFFSET;
-
- size = dm_round_up(lc->bitset_uint32_count * sizeof(uint32_t),
- 1 << SECTOR_SHIFT);
- lc->bits_location.count = size >> SECTOR_SHIFT;
return 0;
-
- bad:
- dm_put_device(ti, lc->log_dev);
- core_dtr(log);
- return -ENOMEM;
}
static void disk_dtr(struct dirty_log *log)
{
struct log_c *lc = (struct log_c *) log->context;
+
dm_put_device(lc->ti, lc->log_dev);
vfree(lc->disk_header);
- core_dtr(log);
+ destroy_log_context(lc);
}
static int count_bits32(uint32_t *addr, unsigned size)
return count;
}
+static void fail_log_device(struct log_c *lc)
+{
+ if (lc->log_dev_failed)
+ return;
+
+ lc->log_dev_failed = 1;
+ if (lc->failure_response == DMLOG_IOERR_BLOCK)
+ dm_table_event(lc->ti->table);
+}
+
+static void restore_log_device(struct log_c *lc)
+{
+ lc->log_dev_failed = 0;
+}
+
static int disk_resume(struct dirty_log *log)
{
int r;
struct log_c *lc = (struct log_c *) log->context;
size_t size = lc->bitset_uint32_count * sizeof(uint32_t);
- /* read the disk header */
- r = read_header(lc);
- if (r)
- return r;
-
- /* read the bits */
- r = read_bits(lc);
- if (r)
- return r;
+ /*
+ * Read the disk header, but only if we know it is good.
+ * Assume the worst in the event of failure.
+ */
+ if (!lc->log_dev_failed && read_header(lc)) {
+ DMWARN("Failed to read header on mirror log device, %s",
+ lc->log_dev->name);
+ fail_log_device(lc);
+ lc->header.nr_regions = 0;
+ }
- /* set or clear any new bits */
+ /* set or clear any new bits -- device has grown */
if (lc->sync == NOSYNC)
for (i = lc->header.nr_regions; i < lc->region_count; i++)
/* FIXME: amazingly inefficient */
/* FIXME: amazingly inefficient */
log_clear_bit(lc, lc->clean_bits, i);
+ /* clear any old bits -- device has shrunk */
+ for (i = lc->region_count; i % (sizeof(*lc->clean_bits) << BYTE_SHIFT); i++)
+ log_clear_bit(lc, lc->clean_bits, i);
+
/* copy clean across to sync */
memcpy(lc->sync_bits, lc->clean_bits, size);
lc->sync_count = count_bits32(lc->clean_bits, lc->bitset_uint32_count);
-
- /* write the bits */
- r = write_bits(lc);
- if (r)
- return r;
+ lc->sync_search = 0;
/* set the correct number of regions in the header */
lc->header.nr_regions = lc->region_count;
/* write the new header */
- return write_header(lc);
+ r = write_header(lc);
+ if (r) {
+ DMWARN("Failed to write header on mirror log device, %s",
+ lc->log_dev->name);
+ fail_log_device(lc);
+ } else
+ restore_log_device(lc);
+
+ return r;
}
static uint32_t core_get_region_size(struct dirty_log *log)
return lc->region_size;
}
+static int core_resume(struct dirty_log *log)
+{
+ struct log_c *lc = (struct log_c *) log->context;
+ lc->sync_search = 0;
+ return 0;
+}
+
static int core_is_clean(struct dirty_log *log, region_t region)
{
struct log_c *lc = (struct log_c *) log->context;
return 0;
}
+static int disk_presuspend(struct dirty_log *log)
+{
+ return 0;
+}
+
static int disk_flush(struct dirty_log *log)
{
int r;
if (!lc->touched)
return 0;
- r = write_bits(lc);
- if (!r)
+ r = write_header(lc);
+ if (r)
+ fail_log_device(lc);
+ else {
lc->touched = 0;
-
+ restore_log_device(lc);
+ }
return r;
}
return 1;
}
-static void core_complete_resync_work(struct dirty_log *log, region_t region,
- int success)
+static void core_set_region_sync(struct dirty_log *log, region_t region,
+ int in_sync)
{
struct log_c *lc = (struct log_c *) log->context;
log_clear_bit(lc, lc->recovering_bits, region);
- if (success) {
+ if (in_sync) {
log_set_bit(lc, lc->sync_bits, region);
lc->sync_count++;
- }
+ } else if (log_test_bit(lc->sync_bits, region)) {
+ lc->sync_count--;
+ log_clear_bit(lc, lc->sync_bits, region);
+ }
}
static region_t core_get_sync_count(struct dirty_log *log)
char *result, unsigned int maxlen)
{
int sz = 0;
+ int params;
struct log_c *lc = log->context;
switch(status) {
case STATUSTYPE_INFO:
+ DMEMIT("1 core");
break;
case STATUSTYPE_TABLE:
- DMEMIT("%s %u %u ", log->type->name,
- lc->sync == DEFAULTSYNC ? 1 : 2, lc->region_size);
+ params = (lc->sync == DEFAULTSYNC) ? 1 : 2;
+ params += (lc->failure_response == DMLOG_IOERR_BLOCK) ? 1 : 0;
+
+ DMEMIT("%s %d %u ", log->type->name, params, lc->region_size);
DMEMIT_SYNC;
+ if (lc->failure_response == DMLOG_IOERR_BLOCK)
+ DMEMIT("block_on_error ");
}
return sz;
char *result, unsigned int maxlen)
{
int sz = 0;
- char buffer[16];
+ int params;
struct log_c *lc = log->context;
switch(status) {
case STATUSTYPE_INFO:
+ DMEMIT("3 disk %s %c", lc->log_dev->name,
+ lc->log_dev_failed ? 'D' : 'A');
break;
case STATUSTYPE_TABLE:
- format_dev_t(buffer, lc->log_dev->bdev->bd_dev);
- DMEMIT("%s %u %s %u ", log->type->name,
- lc->sync == DEFAULTSYNC ? 2 : 3, buffer,
+ params = (lc->sync == DEFAULTSYNC) ? 2 : 3;
+ params += (lc->failure_response == DMLOG_IOERR_BLOCK) ? 1 : 0;
+
+ DMEMIT("%s %d %s %u ", log->type->name,
+ params,
+ lc->log_dev->name,
lc->region_size);
DMEMIT_SYNC;
+ if (lc->failure_response == DMLOG_IOERR_BLOCK)
+ DMEMIT("block_on_error ");
}
return sz;
}
+static int core_get_failure_response(struct dirty_log *log)
+{
+ struct log_c *lc = log->context;
+
+ return lc->failure_response;
+}
+
static struct dirty_log_type _core_type = {
.name = "core",
.module = THIS_MODULE,
.ctr = core_ctr,
.dtr = core_dtr,
+ .resume = core_resume,
.get_region_size = core_get_region_size,
.is_clean = core_is_clean,
.in_sync = core_in_sync,
.mark_region = core_mark_region,
.clear_region = core_clear_region,
.get_resync_work = core_get_resync_work,
- .complete_resync_work = core_complete_resync_work,
+ .set_region_sync = core_set_region_sync,
.get_sync_count = core_get_sync_count,
.status = core_status,
+ .get_failure_response = core_get_failure_response,
};
static struct dirty_log_type _disk_type = {
.module = THIS_MODULE,
.ctr = disk_ctr,
.dtr = disk_dtr,
- .suspend = disk_flush,
+ .presuspend = disk_presuspend,
+ .postsuspend = disk_flush,
.resume = disk_resume,
.get_region_size = core_get_region_size,
.is_clean = core_is_clean,
.mark_region = core_mark_region,
.clear_region = core_clear_region,
.get_resync_work = core_get_resync_work,
- .complete_resync_work = core_complete_resync_work,
+ .set_region_sync = core_set_region_sync,
.get_sync_count = core_get_sync_count,
.status = disk_status,
+ .get_failure_response = core_get_failure_response,
};
int __init dm_dirty_log_init(void)