Subject: [PATCH] [BZ 190371] libutil: add function to get base device for blockdevice
From: Stefan Haberland <sth@linux.ibm.com>

Description:   zipl: fix multi volume dump creation
Symptom:       zipl -M is not working.
Problem:       util_sys_get_dev_addr() returns the device address for
               a given blockdevice. This does not work for partitions
               but some tools like zipl rely on the ability to get the
               device address for partitions.
Solution:      Add code that determines the base device for a partition.
Reproduction:  Run zipl -M.
Upstream-ID:   fa7a4dafa3d89b2e8787de102e4362e895d44b05
Problem-ID:    190371

Upstream-Description:

              libutil: add function to get base device for blockdevice

              Some operations are only possible on base devices not on partitions.
              Add functions to determine if a given device is a partition or a base
              device and to get the base device to a given partition.

              Signed-off-by: Stefan Haberland <sth@linux.ibm.com>
              Reviewed-by: Jan Hoeppner <hoeppner@linux.ibm.com>
              Reviewed-by: Philipp Rudo <prudo@linux.ibm.com>
              Signed-off-by: Jan Hoeppner <hoeppner@linux.ibm.com>


Signed-off-by: Stefan Haberland <sth@linux.ibm.com>
---
 include/lib/util_sys.h |    5 ++
 libutil/util_sys.c     |   97 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 102 insertions(+)

--- a/include/lib/util_sys.h
+++ b/include/lib/util_sys.h
@@ -12,6 +12,11 @@
 #ifndef LIB_UTIL_SYS_H
 #define LIB_UTIL_SYS_H
 
+#include <stdbool.h>
+
 int util_sys_get_dev_addr(const char *dev, char *addr);
+bool util_sys_dev_is_partition(dev_t dev);
+int util_sys_get_partnum(dev_t dev);
+int util_sys_get_base_dev(dev_t dev, dev_t *base_dev);
 
 #endif /** LIB_UTIL_SYS_H @} */
--- a/libutil/util_sys.c
+++ b/libutil/util_sys.c
@@ -11,17 +11,114 @@
 
 #include <err.h>
 #include <errno.h>
+#include <linux/fs.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <unistd.h>
 
+#include "lib/util_file.h"
+#include "lib/util_libc.h"
 #include "lib/util_path.h"
 #include "lib/util_sys.h"
 
 /* lstat() doesn't work for sysfs files, a fixed size is therefore inevitable */
 #define READLINK_SIZE	256
+#define PAGE_SIZE	4096
+
+/**
+ * Return the partition number of a given partition.
+ *
+ * @param[in]	dev	Device node of interest
+ *
+ * @retval	int	Partition number of the device
+ * @retval	-1	Error when trying to read the partition number.
+ */
+int util_sys_get_partnum(dev_t dev)
+{
+	int partnum = -1;
+	char *path;
+
+	path = util_path_sysfs("dev/block/%u:%u/partition",
+			       major(dev), minor(dev));
+	if (util_file_read_i(&partnum, 10, path)) {
+		warnx("Could not read from path '%s'", path);
+		goto out;
+	}
+	if (partnum <= 0) {
+		warnx("Bad partition number in '%s'", path);
+		partnum = -1;
+		goto out;
+	}
+
+out:
+	free(path);
+	return partnum;
+}
+
+/**
+ * Determine if the given device is a partition.
+ *
+ * @param[in]	dev	Device node of interest
+ *
+ * @retval	true	Device is partition
+ * @retval	false	Device is not a partition
+ */
+bool util_sys_dev_is_partition(dev_t dev)
+{
+	bool is_part;
+	char *path;
+
+	path = util_path_sysfs("dev/block/%u:%u/partition",
+			       major(dev), minor(dev));
+	is_part = util_path_exists(path);
+	free(path);
+
+	return is_part;
+}
+
+/**
+ * Determine base device
+ *
+ * This function determines the base device \p base_dev of a given
+ * device \p dev. If \p dev is a base device, \p base_dev becomes \p dev.
+ *
+ * @param[in]	dev		Device node of interest
+ * @param[out]	base_dev	Identified base device
+ *
+ * @retval	 0		Success
+ * @retval	-1		Error while reading device information or
+ *				constructed path
+ */
+int util_sys_get_base_dev(dev_t dev, dev_t *base_dev)
+{
+	int base_major, base_minor;
+	char buf[PAGE_SIZE];
+	char *path;
+
+	/* check if the device already is a base device */
+	if (!util_sys_dev_is_partition(dev)) {
+		*base_dev = makedev(major(dev), minor(dev));
+		return 0;
+	}
+	path = util_path_sysfs("dev/block/%d:%d/../dev",
+			       major(dev), minor(dev));
+	if (util_file_read_line(buf, sizeof(buf), path)) {
+		warnx("Could not read from path '%s'", path);
+		free(path);
+		return -1;
+	}
+	free(path);
+	if (sscanf(buf, "%i:%i", &base_major, &base_minor) != 2) {
+		warn("Could not parse major:minor from string '%s'", buf);
+		return -1;
+	}
+	*base_dev = makedev(base_major, base_minor);
+
+	return 0;
+}
 
 /**
  * Identify device address
