Commit | Line | Data |
---|---|---|
702f3189 CH |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Code for looking up block devices in the early boot code before mounting the | |
2577f53f | 4 | * root file system. |
702f3189 CH |
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 | */ | |
2577f53f | 21 | static int __init match_dev_by_uuid(struct device *dev, const void *data) |
702f3189 CH |
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 | |
017fb83e | 35 | * @devt: dev_t result |
702f3189 CH |
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 | * | |
017fb83e | 44 | * Returns 0 on success or a negative error code on failure. |
702f3189 | 45 | */ |
2577f53f | 46 | static int __init devt_from_partuuid(const char *uuid_str, dev_t *devt) |
702f3189 CH |
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 | */ | |
2577f53f | 102 | static int __init match_dev_by_label(struct device *dev, const void *data) |
702f3189 CH |
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 | ||
2577f53f | 112 | static int __init devt_from_partlabel(const char *label, dev_t *devt) |
702f3189 CH |
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 | ||
2577f53f | 124 | static dev_t __init blk_lookup_devt(const char *name, int partno) |
7cadcaf1 CH |
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 | ||
2577f53f | 153 | static int __init devt_from_devname(const char *name, dev_t *devt) |
702f3189 CH |
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') | |
648fa60f | 178 | return -ENODEV; |
702f3189 CH |
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) | |
bb91a7d9 | 185 | return 0; |
702f3189 CH |
186 | |
187 | /* try disk name without p<part number> */ | |
188 | if (p < s + 2 || !isdigit(p[-2]) || p[-1] != 'p') | |
648fa60f | 189 | return -ENODEV; |
702f3189 CH |
190 | p[-1] = '\0'; |
191 | *devt = blk_lookup_devt(s, part); | |
192 | if (*devt) | |
193 | return 0; | |
1341c7d2 | 194 | return -ENODEV; |
702f3189 CH |
195 | } |
196 | ||
2577f53f | 197 | static int __init devt_from_devnum(const char *name, dev_t *devt) |
702f3189 CH |
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 | */ | |
2577f53f | 244 | int __init early_lookup_bdev(const char *name, dev_t *devt) |
702f3189 CH |
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 | } | |
7cadcaf1 CH |
254 | |
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 | */ | |
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 | } |