linux/drivers/md/dm-init.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2
   3/*
   4 * dm-init.c
   5 * Copyright (C) 2017 The Chromium OS Authors <chromium-os-dev@chromium.org>
   6 *
   7 * This file is released under the GPLv2.
   8 */
   9
  10#include <linux/ctype.h>
  11#include <linux/device.h>
  12#include <linux/device-mapper.h>
  13#include <linux/init.h>
  14#include <linux/list.h>
  15#include <linux/moduleparam.h>
  16
  17#define DM_MSG_PREFIX "init"
  18#define DM_MAX_DEVICES 256
  19#define DM_MAX_TARGETS 256
  20#define DM_MAX_STR_SIZE 4096
  21
  22static char *create;
  23
  24/*
  25 * Format: dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
  26 * Table format: <start_sector> <num_sectors> <target_type> <target_args>
  27 *
  28 * See Documentation/admin-guide/device-mapper/dm-init.rst for dm-mod.create="..." format
  29 * details.
  30 */
  31
  32struct dm_device {
  33        struct dm_ioctl dmi;
  34        struct dm_target_spec *table[DM_MAX_TARGETS];
  35        char *target_args_array[DM_MAX_TARGETS];
  36        struct list_head list;
  37};
  38
  39static const char * const dm_allowed_targets[] __initconst = {
  40        "crypt",
  41        "delay",
  42        "linear",
  43        "snapshot-origin",
  44        "striped",
  45        "verity",
  46};
  47
  48static int __init dm_verify_target_type(const char *target)
  49{
  50        unsigned int i;
  51
  52        for (i = 0; i < ARRAY_SIZE(dm_allowed_targets); i++) {
  53                if (!strcmp(dm_allowed_targets[i], target))
  54                        return 0;
  55        }
  56        return -EINVAL;
  57}
  58
  59static void __init dm_setup_cleanup(struct list_head *devices)
  60{
  61        struct dm_device *dev, *tmp;
  62        unsigned int i;
  63
  64        list_for_each_entry_safe(dev, tmp, devices, list) {
  65                list_del(&dev->list);
  66                for (i = 0; i < dev->dmi.target_count; i++) {
  67                        kfree(dev->table[i]);
  68                        kfree(dev->target_args_array[i]);
  69                }
  70                kfree(dev);
  71        }
  72}
  73
  74/**
  75 * str_field_delimit - delimit a string based on a separator char.
  76 * @str: the pointer to the string to delimit.
  77 * @separator: char that delimits the field
  78 *
  79 * Find a @separator and replace it by '\0'.
  80 * Remove leading and trailing spaces.
  81 * Return the remainder string after the @separator.
  82 */
  83static char __init *str_field_delimit(char **str, char separator)
  84{
  85        char *s;
  86
  87        /* TODO: add support for escaped characters */
  88        *str = skip_spaces(*str);
  89        s = strchr(*str, separator);
  90        /* Delimit the field and remove trailing spaces */
  91        if (s)
  92                *s = '\0';
  93        *str = strim(*str);
  94        return s ? ++s : NULL;
  95}
  96
  97/**
  98 * dm_parse_table_entry - parse a table entry
  99 * @dev: device to store the parsed information.
 100 * @str: the pointer to a string with the format:
 101 *      <start_sector> <num_sectors> <target_type> <target_args>[, ...]
 102 *
 103 * Return the remainder string after the table entry, i.e, after the comma which
 104 * delimits the entry or NULL if reached the end of the string.
 105 */
 106static char __init *dm_parse_table_entry(struct dm_device *dev, char *str)
 107{
 108        const unsigned int n = dev->dmi.target_count - 1;
 109        struct dm_target_spec *sp;
 110        unsigned int i;
 111        /* fields:  */
 112        char *field[4];
 113        char *next;
 114
 115        field[0] = str;
 116        /* Delimit first 3 fields that are separated by space */
 117        for (i = 0; i < ARRAY_SIZE(field) - 1; i++) {
 118                field[i + 1] = str_field_delimit(&field[i], ' ');
 119                if (!field[i + 1])
 120                        return ERR_PTR(-EINVAL);
 121        }
 122        /* Delimit last field that can be terminated by comma */
 123        next = str_field_delimit(&field[i], ',');
 124
 125        sp = kzalloc(sizeof(*sp), GFP_KERNEL);
 126        if (!sp)
 127                return ERR_PTR(-ENOMEM);
 128        dev->table[n] = sp;
 129
 130        /* start_sector */
 131        if (kstrtoull(field[0], 0, &sp->sector_start))
 132                return ERR_PTR(-EINVAL);
 133        /* num_sector */
 134        if (kstrtoull(field[1], 0, &sp->length))
 135                return ERR_PTR(-EINVAL);
 136        /* target_type */
 137        strscpy(sp->target_type, field[2], sizeof(sp->target_type));
 138        if (dm_verify_target_type(sp->target_type)) {
 139                DMERR("invalid type \"%s\"", sp->target_type);
 140                return ERR_PTR(-EINVAL);
 141        }
 142        /* target_args */
 143        dev->target_args_array[n] = kstrndup(field[3], DM_MAX_STR_SIZE,
 144                                             GFP_KERNEL);
 145        if (!dev->target_args_array[n])
 146                return ERR_PTR(-ENOMEM);
 147
 148        return next;
 149}
 150
 151/**
 152 * dm_parse_table - parse "dm-mod.create=" table field
 153 * @dev: device to store the parsed information.
 154 * @str: the pointer to a string with the format:
 155 *      <table>[,<table>+]
 156 */
 157static int __init dm_parse_table(struct dm_device *dev, char *str)
 158{
 159        char *table_entry = str;
 160
 161        while (table_entry) {
 162                DMDEBUG("parsing table \"%s\"", str);
 163                if (++dev->dmi.target_count > DM_MAX_TARGETS) {
 164                        DMERR("too many targets %u > %d",
 165                              dev->dmi.target_count, DM_MAX_TARGETS);
 166                        return -EINVAL;
 167                }
 168                table_entry = dm_parse_table_entry(dev, table_entry);
 169                if (IS_ERR(table_entry)) {
 170                        DMERR("couldn't parse table");
 171                        return PTR_ERR(table_entry);
 172                }
 173        }
 174
 175        return 0;
 176}
 177
 178/**
 179 * dm_parse_device_entry - parse a device entry
 180 * @dev: device to store the parsed information.
 181 * @str: the pointer to a string with the format:
 182 *      name,uuid,minor,flags,table[; ...]
 183 *
 184 * Return the remainder string after the table entry, i.e, after the semi-colon
 185 * which delimits the entry or NULL if reached the end of the string.
 186 */
 187static char __init *dm_parse_device_entry(struct dm_device *dev, char *str)
 188{
 189        /* There are 5 fields: name,uuid,minor,flags,table; */
 190        char *field[5];
 191        unsigned int i;
 192        char *next;
 193
 194        field[0] = str;
 195        /* Delimit first 4 fields that are separated by comma */
 196        for (i = 0; i < ARRAY_SIZE(field) - 1; i++) {
 197                field[i+1] = str_field_delimit(&field[i], ',');
 198                if (!field[i+1])
 199                        return ERR_PTR(-EINVAL);
 200        }
 201        /* Delimit last field that can be delimited by semi-colon */
 202        next = str_field_delimit(&field[i], ';');
 203
 204        /* name */
 205        strscpy(dev->dmi.name, field[0], sizeof(dev->dmi.name));
 206        /* uuid */
 207        strscpy(dev->dmi.uuid, field[1], sizeof(dev->dmi.uuid));
 208        /* minor */
 209        if (strlen(field[2])) {
 210                if (kstrtoull(field[2], 0, &dev->dmi.dev))
 211                        return ERR_PTR(-EINVAL);
 212                dev->dmi.flags |= DM_PERSISTENT_DEV_FLAG;
 213        }
 214        /* flags */
 215        if (!strcmp(field[3], "ro"))
 216                dev->dmi.flags |= DM_READONLY_FLAG;
 217        else if (strcmp(field[3], "rw"))
 218                return ERR_PTR(-EINVAL);
 219        /* table */
 220        if (dm_parse_table(dev, field[4]))
 221                return ERR_PTR(-EINVAL);
 222
 223        return next;
 224}
 225
 226/**
 227 * dm_parse_devices - parse "dm-mod.create=" argument
 228 * @devices: list of struct dm_device to store the parsed information.
 229 * @str: the pointer to a string with the format:
 230 *      <device>[;<device>+]
 231 */
 232static int __init dm_parse_devices(struct list_head *devices, char *str)
 233{
 234        unsigned long ndev = 0;
 235        struct dm_device *dev;
 236        char *device = str;
 237
 238        DMDEBUG("parsing \"%s\"", str);
 239        while (device) {
 240                dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 241                if (!dev)
 242                        return -ENOMEM;
 243                list_add_tail(&dev->list, devices);
 244
 245                if (++ndev > DM_MAX_DEVICES) {
 246                        DMERR("too many devices %lu > %d",
 247                              ndev, DM_MAX_DEVICES);
 248                        return -EINVAL;
 249                }
 250
 251                device = dm_parse_device_entry(dev, device);
 252                if (IS_ERR(device)) {
 253                        DMERR("couldn't parse device");
 254                        return PTR_ERR(device);
 255                }
 256        }
 257
 258        return 0;
 259}
 260
 261/**
 262 * dm_init_init - parse "dm-mod.create=" argument and configure drivers
 263 */
 264static int __init dm_init_init(void)
 265{
 266        struct dm_device *dev;
 267        LIST_HEAD(devices);
 268        char *str;
 269        int r;
 270
 271        if (!create)
 272                return 0;
 273
 274        if (strlen(create) >= DM_MAX_STR_SIZE) {
 275                DMERR("Argument is too big. Limit is %d", DM_MAX_STR_SIZE);
 276                return -EINVAL;
 277        }
 278        str = kstrndup(create, DM_MAX_STR_SIZE, GFP_KERNEL);
 279        if (!str)
 280                return -ENOMEM;
 281
 282        r = dm_parse_devices(&devices, str);
 283        if (r)
 284                goto out;
 285
 286        DMINFO("waiting for all devices to be available before creating mapped devices");
 287        wait_for_device_probe();
 288
 289        list_for_each_entry(dev, &devices, list) {
 290                if (dm_early_create(&dev->dmi, dev->table,
 291                                    dev->target_args_array))
 292                        break;
 293        }
 294out:
 295        kfree(str);
 296        dm_setup_cleanup(&devices);
 297        return r;
 298}
 299
 300late_initcall(dm_init_init);
 301
 302module_param(create, charp, 0);
 303MODULE_PARM_DESC(create, "Create a mapped device in early boot");
 304