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