blob: 7c201a51aa26a4a355508b0f86a2897485d5c94a [file] [log] [blame]
/*
* do_mounts_dm.c
* Copyright (C) 2010 The Chromium OS Authors <chromium-os-dev@chromium.org>
* Based on do_mounts_md.c
*
* This file is released under the GPLv2.
*/
#include <linux/async.h>
#include <linux/ctype.h>
#include <linux/device-mapper.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/delay.h>
#include "do_mounts.h"
#define DM_MAX_DEVICES 256
#define DM_MAX_TARGETS 256
#define DM_MAX_NAME 32
#define DM_MAX_UUID 129
#define DM_NO_UUID "none"
#define DM_MSG_PREFIX "init"
#define DMERR_PARSE(fmt, args...) \
DMERR("failed to parse " fmt " for device %s<%lu>", args)
/* Separators used for parsing the dm= argument. */
#define DM_FIELD_SEP " "
#define DM_LINE_SEP ","
#define DM_ANY_SEP DM_FIELD_SEP DM_LINE_SEP
/* See Documentation/device-mapper/boot.txt for dm="..." format details. */
struct dm_setup_table {
sector_t begin;
sector_t length;
char *type;
char *params;
/* simple singly linked list */
struct dm_setup_table *next;
};
struct dm_device {
int minor;
int ro;
char name[DM_MAX_NAME];
char uuid[DM_MAX_UUID];
unsigned long num_tables;
struct dm_setup_table *table;
int table_count;
struct dm_device *next;
};
struct dm_option {
char *start;
char *next;
size_t len;
char delim;
};
static struct {
unsigned long num_devices;
char *str;
} dm_setup_args __initdata;
static int dm_early_setup __initdata;
static int __init get_dm_option(struct dm_option *opt, const char *accept)
{
char *str = opt->next;
char *endp;
if (!str)
return 0;
str = skip_spaces(str);
opt->start = str;
endp = strpbrk(str, accept);
if (!endp) { /* act like strchrnul */
opt->len = strlen(str);
endp = str + opt->len;
} else {
opt->len = endp - str;
}
opt->delim = *endp;
if (*endp == 0) {
/* Don't advance past the nul. */
opt->next = endp;
} else {
opt->next = endp + 1;
}
return opt->len != 0;
}
static int __init get_dm_option_u64(struct dm_option *opt, const char *sep,
unsigned long long *result)
{
char buf[32];
if (!get_dm_option(opt, sep))
return -EINVAL;
strlcpy(buf, opt->start, min(sizeof(buf), opt->len + 1));
return kstrtoull(buf, 0, result);
}
static void __init dm_setup_cleanup(struct dm_device *devices)
{
struct dm_device *dev = devices;
while (dev) {
struct dm_device *old_dev = dev;
struct dm_setup_table *table = dev->table;
while (table) {
struct dm_setup_table *old_table = table;
kfree(table->type);
kfree(table->params);
table = table->next;
kfree(old_table);
dev->table_count--;
}
WARN_ON(dev->table_count);
dev = dev->next;
kfree(old_dev);
}
}
static char * __init dm_parse_device(struct dm_device *dev, char *str,
unsigned long idx)
{
struct dm_option opt;
size_t len;
unsigned long long num_tables;
/* Grab the logical name of the device to be exported to udev */
opt.next = str;
if (!get_dm_option(&opt, DM_FIELD_SEP)) {
DMERR_PARSE("name", "", idx);
goto parse_fail;
}
len = min(opt.len + 1, sizeof(dev->name));
strlcpy(dev->name, opt.start, len); /* includes nul */
/* Grab the UUID value or "none" */
if (!get_dm_option(&opt, DM_FIELD_SEP)) {
DMERR_PARSE("uuid", dev->name, idx);
goto parse_fail;
}
len = min(opt.len + 1, sizeof(dev->uuid));
strlcpy(dev->uuid, opt.start, len);
/* Determine if the table/device will be read only or read-write */
get_dm_option(&opt, DM_ANY_SEP);
if (!strncmp("ro", opt.start, opt.len)) {
dev->ro = 1;
} else if (!strncmp("rw", opt.start, opt.len)) {
dev->ro = 0;
} else {
DMERR_PARSE("table mode", dev->name, idx);
goto parse_fail;
}
/* Optional number field */
if (opt.delim == DM_FIELD_SEP[0]) {
if (get_dm_option_u64(&opt, DM_LINE_SEP, &num_tables)) {
DMERR_PARSE("number of tables", dev->name, idx);
goto parse_fail;
}
} else {
num_tables = 1;
}
if (num_tables > DM_MAX_TARGETS) {
DMERR_PARSE("too many tables (%llu > %d)", num_tables,
DM_MAX_TARGETS, dev->name, idx);
}
dev->num_tables = num_tables;
return opt.next;
parse_fail:
return NULL;
}
static char * __init dm_parse_tables(struct dm_device *dev, char *str,
unsigned long idx)
{
struct dm_option opt;
struct dm_setup_table **table = &dev->table;
unsigned long num_tables = dev->num_tables;
unsigned long i;
unsigned long long value;
/*
* Tables are defined as per the normal table format but with a
* comma as a newline separator.
*/
opt.next = str;
for (i = 0; i < num_tables; i++) {
*table = kzalloc(sizeof(struct dm_setup_table), GFP_KERNEL);
if (!*table) {
DMERR_PARSE("table %lu (out of memory)", i, dev->name,
idx);
goto parse_fail;
}
dev->table_count++;
if (get_dm_option_u64(&opt, DM_FIELD_SEP, &value)) {
DMERR_PARSE("starting sector for table %lu", i,
dev->name, idx);
goto parse_fail;
}
(*table)->begin = value;
if (get_dm_option_u64(&opt, DM_FIELD_SEP, &value)) {
DMERR_PARSE("length for table %lu", i, dev->name, idx);
goto parse_fail;
}
(*table)->length = value;
if (get_dm_option(&opt, DM_FIELD_SEP))
(*table)->type = kstrndup(opt.start, opt.len,
GFP_KERNEL);
if (!((*table)->type)) {
DMERR_PARSE("type for table %lu", i, dev->name, idx);
goto parse_fail;
}
if (get_dm_option(&opt, DM_LINE_SEP))
(*table)->params = kstrndup(opt.start, opt.len,
GFP_KERNEL);
if (!((*table)->params)) {
DMERR_PARSE("params for table %lu", i, dev->name, idx);
goto parse_fail;
}
table = &((*table)->next);
}
DMDEBUG("tables parsed: %d", dev->table_count);
return opt.next;
parse_fail:
return NULL;
}
static struct dm_device * __init dm_parse_args(void)
{
struct dm_device *devices = NULL;
struct dm_device **tail = &devices;
struct dm_device *dev;
char *str = dm_setup_args.str;
unsigned long num_devices = dm_setup_args.num_devices;
unsigned long i;
if (!str)
return NULL;
for (i = 0; i < num_devices; i++) {
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
DMERR("failed to allocated memory for device %lu", i);
goto error;
}
*tail = dev;
tail = &dev->next;
/*
* devices are given minor numbers 0 - n-1
* in the order they are found in the arg
* string.
*/
dev->minor = i;
str = dm_parse_device(dev, str, i);
if (!str) /* NULL indicates error in parsing, bail */
goto error;
str = dm_parse_tables(dev, str, i);
if (!str)
goto error;
}
return devices;
error:
dm_setup_cleanup(devices);
return NULL;
}
/*
* Parse the command-line parameters given our kernel, but do not
* actually try to invoke the DM device now; that is handled by
* dm_setup_drives after the low-level disk drivers have initialised.
* dm format is described at the top of the file.
*
* Because dm minor numbers are assigned in assending order starting with 0,
* You can assume the first device is /dev/dm-0, the next device is /dev/dm-1,
* and so forth.
*/
static int __init dm_setup(char *str)
{
struct dm_option opt;
unsigned long long num_devices;
if (!str) {
DMERR("setup str is NULL");
goto parse_fail;
}
DMDEBUG("Want to parse \"%s\"", str);
opt.next = str;
if (get_dm_option_u64(&opt, DM_FIELD_SEP, &num_devices))
goto parse_fail;
str = opt.next;
if (num_devices > DM_MAX_DEVICES) {
DMERR("too many devices %llu > %d", num_devices,
DM_MAX_DEVICES);
}
dm_setup_args.num_devices = num_devices;
dm_setup_args.str = str;
DMINFO("will configure %lu device%s", dm_setup_args.num_devices,
dm_setup_args.num_devices == 1 ? "" : "s");
dm_early_setup = 1;
return 1;
parse_fail:
DMWARN("Invalid arguments supplied to dm=.");
return 0;
}
static void __init dm_setup_drives(void)
{
struct mapped_device *md = NULL;
struct dm_table *tables = NULL;
struct dm_setup_table *table;
struct dm_device *dev;
char *uuid;
fmode_t fmode = FMODE_READ;
struct dm_device *devices;
devices = dm_parse_args();
for (dev = devices; dev; dev = dev->next) {
if (dm_create(dev->minor, &md)) {
DMERR("failed to create device %s", dev->name);
goto fail;
}
DMDEBUG("created device '%s'", dm_device_name(md));
/*
* In addition to flagging the table below, the disk must be
* set explicitly ro/rw.
*/
set_disk_ro(dm_disk(md), dev->ro);
if (!dev->ro)
fmode |= FMODE_WRITE;
if (dm_table_create(&tables, fmode, dev->table_count, md)) {
DMERR("failed to create device %s tables", dev->name);
goto fail_put;
}
for (table = dev->table; table; table = table->next) {
DMINFO("device %s adding table '%llu %llu %s %s'",
dev->name,
(unsigned long long) table->begin,
(unsigned long long) table->length,
table->type, table->params);
if (dm_table_add_target(tables, table->type,
table->begin,
table->length,
table->params)) {
DMERR("failed to add table to device %s",
dev->name);
goto fail_put;
}
}
dm_lock_md_type(md);
if (dm_table_complete(tables)) {
DMERR("failed to complete device %s tables",
dev->name);
dm_unlock_md_type(md);
goto fail_put;
}
dm_unlock_md_type(md);
/* Suspend the device so that we can bind it to the tables. */
if (dm_suspend(md, 0)) {
DMERR("failed to suspend device %s pre-bind",
dev->name);
goto fail_put;
}
/*
* Bind the tables to the device. This is the only way
* to associate md->map with the tables and set the disk
* capacity directly.
*/
if (dm_swap_table(md, tables)) { /* should return NULL. */
DMERR("failed to bind device %s to tables",
dev->name);
goto fail_put;
}
/* Finally, resume and the device should be ready. */
if (dm_resume(md)) {
DMERR("failed to resume device %s", dev->name);
goto fail_put;
}
/* Export the dm device via the ioctl interface */
if (!strcmp(DM_NO_UUID, dev->uuid))
uuid = NULL;
if (dm_ioctl_export(md, dev->name, uuid)) {
DMERR("failed to export device %s", dev->name);
goto fail_put;
}
DMINFO("dm-%d (%s) is ready", dev->minor, dev->name);
}
dm_setup_cleanup(devices);
return;
fail_put:
dm_put(md);
fail:
DMERR("starting dm-%d (%s) failed", dev->minor, dev->name);
dm_setup_cleanup(devices);
}
__setup("dm=", dm_setup);
void __init dm_run_setup(void)
{
if (!dm_early_setup)
return;
DMINFO("attempting early device configuration.");
dm_setup_drives();
}