162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Code for looking up block devices in the early boot code before mounting the
462306a36Sopenharmony_ci * root file system.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/blkdev.h>
762306a36Sopenharmony_ci#include <linux/ctype.h>
862306a36Sopenharmony_ci
962306a36Sopenharmony_cistruct uuidcmp {
1062306a36Sopenharmony_ci	const char *uuid;
1162306a36Sopenharmony_ci	int len;
1262306a36Sopenharmony_ci};
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci/**
1562306a36Sopenharmony_ci * match_dev_by_uuid - callback for finding a partition using its uuid
1662306a36Sopenharmony_ci * @dev:	device passed in by the caller
1762306a36Sopenharmony_ci * @data:	opaque pointer to the desired struct uuidcmp to match
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * Returns 1 if the device matches, and 0 otherwise.
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_cistatic int __init match_dev_by_uuid(struct device *dev, const void *data)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	struct block_device *bdev = dev_to_bdev(dev);
2462306a36Sopenharmony_ci	const struct uuidcmp *cmp = data;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	if (!bdev->bd_meta_info ||
2762306a36Sopenharmony_ci	    strncasecmp(cmp->uuid, bdev->bd_meta_info->uuid, cmp->len))
2862306a36Sopenharmony_ci		return 0;
2962306a36Sopenharmony_ci	return 1;
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/**
3362306a36Sopenharmony_ci * devt_from_partuuid - looks up the dev_t of a partition by its UUID
3462306a36Sopenharmony_ci * @uuid_str:	char array containing ascii UUID
3562306a36Sopenharmony_ci * @devt:	dev_t result
3662306a36Sopenharmony_ci *
3762306a36Sopenharmony_ci * The function will return the first partition which contains a matching
3862306a36Sopenharmony_ci * UUID value in its partition_meta_info struct.  This does not search
3962306a36Sopenharmony_ci * by filesystem UUIDs.
4062306a36Sopenharmony_ci *
4162306a36Sopenharmony_ci * If @uuid_str is followed by a "/PARTNROFF=%d", then the number will be
4262306a36Sopenharmony_ci * extracted and used as an offset from the partition identified by the UUID.
4362306a36Sopenharmony_ci *
4462306a36Sopenharmony_ci * Returns 0 on success or a negative error code on failure.
4562306a36Sopenharmony_ci */
4662306a36Sopenharmony_cistatic int __init devt_from_partuuid(const char *uuid_str, dev_t *devt)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct uuidcmp cmp;
4962306a36Sopenharmony_ci	struct device *dev = NULL;
5062306a36Sopenharmony_ci	int offset = 0;
5162306a36Sopenharmony_ci	char *slash;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	cmp.uuid = uuid_str;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	slash = strchr(uuid_str, '/');
5662306a36Sopenharmony_ci	/* Check for optional partition number offset attributes. */
5762306a36Sopenharmony_ci	if (slash) {
5862306a36Sopenharmony_ci		char c = 0;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci		/* Explicitly fail on poor PARTUUID syntax. */
6162306a36Sopenharmony_ci		if (sscanf(slash + 1, "PARTNROFF=%d%c", &offset, &c) != 1)
6262306a36Sopenharmony_ci			goto out_invalid;
6362306a36Sopenharmony_ci		cmp.len = slash - uuid_str;
6462306a36Sopenharmony_ci	} else {
6562306a36Sopenharmony_ci		cmp.len = strlen(uuid_str);
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (!cmp.len)
6962306a36Sopenharmony_ci		goto out_invalid;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	dev = class_find_device(&block_class, NULL, &cmp, &match_dev_by_uuid);
7262306a36Sopenharmony_ci	if (!dev)
7362306a36Sopenharmony_ci		return -ENODEV;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	if (offset) {
7662306a36Sopenharmony_ci		/*
7762306a36Sopenharmony_ci		 * Attempt to find the requested partition by adding an offset
7862306a36Sopenharmony_ci		 * to the partition number found by UUID.
7962306a36Sopenharmony_ci		 */
8062306a36Sopenharmony_ci		*devt = part_devt(dev_to_disk(dev),
8162306a36Sopenharmony_ci				  dev_to_bdev(dev)->bd_partno + offset);
8262306a36Sopenharmony_ci	} else {
8362306a36Sopenharmony_ci		*devt = dev->devt;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	put_device(dev);
8762306a36Sopenharmony_ci	return 0;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ciout_invalid:
9062306a36Sopenharmony_ci	pr_err("VFS: PARTUUID= is invalid.\n"
9162306a36Sopenharmony_ci	       "Expected PARTUUID=<valid-uuid-id>[/PARTNROFF=%%d]\n");
9262306a36Sopenharmony_ci	return -EINVAL;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci/**
9662306a36Sopenharmony_ci * match_dev_by_label - callback for finding a partition using its label
9762306a36Sopenharmony_ci * @dev:	device passed in by the caller
9862306a36Sopenharmony_ci * @data:	opaque pointer to the label to match
9962306a36Sopenharmony_ci *
10062306a36Sopenharmony_ci * Returns 1 if the device matches, and 0 otherwise.
10162306a36Sopenharmony_ci */
10262306a36Sopenharmony_cistatic int __init match_dev_by_label(struct device *dev, const void *data)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct block_device *bdev = dev_to_bdev(dev);
10562306a36Sopenharmony_ci	const char *label = data;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	if (!bdev->bd_meta_info || strcmp(label, bdev->bd_meta_info->volname))
10862306a36Sopenharmony_ci		return 0;
10962306a36Sopenharmony_ci	return 1;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int __init devt_from_partlabel(const char *label, dev_t *devt)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct device *dev;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	dev = class_find_device(&block_class, NULL, label, &match_dev_by_label);
11762306a36Sopenharmony_ci	if (!dev)
11862306a36Sopenharmony_ci		return -ENODEV;
11962306a36Sopenharmony_ci	*devt = dev->devt;
12062306a36Sopenharmony_ci	put_device(dev);
12162306a36Sopenharmony_ci	return 0;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic dev_t __init blk_lookup_devt(const char *name, int partno)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	dev_t devt = MKDEV(0, 0);
12762306a36Sopenharmony_ci	struct class_dev_iter iter;
12862306a36Sopenharmony_ci	struct device *dev;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	class_dev_iter_init(&iter, &block_class, NULL, &disk_type);
13162306a36Sopenharmony_ci	while ((dev = class_dev_iter_next(&iter))) {
13262306a36Sopenharmony_ci		struct gendisk *disk = dev_to_disk(dev);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci		if (strcmp(dev_name(dev), name))
13562306a36Sopenharmony_ci			continue;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci		if (partno < disk->minors) {
13862306a36Sopenharmony_ci			/* We need to return the right devno, even
13962306a36Sopenharmony_ci			 * if the partition doesn't exist yet.
14062306a36Sopenharmony_ci			 */
14162306a36Sopenharmony_ci			devt = MKDEV(MAJOR(dev->devt),
14262306a36Sopenharmony_ci				     MINOR(dev->devt) + partno);
14362306a36Sopenharmony_ci		} else {
14462306a36Sopenharmony_ci			devt = part_devt(disk, partno);
14562306a36Sopenharmony_ci			if (devt)
14662306a36Sopenharmony_ci				break;
14762306a36Sopenharmony_ci		}
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci	class_dev_iter_exit(&iter);
15062306a36Sopenharmony_ci	return devt;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic int __init devt_from_devname(const char *name, dev_t *devt)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	int part;
15662306a36Sopenharmony_ci	char s[32];
15762306a36Sopenharmony_ci	char *p;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (strlen(name) > 31)
16062306a36Sopenharmony_ci		return -EINVAL;
16162306a36Sopenharmony_ci	strcpy(s, name);
16262306a36Sopenharmony_ci	for (p = s; *p; p++) {
16362306a36Sopenharmony_ci		if (*p == '/')
16462306a36Sopenharmony_ci			*p = '!';
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	*devt = blk_lookup_devt(s, 0);
16862306a36Sopenharmony_ci	if (*devt)
16962306a36Sopenharmony_ci		return 0;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/*
17262306a36Sopenharmony_ci	 * Try non-existent, but valid partition, which may only exist after
17362306a36Sopenharmony_ci	 * opening the device, like partitioned md devices.
17462306a36Sopenharmony_ci	 */
17562306a36Sopenharmony_ci	while (p > s && isdigit(p[-1]))
17662306a36Sopenharmony_ci		p--;
17762306a36Sopenharmony_ci	if (p == s || !*p || *p == '0')
17862306a36Sopenharmony_ci		return -ENODEV;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/* try disk name without <part number> */
18162306a36Sopenharmony_ci	part = simple_strtoul(p, NULL, 10);
18262306a36Sopenharmony_ci	*p = '\0';
18362306a36Sopenharmony_ci	*devt = blk_lookup_devt(s, part);
18462306a36Sopenharmony_ci	if (*devt)
18562306a36Sopenharmony_ci		return 0;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/* try disk name without p<part number> */
18862306a36Sopenharmony_ci	if (p < s + 2 || !isdigit(p[-2]) || p[-1] != 'p')
18962306a36Sopenharmony_ci		return -ENODEV;
19062306a36Sopenharmony_ci	p[-1] = '\0';
19162306a36Sopenharmony_ci	*devt = blk_lookup_devt(s, part);
19262306a36Sopenharmony_ci	if (*devt)
19362306a36Sopenharmony_ci		return 0;
19462306a36Sopenharmony_ci	return -ENODEV;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic int __init devt_from_devnum(const char *name, dev_t *devt)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	unsigned maj, min, offset;
20062306a36Sopenharmony_ci	char *p, dummy;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	if (sscanf(name, "%u:%u%c", &maj, &min, &dummy) == 2 ||
20362306a36Sopenharmony_ci	    sscanf(name, "%u:%u:%u:%c", &maj, &min, &offset, &dummy) == 3) {
20462306a36Sopenharmony_ci		*devt = MKDEV(maj, min);
20562306a36Sopenharmony_ci		if (maj != MAJOR(*devt) || min != MINOR(*devt))
20662306a36Sopenharmony_ci			return -EINVAL;
20762306a36Sopenharmony_ci	} else {
20862306a36Sopenharmony_ci		*devt = new_decode_dev(simple_strtoul(name, &p, 16));
20962306a36Sopenharmony_ci		if (*p)
21062306a36Sopenharmony_ci			return -EINVAL;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	return 0;
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci/*
21762306a36Sopenharmony_ci *	Convert a name into device number.  We accept the following variants:
21862306a36Sopenharmony_ci *
21962306a36Sopenharmony_ci *	1) <hex_major><hex_minor> device number in hexadecimal represents itself
22062306a36Sopenharmony_ci *         no leading 0x, for example b302.
22162306a36Sopenharmony_ci *	3) /dev/<disk_name> represents the device number of disk
22262306a36Sopenharmony_ci *	4) /dev/<disk_name><decimal> represents the device number
22362306a36Sopenharmony_ci *         of partition - device number of disk plus the partition number
22462306a36Sopenharmony_ci *	5) /dev/<disk_name>p<decimal> - same as the above, that form is
22562306a36Sopenharmony_ci *	   used when disk name of partitioned disk ends on a digit.
22662306a36Sopenharmony_ci *	6) PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF representing the
22762306a36Sopenharmony_ci *	   unique id of a partition if the partition table provides it.
22862306a36Sopenharmony_ci *	   The UUID may be either an EFI/GPT UUID, or refer to an MSDOS
22962306a36Sopenharmony_ci *	   partition using the format SSSSSSSS-PP, where SSSSSSSS is a zero-
23062306a36Sopenharmony_ci *	   filled hex representation of the 32-bit "NT disk signature", and PP
23162306a36Sopenharmony_ci *	   is a zero-filled hex representation of the 1-based partition number.
23262306a36Sopenharmony_ci *	7) PARTUUID=<UUID>/PARTNROFF=<int> to select a partition in relation to
23362306a36Sopenharmony_ci *	   a partition with a known unique id.
23462306a36Sopenharmony_ci *	8) <major>:<minor> major and minor number of the device separated by
23562306a36Sopenharmony_ci *	   a colon.
23662306a36Sopenharmony_ci *	9) PARTLABEL=<name> with name being the GPT partition label.
23762306a36Sopenharmony_ci *	   MSDOS partitions do not support labels!
23862306a36Sopenharmony_ci *
23962306a36Sopenharmony_ci *	If name doesn't have fall into the categories above, we return (0,0).
24062306a36Sopenharmony_ci *	block_class is used to check if something is a disk name. If the disk
24162306a36Sopenharmony_ci *	name contains slashes, the device name has them replaced with
24262306a36Sopenharmony_ci *	bangs.
24362306a36Sopenharmony_ci */
24462306a36Sopenharmony_ciint __init early_lookup_bdev(const char *name, dev_t *devt)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	if (strncmp(name, "PARTUUID=", 9) == 0)
24762306a36Sopenharmony_ci		return devt_from_partuuid(name + 9, devt);
24862306a36Sopenharmony_ci	if (strncmp(name, "PARTLABEL=", 10) == 0)
24962306a36Sopenharmony_ci		return devt_from_partlabel(name + 10, devt);
25062306a36Sopenharmony_ci	if (strncmp(name, "/dev/", 5) == 0)
25162306a36Sopenharmony_ci		return devt_from_devname(name + 5, devt);
25262306a36Sopenharmony_ci	return devt_from_devnum(name, devt);
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_cistatic char __init *bdevt_str(dev_t devt, char *buf)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	if (MAJOR(devt) <= 0xff && MINOR(devt) <= 0xff) {
25862306a36Sopenharmony_ci		char tbuf[BDEVT_SIZE];
25962306a36Sopenharmony_ci		snprintf(tbuf, BDEVT_SIZE, "%02x%02x", MAJOR(devt), MINOR(devt));
26062306a36Sopenharmony_ci		snprintf(buf, BDEVT_SIZE, "%-9s", tbuf);
26162306a36Sopenharmony_ci	} else
26262306a36Sopenharmony_ci		snprintf(buf, BDEVT_SIZE, "%03x:%05x", MAJOR(devt), MINOR(devt));
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	return buf;
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci/*
26862306a36Sopenharmony_ci * print a full list of all partitions - intended for places where the root
26962306a36Sopenharmony_ci * filesystem can't be mounted and thus to give the victim some idea of what
27062306a36Sopenharmony_ci * went wrong
27162306a36Sopenharmony_ci */
27262306a36Sopenharmony_civoid __init printk_all_partitions(void)
27362306a36Sopenharmony_ci{
27462306a36Sopenharmony_ci	struct class_dev_iter iter;
27562306a36Sopenharmony_ci	struct device *dev;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	class_dev_iter_init(&iter, &block_class, NULL, &disk_type);
27862306a36Sopenharmony_ci	while ((dev = class_dev_iter_next(&iter))) {
27962306a36Sopenharmony_ci		struct gendisk *disk = dev_to_disk(dev);
28062306a36Sopenharmony_ci		struct block_device *part;
28162306a36Sopenharmony_ci		char devt_buf[BDEVT_SIZE];
28262306a36Sopenharmony_ci		unsigned long idx;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci		/*
28562306a36Sopenharmony_ci		 * Don't show empty devices or things that have been
28662306a36Sopenharmony_ci		 * suppressed
28762306a36Sopenharmony_ci		 */
28862306a36Sopenharmony_ci		if (get_capacity(disk) == 0 || (disk->flags & GENHD_FL_HIDDEN))
28962306a36Sopenharmony_ci			continue;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci		/*
29262306a36Sopenharmony_ci		 * Note, unlike /proc/partitions, I am showing the numbers in
29362306a36Sopenharmony_ci		 * hex - the same format as the root= option takes.
29462306a36Sopenharmony_ci		 */
29562306a36Sopenharmony_ci		rcu_read_lock();
29662306a36Sopenharmony_ci		xa_for_each(&disk->part_tbl, idx, part) {
29762306a36Sopenharmony_ci			if (!bdev_nr_sectors(part))
29862306a36Sopenharmony_ci				continue;
29962306a36Sopenharmony_ci			printk("%s%s %10llu %pg %s",
30062306a36Sopenharmony_ci			       bdev_is_partition(part) ? "  " : "",
30162306a36Sopenharmony_ci			       bdevt_str(part->bd_dev, devt_buf),
30262306a36Sopenharmony_ci			       bdev_nr_sectors(part) >> 1, part,
30362306a36Sopenharmony_ci			       part->bd_meta_info ?
30462306a36Sopenharmony_ci					part->bd_meta_info->uuid : "");
30562306a36Sopenharmony_ci			if (bdev_is_partition(part))
30662306a36Sopenharmony_ci				printk("\n");
30762306a36Sopenharmony_ci			else if (dev->parent && dev->parent->driver)
30862306a36Sopenharmony_ci				printk(" driver: %s\n",
30962306a36Sopenharmony_ci					dev->parent->driver->name);
31062306a36Sopenharmony_ci			else
31162306a36Sopenharmony_ci				printk(" (driver?)\n");
31262306a36Sopenharmony_ci		}
31362306a36Sopenharmony_ci		rcu_read_unlock();
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci	class_dev_iter_exit(&iter);
31662306a36Sopenharmony_ci}
317