• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* do_mounts_dm.c
2  * Copyright (C) 2010 The Chromium OS Authors <chromium-os-dev@chromium.org>
3  *                    All Rights Reserved.
4  * Based on do_mounts_md.c
5  *
6  * This file is released under the GPL.
7  */
8 #include <linux/device-mapper.h>
9 #include <linux/fs.h>
10 #include <linux/string.h>
11 
12 #include "do_mounts.h"
13 
14 #define DM_MAX_NAME 32
15 #define DM_MAX_UUID 129
16 #define DM_NO_UUID "none"
17 
18 #define DM_MSG_PREFIX "init"
19 
20 /* Separators used for parsing the dm= argument. */
21 #define DM_FIELD_SEP ' '
22 #define DM_LINE_SEP ','
23 
24 /*
25  * When the device-mapper and any targets are compiled into the kernel
26  * (not a module), one target may be created and used as the root device at
27  * boot time with the parameters given with the boot line dm=...
28  * The code for that is here.
29  */
30 
31 struct dm_setup_target {
32 	sector_t begin;
33 	sector_t length;
34 	char *type;
35 	char *params;
36 	/* simple singly linked list */
37 	struct dm_setup_target *next;
38 };
39 
40 static struct {
41 	int minor;
42 	int ro;
43 	char name[DM_MAX_NAME];
44 	char uuid[DM_MAX_UUID];
45 	char *targets;
46 	struct dm_setup_target *target;
47 	int target_count;
48 } dm_setup_args __initdata;
49 
50 static __initdata int dm_early_setup;
51 
get_dm_option(char * str,char ** next,char sep)52 static size_t __init get_dm_option(char *str, char **next, char sep)
53 {
54 	size_t len = 0;
55 	char *endp = NULL;
56 
57 	if (!str)
58 		return 0;
59 
60 	endp = strchr(str, sep);
61 	if (!endp) {  /* act like strchrnul */
62 		len = strlen(str);
63 		endp = str + len;
64 	} else {
65 		len = endp - str;
66 	}
67 
68 	if (endp == str)
69 		return 0;
70 
71 	if (!next)
72 		return len;
73 
74 	if (*endp == 0) {
75 		/* Don't advance past the nul. */
76 		*next = endp;
77 	} else {
78 		*next = endp + 1;
79 	}
80 	return len;
81 }
82 
dm_setup_args_init(void)83 static int __init dm_setup_args_init(void)
84 {
85 	dm_setup_args.minor = 0;
86 	dm_setup_args.ro = 0;
87 	dm_setup_args.target = NULL;
88 	dm_setup_args.target_count = 0;
89 	return 0;
90 }
91 
dm_setup_cleanup(void)92 static int __init dm_setup_cleanup(void)
93 {
94 	struct dm_setup_target *target = dm_setup_args.target;
95 	struct dm_setup_target *old_target = NULL;
96 	while (target) {
97 		kfree(target->type);
98 		kfree(target->params);
99 		old_target = target;
100 		target = target->next;
101 		kfree(old_target);
102 		dm_setup_args.target_count--;
103 	}
104 	BUG_ON(dm_setup_args.target_count);
105 	return 0;
106 }
107 
dm_setup_parse_device_args(char * str)108 static char * __init dm_setup_parse_device_args(char *str)
109 {
110 	char *next = NULL;
111 	size_t len = 0;
112 
113 	/* Grab the logical name of the device to be exported to udev */
114 	len = get_dm_option(str, &next, DM_FIELD_SEP);
115 	if (!len) {
116 		DMERR("failed to parse device name");
117 		goto parse_fail;
118 	}
119 	len = min(len + 1, sizeof(dm_setup_args.name));
120 	strlcpy(dm_setup_args.name, str, len);  /* includes nul */
121 	str = skip_spaces(next);
122 
123 	/* Grab the UUID value or "none" */
124 	len = get_dm_option(str, &next, DM_FIELD_SEP);
125 	if (!len) {
126 		DMERR("failed to parse device uuid");
127 		goto parse_fail;
128 	}
129 	len = min(len + 1, sizeof(dm_setup_args.uuid));
130 	strlcpy(dm_setup_args.uuid, str, len);
131 	str = skip_spaces(next);
132 
133 	/* Determine if the table/device will be read only or read-write */
134 	if (!strncmp("ro,", str, 3)) {
135 		dm_setup_args.ro = 1;
136 	} else if (!strncmp("rw,", str, 3)) {
137 		dm_setup_args.ro = 0;
138 	} else {
139 		DMERR("failed to parse table mode");
140 		goto parse_fail;
141 	}
142 	str = skip_spaces(str + 3);
143 
144 	return str;
145 
146 parse_fail:
147 	return NULL;
148 }
149 
dm_substitute_devices(char * str,size_t str_len)150 static void __init dm_substitute_devices(char *str, size_t str_len)
151 {
152 	char *candidate = str;
153 	char *candidate_end = str;
154 	char old_char;
155 	size_t len = 0;
156 	dev_t dev;
157 
158 	if (str_len < 3)
159 		return;
160 
161 	while (str && *str) {
162 		candidate = strchr(str, '/');
163 		if (!candidate)
164 			break;
165 
166 		/* Avoid embedded slashes */
167 		if (candidate != str && *(candidate - 1) != DM_FIELD_SEP) {
168 			str = strchr(candidate, DM_FIELD_SEP);
169 			continue;
170 		}
171 
172 		len = get_dm_option(candidate, &candidate_end, DM_FIELD_SEP);
173 		str = skip_spaces(candidate_end);
174 		if (len < 3 || len > 37)  /* name_to_dev_t max; maj:mix min */
175 			continue;
176 
177 		/* Temporarily terminate with a nul */
178 		if (*candidate_end)
179 			candidate_end--;
180 		old_char = *candidate_end;
181 		*candidate_end = '\0';
182 
183 		DMDEBUG("converting candidate device '%s' to dev_t", candidate);
184 		/* Use the boot-time specific device naming */
185 		dev = name_to_dev_t(candidate);
186 		*candidate_end = old_char;
187 
188 		DMDEBUG(" -> %u", dev);
189 		/* No suitable replacement found */
190 		if (!dev)
191 			continue;
192 
193 		/* Rewrite the /dev/path as a major:minor */
194 		len = snprintf(candidate, len, "%u:%u", MAJOR(dev), MINOR(dev));
195 		if (!len) {
196 			DMERR("error substituting device major/minor.");
197 			break;
198 		}
199 		candidate += len;
200 		/* Pad out with spaces (fixing our nul) */
201 		while (candidate < candidate_end)
202 			*(candidate++) = DM_FIELD_SEP;
203 	}
204 }
205 
dm_setup_parse_targets(char * str)206 static int __init dm_setup_parse_targets(char *str)
207 {
208 	char *next = NULL;
209 	size_t len = 0;
210 	struct dm_setup_target **target = NULL;
211 
212 	/* Targets are defined as per the table format but with a
213 	 * comma as a newline separator. */
214 	target = &dm_setup_args.target;
215 	while (str && *str) {
216 		*target = kzalloc(sizeof(struct dm_setup_target), GFP_KERNEL);
217 		if (!*target) {
218 			DMERR("failed to allocate memory for target %d",
219 			      dm_setup_args.target_count);
220 			goto parse_fail;
221 		}
222 		dm_setup_args.target_count++;
223 
224 		(*target)->begin = simple_strtoull(str, &next, 10);
225 		if (!next || *next != DM_FIELD_SEP) {
226 			DMERR("failed to parse starting sector for target %d",
227 			      dm_setup_args.target_count - 1);
228 			goto parse_fail;
229 		}
230 		str = skip_spaces(next + 1);
231 
232 		(*target)->length = simple_strtoull(str, &next, 10);
233 		if (!next || *next != DM_FIELD_SEP) {
234 			DMERR("failed to parse length for target %d",
235 			      dm_setup_args.target_count - 1);
236 			goto parse_fail;
237 		}
238 		str = skip_spaces(next + 1);
239 
240 		len = get_dm_option(str, &next, DM_FIELD_SEP);
241 		if (!len ||
242 		    !((*target)->type = kstrndup(str, len, GFP_KERNEL))) {
243 			DMERR("failed to parse type for target %d",
244 			      dm_setup_args.target_count - 1);
245 			goto parse_fail;
246 		}
247 		str = skip_spaces(next);
248 
249 		len = get_dm_option(str, &next, DM_LINE_SEP);
250 		if (!len ||
251 		    !((*target)->params = kstrndup(str, len, GFP_KERNEL))) {
252 			DMERR("failed to parse params for target %d",
253 			      dm_setup_args.target_count - 1);
254 			goto parse_fail;
255 		}
256 		str = skip_spaces(next);
257 
258 		/* Before moving on, walk through the copied target and
259 		 * attempt to replace all /dev/xxx with the major:minor number.
260 		 * It may not be possible to resolve them traditionally at
261 		 * boot-time. */
262 		dm_substitute_devices((*target)->params, len);
263 
264 		target = &((*target)->next);
265 	}
266 	DMDEBUG("parsed %d targets", dm_setup_args.target_count);
267 
268 	return 0;
269 
270 parse_fail:
271 	return 1;
272 }
273 
274 /*
275  * Parse the command-line parameters given our kernel, but do not
276  * actually try to invoke the DM device now; that is handled by
277  * dm_setup_drive after the low-level disk drivers have initialised.
278  * dm format is as follows:
279  *  dm="name uuid fmode,[table line 1],[table line 2],..."
280  * May be used with root=/dev/dm-0 as it always uses the first dm minor.
281  */
282 
dm_setup(char * str)283 static int __init dm_setup(char *str)
284 {
285 	dm_setup_args_init();
286 
287 	str = dm_setup_parse_device_args(str);
288 	if (!str) {
289 		DMDEBUG("str is NULL");
290 		goto parse_fail;
291 	}
292 
293 	/* Target parsing is delayed until we have dynamic memory */
294 	dm_setup_args.targets = str;
295 
296 	printk(KERN_INFO "dm: will configure '%s' on dm-%d\n",
297 	       dm_setup_args.name, dm_setup_args.minor);
298 
299 	dm_early_setup = 1;
300 	return 1;
301 
302 parse_fail:
303 	printk(KERN_WARNING "dm: Invalid arguments supplied to dm=.\n");
304 	return 0;
305 }
306 
307 
dm_setup_drive(void)308 static void __init dm_setup_drive(void)
309 {
310 	struct mapped_device *md = NULL;
311 	struct dm_table *table = NULL;
312 	struct dm_setup_target *target;
313 	char *uuid = dm_setup_args.uuid;
314 	fmode_t fmode = FMODE_READ;
315 
316 	/* Finish parsing the targets. */
317 	if (dm_setup_parse_targets(dm_setup_args.targets))
318 		goto parse_fail;
319 
320 	if (dm_create(dm_setup_args.minor, &md)) {
321 		DMDEBUG("failed to create the device");
322 		goto dm_create_fail;
323 	}
324 	DMDEBUG("created device '%s'", dm_device_name(md));
325 
326 	/* In addition to flagging the table below, the disk must be
327 	 * set explicitly ro/rw. */
328 	set_disk_ro(dm_disk(md), dm_setup_args.ro);
329 
330 	if (!dm_setup_args.ro)
331 		fmode |= FMODE_WRITE;
332 	if (dm_table_create(&table, fmode, dm_setup_args.target_count, md)) {
333 		DMDEBUG("failed to create the table");
334 		goto dm_table_create_fail;
335 	}
336 
337 	target = dm_setup_args.target;
338 	while (target) {
339 		DMINFO("adding target '%llu %llu %s %s'",
340 		       (unsigned long long) target->begin,
341 		       (unsigned long long) target->length, target->type,
342 		       target->params);
343 		if (dm_table_add_target(table, target->type, target->begin,
344 					target->length, target->params)) {
345 			DMDEBUG("failed to add the target to the table");
346 			goto add_target_fail;
347 		}
348 		target = target->next;
349 	}
350 
351 	if (dm_table_complete(table)) {
352 		DMDEBUG("failed to complete the table");
353 		goto table_complete_fail;
354 	}
355 
356 	/* Suspend the device so that we can bind it to the table. */
357 	if (dm_suspend(md, 0)) {
358 		DMDEBUG("failed to suspend the device pre-bind");
359 		goto suspend_fail;
360 	}
361 
362 	/* Bind the table to the device. This is the only way to associate
363 	 * md->map with the table and set the disk capacity directly. */
364 	if (dm_swap_table(md, table)) {  /* should return NULL. */
365 		DMDEBUG("failed to bind the device to the table");
366 		goto table_bind_fail;
367 	}
368 
369 	/* Finally, resume and the device should be ready. */
370 	if (dm_resume(md)) {
371 		DMDEBUG("failed to resume the device");
372 		goto resume_fail;
373 	}
374 
375 	/* Export the dm device via the ioctl interface */
376 	if (!strcmp(DM_NO_UUID, dm_setup_args.uuid))
377 		uuid = NULL;
378 	if (dm_ioctl_export(md, dm_setup_args.name, uuid)) {
379 		DMDEBUG("failed to export device with given name and uuid");
380 		goto export_fail;
381 	}
382 	printk(KERN_INFO "dm: dm-%d is ready\n", dm_setup_args.minor);
383 
384 	dm_setup_cleanup();
385 	return;
386 
387 export_fail:
388 resume_fail:
389 table_bind_fail:
390 suspend_fail:
391 table_complete_fail:
392 add_target_fail:
393 	dm_table_put(table);
394 dm_table_create_fail:
395 	dm_put(md);
396 dm_create_fail:
397 	dm_setup_cleanup();
398 parse_fail:
399 	printk(KERN_WARNING "dm: starting dm-%d (%s) failed\n",
400 	       dm_setup_args.minor, dm_setup_args.name);
401 }
402 
403 __setup("dm=", dm_setup);
404 
dm_run_setup(void)405 void __init dm_run_setup(void)
406 {
407 	if (!dm_early_setup)
408 		return;
409 	printk(KERN_INFO "dm: attempting early device configuration.\n");
410 	dm_setup_drive();
411 }
412