xref: /linux/block/early-lookup.c (revision 648fa60f)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Code for looking up block devices in the early boot code before mounting the
4  * root file system.
5  */
6 #include <linux/blkdev.h>
7 #include <linux/ctype.h>
8 
9 struct uuidcmp {
10 	const char *uuid;
11 	int len;
12 };
13 
14 /**
15  * match_dev_by_uuid - callback for finding a partition using its uuid
16  * @dev:	device passed in by the caller
17  * @data:	opaque pointer to the desired struct uuidcmp to match
18  *
19  * Returns 1 if the device matches, and 0 otherwise.
20  */
match_dev_by_uuid(struct device * dev,const void * data)21 static int __init match_dev_by_uuid(struct device *dev, const void *data)
22 {
23 	struct block_device *bdev = dev_to_bdev(dev);
24 	const struct uuidcmp *cmp = data;
25 
26 	if (!bdev->bd_meta_info ||
27 	    strncasecmp(cmp->uuid, bdev->bd_meta_info->uuid, cmp->len))
28 		return 0;
29 	return 1;
30 }
31 
32 /**
33  * devt_from_partuuid - looks up the dev_t of a partition by its UUID
34  * @uuid_str:	char array containing ascii UUID
35  * @devt:	dev_t result
36  *
37  * The function will return the first partition which contains a matching
38  * UUID value in its partition_meta_info struct.  This does not search
39  * by filesystem UUIDs.
40  *
41  * If @uuid_str is followed by a "/PARTNROFF=%d", then the number will be
42  * extracted and used as an offset from the partition identified by the UUID.
43  *
44  * Returns 0 on success or a negative error code on failure.
45  */
devt_from_partuuid(const char * uuid_str,dev_t * devt)46 static int __init devt_from_partuuid(const char *uuid_str, dev_t *devt)
47 {
48 	struct uuidcmp cmp;
49 	struct device *dev = NULL;
50 	int offset = 0;
51 	char *slash;
52 
53 	cmp.uuid = uuid_str;
54 
55 	slash = strchr(uuid_str, '/');
56 	/* Check for optional partition number offset attributes. */
57 	if (slash) {
58 		char c = 0;
59 
60 		/* Explicitly fail on poor PARTUUID syntax. */
61 		if (sscanf(slash + 1, "PARTNROFF=%d%c", &offset, &c) != 1)
62 			goto out_invalid;
63 		cmp.len = slash - uuid_str;
64 	} else {
65 		cmp.len = strlen(uuid_str);
66 	}
67 
68 	if (!cmp.len)
69 		goto out_invalid;
70 
71 	dev = class_find_device(&block_class, NULL, &cmp, &match_dev_by_uuid);
72 	if (!dev)
73 		return -ENODEV;
74 
75 	if (offset) {
76 		/*
77 		 * Attempt to find the requested partition by adding an offset
78 		 * to the partition number found by UUID.
79 		 */
80 		*devt = part_devt(dev_to_disk(dev),
81 				  dev_to_bdev(dev)->bd_partno + offset);
82 	} else {
83 		*devt = dev->devt;
84 	}
85 
86 	put_device(dev);
87 	return 0;
88 
89 out_invalid:
90 	pr_err("VFS: PARTUUID= is invalid.\n"
91 	       "Expected PARTUUID=<valid-uuid-id>[/PARTNROFF=%%d]\n");
92 	return -EINVAL;
93 }
94 
95 /**
96  * match_dev_by_label - callback for finding a partition using its label
97  * @dev:	device passed in by the caller
98  * @data:	opaque pointer to the label to match
99  *
100  * Returns 1 if the device matches, and 0 otherwise.
101  */
match_dev_by_label(struct device * dev,const void * data)102 static int __init match_dev_by_label(struct device *dev, const void *data)
103 {
104 	struct block_device *bdev = dev_to_bdev(dev);
105 	const char *label = data;
106 
107 	if (!bdev->bd_meta_info || strcmp(label, bdev->bd_meta_info->volname))
108 		return 0;
109 	return 1;
110 }
111 
devt_from_partlabel(const char * label,dev_t * devt)112 static int __init devt_from_partlabel(const char *label, dev_t *devt)
113 {
114 	struct device *dev;
115 
116 	dev = class_find_device(&block_class, NULL, label, &match_dev_by_label);
117 	if (!dev)
118 		return -ENODEV;
119 	*devt = dev->devt;
120 	put_device(dev);
121 	return 0;
122 }
123 
blk_lookup_devt(const char * name,int partno)124 static dev_t __init blk_lookup_devt(const char *name, int partno)
125 {
126 	dev_t devt = MKDEV(0, 0);
127 	struct class_dev_iter iter;
128 	struct device *dev;
129 
130 	class_dev_iter_init(&iter, &block_class, NULL, &disk_type);
131 	while ((dev = class_dev_iter_next(&iter))) {
132 		struct gendisk *disk = dev_to_disk(dev);
133 
134 		if (strcmp(dev_name(dev), name))
135 			continue;
136 
137 		if (partno < disk->minors) {
138 			/* We need to return the right devno, even
139 			 * if the partition doesn't exist yet.
140 			 */
141 			devt = MKDEV(MAJOR(dev->devt),
142 				     MINOR(dev->devt) + partno);
143 		} else {
144 			devt = part_devt(disk, partno);
145 			if (devt)
146 				break;
147 		}
148 	}
149 	class_dev_iter_exit(&iter);
150 	return devt;
151 }
152 
devt_from_devname(const char * name,dev_t * devt)153 static int __init devt_from_devname(const char *name, dev_t *devt)
154 {
155 	int part;
156 	char s[32];
157 	char *p;
158 
159 	if (strlen(name) > 31)
160 		return -EINVAL;
161 	strcpy(s, name);
162 	for (p = s; *p; p++) {
163 		if (*p == '/')
164 			*p = '!';
165 	}
166 
167 	*devt = blk_lookup_devt(s, 0);
168 	if (*devt)
169 		return 0;
170 
171 	/*
172 	 * Try non-existent, but valid partition, which may only exist after
173 	 * opening the device, like partitioned md devices.
174 	 */
175 	while (p > s && isdigit(p[-1]))
176 		p--;
177 	if (p == s || !*p || *p == '0')
178 		return -ENODEV;
179 
180 	/* try disk name without <part number> */
181 	part = simple_strtoul(p, NULL, 10);
182 	*p = '\0';
183 	*devt = blk_lookup_devt(s, part);
184 	if (*devt)
185 		return 0;
186 
187 	/* try disk name without p<part number> */
188 	if (p < s + 2 || !isdigit(p[-2]) || p[-1] != 'p')
189 		return -ENODEV;
190 	p[-1] = '\0';
191 	*devt = blk_lookup_devt(s, part);
192 	if (*devt)
193 		return 0;
194 	return -ENODEV;
195 }
196 
devt_from_devnum(const char * name,dev_t * devt)197 static int __init devt_from_devnum(const char *name, dev_t *devt)
198 {
199 	unsigned maj, min, offset;
200 	char *p, dummy;
201 
202 	if (sscanf(name, "%u:%u%c", &maj, &min, &dummy) == 2 ||
203 	    sscanf(name, "%u:%u:%u:%c", &maj, &min, &offset, &dummy) == 3) {
204 		*devt = MKDEV(maj, min);
205 		if (maj != MAJOR(*devt) || min != MINOR(*devt))
206 			return -EINVAL;
207 	} else {
208 		*devt = new_decode_dev(simple_strtoul(name, &p, 16));
209 		if (*p)
210 			return -EINVAL;
211 	}
212 
213 	return 0;
214 }
215 
216 /*
217  *	Convert a name into device number.  We accept the following variants:
218  *
219  *	1) <hex_major><hex_minor> device number in hexadecimal represents itself
220  *         no leading 0x, for example b302.
221  *	3) /dev/<disk_name> represents the device number of disk
222  *	4) /dev/<disk_name><decimal> represents the device number
223  *         of partition - device number of disk plus the partition number
224  *	5) /dev/<disk_name>p<decimal> - same as the above, that form is
225  *	   used when disk name of partitioned disk ends on a digit.
226  *	6) PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF representing the
227  *	   unique id of a partition if the partition table provides it.
228  *	   The UUID may be either an EFI/GPT UUID, or refer to an MSDOS
229  *	   partition using the format SSSSSSSS-PP, where SSSSSSSS is a zero-
230  *	   filled hex representation of the 32-bit "NT disk signature", and PP
231  *	   is a zero-filled hex representation of the 1-based partition number.
232  *	7) PARTUUID=<UUID>/PARTNROFF=<int> to select a partition in relation to
233  *	   a partition with a known unique id.
234  *	8) <major>:<minor> major and minor number of the device separated by
235  *	   a colon.
236  *	9) PARTLABEL=<name> with name being the GPT partition label.
237  *	   MSDOS partitions do not support labels!
238  *
239  *	If name doesn't have fall into the categories above, we return (0,0).
240  *	block_class is used to check if something is a disk name. If the disk
241  *	name contains slashes, the device name has them replaced with
242  *	bangs.
243  */
early_lookup_bdev(const char * name,dev_t * devt)244 int __init early_lookup_bdev(const char *name, dev_t *devt)
245 {
246 	if (strncmp(name, "PARTUUID=", 9) == 0)
247 		return devt_from_partuuid(name + 9, devt);
248 	if (strncmp(name, "PARTLABEL=", 10) == 0)
249 		return devt_from_partlabel(name + 10, devt);
250 	if (strncmp(name, "/dev/", 5) == 0)
251 		return devt_from_devname(name + 5, devt);
252 	return devt_from_devnum(name, devt);
253 }
254 
bdevt_str(dev_t devt,char * buf)255 static char __init *bdevt_str(dev_t devt, char *buf)
256 {
257 	if (MAJOR(devt) <= 0xff && MINOR(devt) <= 0xff) {
258 		char tbuf[BDEVT_SIZE];
259 		snprintf(tbuf, BDEVT_SIZE, "%02x%02x", MAJOR(devt), MINOR(devt));
260 		snprintf(buf, BDEVT_SIZE, "%-9s", tbuf);
261 	} else
262 		snprintf(buf, BDEVT_SIZE, "%03x:%05x", MAJOR(devt), MINOR(devt));
263 
264 	return buf;
265 }
266 
267 /*
268  * print a full list of all partitions - intended for places where the root
269  * filesystem can't be mounted and thus to give the victim some idea of what
270  * went wrong
271  */
printk_all_partitions(void)272 void __init printk_all_partitions(void)
273 {
274 	struct class_dev_iter iter;
275 	struct device *dev;
276 
277 	class_dev_iter_init(&iter, &block_class, NULL, &disk_type);
278 	while ((dev = class_dev_iter_next(&iter))) {
279 		struct gendisk *disk = dev_to_disk(dev);
280 		struct block_device *part;
281 		char devt_buf[BDEVT_SIZE];
282 		unsigned long idx;
283 
284 		/*
285 		 * Don't show empty devices or things that have been
286 		 * suppressed
287 		 */
288 		if (get_capacity(disk) == 0 || (disk->flags & GENHD_FL_HIDDEN))
289 			continue;
290 
291 		/*
292 		 * Note, unlike /proc/partitions, I am showing the numbers in
293 		 * hex - the same format as the root= option takes.
294 		 */
295 		rcu_read_lock();
296 		xa_for_each(&disk->part_tbl, idx, part) {
297 			if (!bdev_nr_sectors(part))
298 				continue;
299 			printk("%s%s %10llu %pg %s",
300 			       bdev_is_partition(part) ? "  " : "",
301 			       bdevt_str(part->bd_dev, devt_buf),
302 			       bdev_nr_sectors(part) >> 1, part,
303 			       part->bd_meta_info ?
304 					part->bd_meta_info->uuid : "");
305 			if (bdev_is_partition(part))
306 				printk("\n");
307 			else if (dev->parent && dev->parent->driver)
308 				printk(" driver: %s\n",
309 					dev->parent->driver->name);
310 			else
311 				printk(" (driver?)\n");
312 		}
313 		rcu_read_unlock();
314 	}
315 	class_dev_iter_exit(&iter);
316 }
317