| /* |
| * 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(); |
| } |