18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci 38c2ecf20Sopenharmony_ci/* 48c2ecf20Sopenharmony_ci * dm-init.c 58c2ecf20Sopenharmony_ci * Copyright (C) 2017 The Chromium OS Authors <chromium-os-dev@chromium.org> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This file is released under the GPLv2. 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/ctype.h> 118c2ecf20Sopenharmony_ci#include <linux/device.h> 128c2ecf20Sopenharmony_ci#include <linux/device-mapper.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/list.h> 158c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define DM_MSG_PREFIX "init" 188c2ecf20Sopenharmony_ci#define DM_MAX_DEVICES 256 198c2ecf20Sopenharmony_ci#define DM_MAX_TARGETS 256 208c2ecf20Sopenharmony_ci#define DM_MAX_STR_SIZE 4096 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic char *create; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * Format: dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+] 268c2ecf20Sopenharmony_ci * Table format: <start_sector> <num_sectors> <target_type> <target_args> 278c2ecf20Sopenharmony_ci * 288c2ecf20Sopenharmony_ci * See Documentation/admin-guide/device-mapper/dm-init.rst for dm-mod.create="..." format 298c2ecf20Sopenharmony_ci * details. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistruct dm_device { 338c2ecf20Sopenharmony_ci struct dm_ioctl dmi; 348c2ecf20Sopenharmony_ci struct dm_target_spec *table[DM_MAX_TARGETS]; 358c2ecf20Sopenharmony_ci char *target_args_array[DM_MAX_TARGETS]; 368c2ecf20Sopenharmony_ci struct list_head list; 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic const char * const dm_allowed_targets[] __initconst = { 408c2ecf20Sopenharmony_ci "crypt", 418c2ecf20Sopenharmony_ci "delay", 428c2ecf20Sopenharmony_ci "linear", 438c2ecf20Sopenharmony_ci "snapshot-origin", 448c2ecf20Sopenharmony_ci "striped", 458c2ecf20Sopenharmony_ci "verity", 468c2ecf20Sopenharmony_ci}; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic int __init dm_verify_target_type(const char *target) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci unsigned int i; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(dm_allowed_targets); i++) { 538c2ecf20Sopenharmony_ci if (!strcmp(dm_allowed_targets[i], target)) 548c2ecf20Sopenharmony_ci return 0; 558c2ecf20Sopenharmony_ci } 568c2ecf20Sopenharmony_ci return -EINVAL; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic void __init dm_setup_cleanup(struct list_head *devices) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci struct dm_device *dev, *tmp; 628c2ecf20Sopenharmony_ci unsigned int i; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci list_for_each_entry_safe(dev, tmp, devices, list) { 658c2ecf20Sopenharmony_ci list_del(&dev->list); 668c2ecf20Sopenharmony_ci for (i = 0; i < dev->dmi.target_count; i++) { 678c2ecf20Sopenharmony_ci kfree(dev->table[i]); 688c2ecf20Sopenharmony_ci kfree(dev->target_args_array[i]); 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci kfree(dev); 718c2ecf20Sopenharmony_ci } 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci/** 758c2ecf20Sopenharmony_ci * str_field_delimit - delimit a string based on a separator char. 768c2ecf20Sopenharmony_ci * @str: the pointer to the string to delimit. 778c2ecf20Sopenharmony_ci * @separator: char that delimits the field 788c2ecf20Sopenharmony_ci * 798c2ecf20Sopenharmony_ci * Find a @separator and replace it by '\0'. 808c2ecf20Sopenharmony_ci * Remove leading and trailing spaces. 818c2ecf20Sopenharmony_ci * Return the remainder string after the @separator. 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_cistatic char __init *str_field_delimit(char **str, char separator) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci char *s; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* TODO: add support for escaped characters */ 888c2ecf20Sopenharmony_ci *str = skip_spaces(*str); 898c2ecf20Sopenharmony_ci s = strchr(*str, separator); 908c2ecf20Sopenharmony_ci /* Delimit the field and remove trailing spaces */ 918c2ecf20Sopenharmony_ci if (s) 928c2ecf20Sopenharmony_ci *s = '\0'; 938c2ecf20Sopenharmony_ci *str = strim(*str); 948c2ecf20Sopenharmony_ci return s ? ++s : NULL; 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci/** 988c2ecf20Sopenharmony_ci * dm_parse_table_entry - parse a table entry 998c2ecf20Sopenharmony_ci * @dev: device to store the parsed information. 1008c2ecf20Sopenharmony_ci * @str: the pointer to a string with the format: 1018c2ecf20Sopenharmony_ci * <start_sector> <num_sectors> <target_type> <target_args>[, ...] 1028c2ecf20Sopenharmony_ci * 1038c2ecf20Sopenharmony_ci * Return the remainder string after the table entry, i.e, after the comma which 1048c2ecf20Sopenharmony_ci * delimits the entry or NULL if reached the end of the string. 1058c2ecf20Sopenharmony_ci */ 1068c2ecf20Sopenharmony_cistatic char __init *dm_parse_table_entry(struct dm_device *dev, char *str) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci const unsigned int n = dev->dmi.target_count - 1; 1098c2ecf20Sopenharmony_ci struct dm_target_spec *sp; 1108c2ecf20Sopenharmony_ci unsigned int i; 1118c2ecf20Sopenharmony_ci /* fields: */ 1128c2ecf20Sopenharmony_ci char *field[4]; 1138c2ecf20Sopenharmony_ci char *next; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci field[0] = str; 1168c2ecf20Sopenharmony_ci /* Delimit first 3 fields that are separated by space */ 1178c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(field) - 1; i++) { 1188c2ecf20Sopenharmony_ci field[i + 1] = str_field_delimit(&field[i], ' '); 1198c2ecf20Sopenharmony_ci if (!field[i + 1]) 1208c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci /* Delimit last field that can be terminated by comma */ 1238c2ecf20Sopenharmony_ci next = str_field_delimit(&field[i], ','); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci sp = kzalloc(sizeof(*sp), GFP_KERNEL); 1268c2ecf20Sopenharmony_ci if (!sp) 1278c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 1288c2ecf20Sopenharmony_ci dev->table[n] = sp; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci /* start_sector */ 1318c2ecf20Sopenharmony_ci if (kstrtoull(field[0], 0, &sp->sector_start)) 1328c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 1338c2ecf20Sopenharmony_ci /* num_sector */ 1348c2ecf20Sopenharmony_ci if (kstrtoull(field[1], 0, &sp->length)) 1358c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 1368c2ecf20Sopenharmony_ci /* target_type */ 1378c2ecf20Sopenharmony_ci strscpy(sp->target_type, field[2], sizeof(sp->target_type)); 1388c2ecf20Sopenharmony_ci if (dm_verify_target_type(sp->target_type)) { 1398c2ecf20Sopenharmony_ci DMERR("invalid type \"%s\"", sp->target_type); 1408c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci /* target_args */ 1438c2ecf20Sopenharmony_ci dev->target_args_array[n] = kstrndup(field[3], DM_MAX_STR_SIZE, 1448c2ecf20Sopenharmony_ci GFP_KERNEL); 1458c2ecf20Sopenharmony_ci if (!dev->target_args_array[n]) 1468c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci return next; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci/** 1528c2ecf20Sopenharmony_ci * dm_parse_table - parse "dm-mod.create=" table field 1538c2ecf20Sopenharmony_ci * @dev: device to store the parsed information. 1548c2ecf20Sopenharmony_ci * @str: the pointer to a string with the format: 1558c2ecf20Sopenharmony_ci * <table>[,<table>+] 1568c2ecf20Sopenharmony_ci */ 1578c2ecf20Sopenharmony_cistatic int __init dm_parse_table(struct dm_device *dev, char *str) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci char *table_entry = str; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci while (table_entry) { 1628c2ecf20Sopenharmony_ci DMDEBUG("parsing table \"%s\"", str); 1638c2ecf20Sopenharmony_ci if (++dev->dmi.target_count > DM_MAX_TARGETS) { 1648c2ecf20Sopenharmony_ci DMERR("too many targets %u > %d", 1658c2ecf20Sopenharmony_ci dev->dmi.target_count, DM_MAX_TARGETS); 1668c2ecf20Sopenharmony_ci return -EINVAL; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci table_entry = dm_parse_table_entry(dev, table_entry); 1698c2ecf20Sopenharmony_ci if (IS_ERR(table_entry)) { 1708c2ecf20Sopenharmony_ci DMERR("couldn't parse table"); 1718c2ecf20Sopenharmony_ci return PTR_ERR(table_entry); 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci return 0; 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci/** 1798c2ecf20Sopenharmony_ci * dm_parse_device_entry - parse a device entry 1808c2ecf20Sopenharmony_ci * @dev: device to store the parsed information. 1818c2ecf20Sopenharmony_ci * @str: the pointer to a string with the format: 1828c2ecf20Sopenharmony_ci * name,uuid,minor,flags,table[; ...] 1838c2ecf20Sopenharmony_ci * 1848c2ecf20Sopenharmony_ci * Return the remainder string after the table entry, i.e, after the semi-colon 1858c2ecf20Sopenharmony_ci * which delimits the entry or NULL if reached the end of the string. 1868c2ecf20Sopenharmony_ci */ 1878c2ecf20Sopenharmony_cistatic char __init *dm_parse_device_entry(struct dm_device *dev, char *str) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci /* There are 5 fields: name,uuid,minor,flags,table; */ 1908c2ecf20Sopenharmony_ci char *field[5]; 1918c2ecf20Sopenharmony_ci unsigned int i; 1928c2ecf20Sopenharmony_ci char *next; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci field[0] = str; 1958c2ecf20Sopenharmony_ci /* Delimit first 4 fields that are separated by comma */ 1968c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(field) - 1; i++) { 1978c2ecf20Sopenharmony_ci field[i+1] = str_field_delimit(&field[i], ','); 1988c2ecf20Sopenharmony_ci if (!field[i+1]) 1998c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci /* Delimit last field that can be delimited by semi-colon */ 2028c2ecf20Sopenharmony_ci next = str_field_delimit(&field[i], ';'); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci /* name */ 2058c2ecf20Sopenharmony_ci strscpy(dev->dmi.name, field[0], sizeof(dev->dmi.name)); 2068c2ecf20Sopenharmony_ci /* uuid */ 2078c2ecf20Sopenharmony_ci strscpy(dev->dmi.uuid, field[1], sizeof(dev->dmi.uuid)); 2088c2ecf20Sopenharmony_ci /* minor */ 2098c2ecf20Sopenharmony_ci if (strlen(field[2])) { 2108c2ecf20Sopenharmony_ci if (kstrtoull(field[2], 0, &dev->dmi.dev)) 2118c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2128c2ecf20Sopenharmony_ci dev->dmi.flags |= DM_PERSISTENT_DEV_FLAG; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci /* flags */ 2158c2ecf20Sopenharmony_ci if (!strcmp(field[3], "ro")) 2168c2ecf20Sopenharmony_ci dev->dmi.flags |= DM_READONLY_FLAG; 2178c2ecf20Sopenharmony_ci else if (strcmp(field[3], "rw")) 2188c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2198c2ecf20Sopenharmony_ci /* table */ 2208c2ecf20Sopenharmony_ci if (dm_parse_table(dev, field[4])) 2218c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci return next; 2248c2ecf20Sopenharmony_ci} 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci/** 2278c2ecf20Sopenharmony_ci * dm_parse_devices - parse "dm-mod.create=" argument 2288c2ecf20Sopenharmony_ci * @devices: list of struct dm_device to store the parsed information. 2298c2ecf20Sopenharmony_ci * @str: the pointer to a string with the format: 2308c2ecf20Sopenharmony_ci * <device>[;<device>+] 2318c2ecf20Sopenharmony_ci */ 2328c2ecf20Sopenharmony_cistatic int __init dm_parse_devices(struct list_head *devices, char *str) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci unsigned long ndev = 0; 2358c2ecf20Sopenharmony_ci struct dm_device *dev; 2368c2ecf20Sopenharmony_ci char *device = str; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci DMDEBUG("parsing \"%s\"", str); 2398c2ecf20Sopenharmony_ci while (device) { 2408c2ecf20Sopenharmony_ci dev = kzalloc(sizeof(*dev), GFP_KERNEL); 2418c2ecf20Sopenharmony_ci if (!dev) 2428c2ecf20Sopenharmony_ci return -ENOMEM; 2438c2ecf20Sopenharmony_ci list_add_tail(&dev->list, devices); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci if (++ndev > DM_MAX_DEVICES) { 2468c2ecf20Sopenharmony_ci DMERR("too many devices %lu > %d", 2478c2ecf20Sopenharmony_ci ndev, DM_MAX_DEVICES); 2488c2ecf20Sopenharmony_ci return -EINVAL; 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci device = dm_parse_device_entry(dev, device); 2528c2ecf20Sopenharmony_ci if (IS_ERR(device)) { 2538c2ecf20Sopenharmony_ci DMERR("couldn't parse device"); 2548c2ecf20Sopenharmony_ci return PTR_ERR(device); 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci return 0; 2598c2ecf20Sopenharmony_ci} 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci/** 2628c2ecf20Sopenharmony_ci * dm_init_init - parse "dm-mod.create=" argument and configure drivers 2638c2ecf20Sopenharmony_ci */ 2648c2ecf20Sopenharmony_cistatic int __init dm_init_init(void) 2658c2ecf20Sopenharmony_ci{ 2668c2ecf20Sopenharmony_ci struct dm_device *dev; 2678c2ecf20Sopenharmony_ci LIST_HEAD(devices); 2688c2ecf20Sopenharmony_ci char *str; 2698c2ecf20Sopenharmony_ci int r; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci if (!create) 2728c2ecf20Sopenharmony_ci return 0; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci if (strlen(create) >= DM_MAX_STR_SIZE) { 2758c2ecf20Sopenharmony_ci DMERR("Argument is too big. Limit is %d", DM_MAX_STR_SIZE); 2768c2ecf20Sopenharmony_ci return -EINVAL; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci str = kstrndup(create, DM_MAX_STR_SIZE, GFP_KERNEL); 2798c2ecf20Sopenharmony_ci if (!str) 2808c2ecf20Sopenharmony_ci return -ENOMEM; 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci r = dm_parse_devices(&devices, str); 2838c2ecf20Sopenharmony_ci if (r) 2848c2ecf20Sopenharmony_ci goto out; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci DMINFO("waiting for all devices to be available before creating mapped devices"); 2878c2ecf20Sopenharmony_ci wait_for_device_probe(); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci list_for_each_entry(dev, &devices, list) { 2908c2ecf20Sopenharmony_ci if (dm_early_create(&dev->dmi, dev->table, 2918c2ecf20Sopenharmony_ci dev->target_args_array)) 2928c2ecf20Sopenharmony_ci break; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ciout: 2958c2ecf20Sopenharmony_ci kfree(str); 2968c2ecf20Sopenharmony_ci dm_setup_cleanup(&devices); 2978c2ecf20Sopenharmony_ci return r; 2988c2ecf20Sopenharmony_ci} 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cilate_initcall(dm_init_init); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_cimodule_param(create, charp, 0); 3038c2ecf20Sopenharmony_ciMODULE_PARM_DESC(create, "Create a mapped device in early boot"); 304