diff mbox series

[2/2] Add disk partitioner handler

Message ID 20200121130336.16724-2-sbabic@denx.de
State Accepted
Headers show
Series None | expand

Commit Message

Stefano Babic Jan. 21, 2020, 1:03 p.m. UTC
Add handler to change partition of a block device.

Signed-off-by: Stefano Babic <sbabic@denx.de>
---
 Kconfig                     |   4 +
 Makefile.deps               |   4 +
 Makefile.flags              |   4 +
 doc/source/handlers.rst     |  59 +++++++
 handlers/Config.in          |  10 ++
 handlers/Makefile           |   1 +
 handlers/diskpart_handler.c | 297 ++++++++++++++++++++++++++++++++++++
 7 files changed, 379 insertions(+)
 create mode 100644 handlers/diskpart_handler.c
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index 6f1e7ad..59edb70 100644
--- a/Kconfig
+++ b/Kconfig
@@ -33,6 +33,10 @@  config HAVE_LIBCURL
 	bool
 	option env="HAVE_LIBCURL"
 
+config HAVE_LIBFDISK
+	bool
+	option env="HAVE_LIBFDISK"
+
 config HAVE_LIBGPIOD
 	bool
 	option env="HAVE_LIBGPIOD"
diff --git a/Makefile.deps b/Makefile.deps
index 5e2bd2f..b90ca0d 100644
--- a/Makefile.deps
+++ b/Makefile.deps
@@ -14,6 +14,10 @@  ifeq ($(HAVE_LIBCURL),)
 export HAVE_LIBCURL = y
 endif
 
+ifeq ($(HAVE_LIBFDISK),)
+export HAVE_LIBFDISK = y
+endif
+
 ifeq ($(HAVE_LIBGPIOD),)
 export HAVE_LIBGPIOD = y
 endif
diff --git a/Makefile.flags b/Makefile.flags
index 614d772..bd21197 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -166,6 +166,10 @@  ifeq ($(CONFIG_ZSTD),y)
 LDLIBS += zstd
 endif
 
+ifeq ($(CONFIG_DISKPART),y)
+LDLIBS += fdisk
+endif
+
 ifeq ($(CONFIG_RDIFFHANDLER),y)
 LDLIBS += rsync
 endif
diff --git a/doc/source/handlers.rst b/doc/source/handlers.rst
index e4b1619..6c42d1e 100644
--- a/doc/source/handlers.rst
+++ b/doc/source/handlers.rst
@@ -23,6 +23,8 @@  Supplied handlers
 In mainline there are the handlers for the most common cases. They include:
 	- flash devices in raw mode (both NOR and NAND)
 	- UBI volumes
+        - UBI volumus partitioner
+        - disk partitioner
 	- raw devices, such as a SD Card partition
 	- bootloader (U-Boot, GRUB, EFI Boot Guard) environment
 	- Lua scripts
@@ -684,3 +686,60 @@  Properties ``size`` and ``offset`` are optional, all the other properties are ma
     | offset      | string   | Offset (in bytes) to the start of the partition.   |
     |             |          | If not set, default value 0 will be used.          |
     +-------------+----------+----------------------------------------------------+
+
+Disk partitioner
+----------------
+
+This handler creates or modifies partitions using the library libfdisk. Handler must be put into
+the `partitions` section of sw-description. Setup for each partition is put into the `properties` field
+of sw-description.
+
+.. table:: Properties for diskpart handler
+
+   +-------------+----------+----------------------------------------------------+
+   |  Name       |  Type    |  Description                                       |
+   +=============+==========+====================================================+
+   | labeltype   | string   | "gpt" or "dos"                                     |
+   +-------------+----------+----------------------------------------------------+
+   | partition-X | array    | Array of values belonging to the partition number X|
+   +-------------+----------+----------------------------------------------------+
+
+For each partition, an array of couples key=value must be given. The following keys are
+supported:
+
+.. table:: Setup for a disk partition
+
+   +-------------+----------+----------------------------------------------------+
+   |  Name       |  Type    |  Description                                       |
+   +=============+==========+====================================================+
+   | size        | string   | Size of partition. K, M and G can be used for      |
+   |             |          | Kilobytes, Megabytes and Gigabytes.                |
+   +-------------+----------+----------------------------------------------------+
+   | start       | integer  | First sector for the partition                     |
+   +-------------+----------+----------------------------------------------------+
+   | name        | string   | Name of the partition                              |
+   +-------------+----------+----------------------------------------------------+
+   | type        | string   | Type of partition, it has two different meanings.  |
+   |             |          | It is the hex code for DOS (MBR) partition table   |
+   |             |          | or it is the string identifier in case of GPT.     |
+   +-------------+----------+----------------------------------------------------+
+
+
+
+Example:
+
+::
+
+        properties: {
+		labeltype = "gpt";
+		partition-1 = [ "size=64M", "start=2048",
+                        "name=bigrootfs", "type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B"];
+		partition-2 = ["size=256M", "start=133120",
+                        "name=ldata", "type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"];
+		partition-3 = ["size=512M", "start=657408",
+                        "name=log", "type=0FC63DAF-8483-4772-8E79-3D69D8477DE4"];
+		partition-4 = ["size=4G", "start=1705984",
+                        "name=system",  "type=0FC63DAF-8483-4772-8E79-3D69D8477DE4"];
+		partition-5 = ["size=512M", "start=10094592",
+                        "name=part5",  "type=0FC63DAF-8483-4772-8E79-3D69D8477DE4"];
+	}
diff --git a/handlers/Config.in b/handlers/Config.in
index be1c7a0..bf14303 100644
--- a/handlers/Config.in
+++ b/handlers/Config.in
@@ -92,6 +92,16 @@  config CFIHAMMING1
 
 	  You do not need this if you do not have an OMAP SoC.
 
+config DISKPART
+	bool "diskpart"
+	depends on HAVE_LIBFDISK
+	default n
+	help
+	  Handler to partition a disk, eMMC or SD
+
+comment "diskpart support needs libfdisk"
+	depends on !HAVE_LIBFDISK
+
 config RAW
 	bool "raw"
 	default n
diff --git a/handlers/Makefile b/handlers/Makefile
index b756f31..1ad62be 100644
--- a/handlers/Makefile
+++ b/handlers/Makefile
@@ -11,6 +11,7 @@  obj-y	+= dummy_handler.o
 obj-$(CONFIG_ARCHIVE) += archive_handler.o
 obj-$(CONFIG_BOOTLOADERHANDLER) += boot_handler.o
 obj-$(CONFIG_CFI)	+= flash_handler.o
+obj-$(CONFIG_DISKPART)	+= diskpart_handler.o
 obj-$(CONFIG_CFIHAMMING1)+= flash_hamming1_handler.o
 obj-$(CONFIG_LUASCRIPTHANDLER) += lua_scripthandler.o
 obj-$(CONFIG_RAW)	+= raw_handler.o
diff --git a/handlers/diskpart_handler.c b/handlers/diskpart_handler.c
new file mode 100644
index 0000000..b61d574
--- /dev/null
+++ b/handlers/diskpart_handler.c
@@ -0,0 +1,297 @@ 
+/*
+ * (C) Copyright 2019
+ * Stefano Babic, DENX Software Engineering, sbabic@denx.de.
+ *
+ * SPDX-License-Identifier:     GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <libfdisk/libfdisk.h>
+#include "swupdate.h"
+#include "handler.h"
+#include "util.h"
+
+void diskpart_handler(void);
+
+/**
+ * Keys for the properties field in sw-description
+ */
+enum partfield {
+	PART_SIZE = 0,
+	PART_START,
+	PART_TYPE,
+	PART_NAME
+};
+
+const char *fields[] = {
+	[PART_SIZE] = "size",
+	[PART_START] = "start",
+	[PART_TYPE] = "type",
+	[PART_NAME] = "name"
+};
+
+struct partition_data {
+	size_t partno;
+	uint64_t size;
+	size_t start;
+	char type[SWUPDATE_GENERAL_STRING_SIZE];
+	char name[SWUPDATE_GENERAL_STRING_SIZE];
+	LIST_ENTRY(partition_data) next;
+};
+LIST_HEAD(listparts, partition_data);
+
+/*
+ * Internal handler data
+ */
+struct hnd_priv {
+	enum fdisk_labeltype labeltype;
+	struct listparts listparts;	/* list of partitions */
+};
+
+/**
+ * diskpart_set_partition - set values in a fdisk_partition
+ * @cxt: libfdisk context
+ * @pa: pointer to fdisk_partition to be changed
+ * @part: structure with values to be set, read from sw-description
+ *
+ * return 0 if ok
+ */
+static int diskpart_set_partition(struct fdisk_context *cxt,
+				  struct fdisk_partition *pa,
+				  struct partition_data *part)
+{
+	unsigned long sector_size = fdisk_get_sector_size(cxt);
+	struct fdisk_label *lb;
+	struct fdisk_parttype *parttype = NULL;
+	int ret;
+
+	lb = fdisk_get_label(cxt, NULL);
+
+	if (!sector_size)
+		sector_size = 1;
+	ret = fdisk_partition_set_partno(pa, part->partno) ||
+	      fdisk_partition_set_size(pa, part->size / sector_size) ||
+	      fdisk_partition_set_name(pa, part->name) ||
+	      fdisk_partition_set_start(pa, part->start);
+
+	/*
+	 * GPT uses strings instead of hex code for partition type
+	 */
+	if (fdisk_is_label(cxt, GPT)) {
+		parttype = fdisk_label_get_parttype_from_string(lb, part->type); 
+	} else if (fdisk_is_label(cxt, DOS)) {
+		parttype = fdisk_label_get_parttype_from_code(lb, ustrtoull(part->type, 16));
+	} else
+		WARN("Partition type set just for GPT or DOS");
+
+	if (parttype)
+		ret |= fdisk_partition_set_type(pa, parttype);
+	return ret;
+}
+
+static int diskpart(struct img_type *img,
+	void __attribute__ ((__unused__)) *data)
+{
+	char *lbtype = dict_get_value(&img->properties, "labeltype");
+	struct dict_list *parts;
+	struct dict_list_elem *elem;
+	struct fdisk_context *cxt;
+	struct partition_data *part;
+	int ret = 0;
+	int i;
+	struct hnd_priv priv;
+
+	if (lbtype && strcmp(lbtype, "gpt") && strcmp(lbtype, "dos")) {
+		ERROR("Just GPT or DOS partition table are supported");
+		return -EINVAL;
+	}
+	LIST_INIT(&priv.listparts);
+	if (!strlen(img->device)) {
+		ERROR("Partition handler without setting the device");
+		return -EINVAL;
+	}
+
+	cxt = fdisk_new_context();
+	if (!cxt) {
+		ERROR("Failed to allocate libfdisk context");
+		return -ENOMEM;
+	}
+
+	ret = fdisk_assign_device(cxt, img->device, 0);
+	if (ret == -EACCES) {
+		ERROR("no access to %s", img->device);
+		goto handler_release;
+	}
+
+	struct dict_entry *entry;
+	LIST_FOREACH(entry, &img->properties, next) {
+		parts = &entry->list;
+		if (!parts)
+			continue;
+
+		if (strncmp(dict_entry_get_key(entry),
+				"partition-", strlen("partition-")))
+			continue;
+
+		part = (struct partition_data *)calloc(1, sizeof(struct partition_data));
+		if (!part) {
+			ERROR("FAULT: no memory");
+			ret = -ENOMEM;
+			goto handler_exit;
+		}
+		elem = LIST_FIRST(parts);
+
+		part->partno = strtoul(entry->key  + strlen("partition-"), NULL, 10);
+		while (elem) {
+			char *equal = index(elem->value, '=');
+			if (equal) {
+				for (i = 0; i < ARRAY_SIZE(fields); i++) {
+					if (!((equal - elem->value) == strlen(fields[i]) &&
+						!strncmp(elem->value, fields[i], strlen(fields[i]))))
+						continue;
+					equal++;
+					switch (i) {
+					case PART_SIZE:
+						part->size = ustrtoull(equal, 10);
+						break;
+					case PART_START:
+						part->start = ustrtoull(equal, 10);
+						break;
+					case PART_TYPE:
+						strncpy(part->type, equal, sizeof(part->type));
+						break;
+					case PART_NAME:
+						strncpy(part->name, equal, sizeof(part->name)); 
+						break;
+					}
+				}
+			}
+			elem = LIST_NEXT(elem, next);
+		}
+
+		TRACE("partition-%lu:%s size %lu start %lu type %s",
+			part->partno,
+			part->name,
+			part->size,
+			part->start,
+			part->type);
+
+		/*
+		 * Partitions in sw-description start from 1,
+		 * libfdisk first partition is 0
+		 */
+		if (part->partno > 0)
+			part->partno--;
+
+		/*
+		 * Insert the partition in the list sorted by partno
+		 */
+		struct partition_data *p = LIST_FIRST(&priv.listparts);
+		if (!p)
+			LIST_INSERT_HEAD(&priv.listparts, part, next);
+		else {
+			while (LIST_NEXT(p, next) &&
+					LIST_NEXT(p, next)->partno < part->partno)
+				p = LIST_NEXT(p, next);
+			LIST_INSERT_BEFORE(p, part, next);
+		}
+	}
+	/*
+	 * Check partition table
+	 */
+	if (!fdisk_has_label(cxt)) {
+		WARN("%s does not contain a recognized partition table",
+		     img->device);
+		fdisk_create_disklabel(cxt, lbtype);
+	} else if (lbtype) {
+		if (!strcmp(lbtype, "gpt"))
+			priv.labeltype = FDISK_DISKLABEL_GPT;
+		else
+			priv.labeltype = FDISK_DISKLABEL_DOS;
+
+		if (!fdisk_is_labeltype(cxt, priv.labeltype)) {
+			WARN("Partition table of different type, setting to %s, all data lost !",
+				lbtype);
+			fdisk_create_disklabel(cxt, lbtype);
+		}
+	}
+
+	i = 0;
+
+	if (priv.labeltype == FDISK_DISKLABEL_DOS) {
+		fdisk_delete_all_partitions(cxt);
+	}
+
+	LIST_FOREACH(part, &priv.listparts, next) {
+		struct fdisk_partition *pa = NULL;
+		size_t partno;
+
+		/*
+		 * Allow to have not consecutives partitions
+		 */
+		if (part->partno > i) {
+			while (i < part->partno) {
+				TRACE("DELETE PARTITION %d", i);
+				fdisk_delete_partition(cxt, i);
+				i++;
+			}
+		}
+
+		if (fdisk_get_partition(cxt, part->partno, &pa)) {
+			struct fdisk_partition *newpa;
+			newpa = fdisk_new_partition();
+			ret = diskpart_set_partition(cxt, newpa, part);
+			if (ret) {
+				WARN("I cannot set all partition's parameters");
+			}
+			if (fdisk_add_partition(cxt, newpa, &partno) < 0) {
+				ERROR("I cannot add partition %lu(%s)", part->partno, part->name);
+			}
+			fdisk_unref_partition(newpa);
+		} else {
+			ret = diskpart_set_partition(cxt, pa, part);
+			if (ret) {
+				WARN("I cannot set all partition's parameters");
+			}
+			if (fdisk_set_partition(cxt, part->partno, pa) < 0) {
+				ERROR("I cannot modify partition %lu(%s)", part->partno, part->name);
+			}
+			fdisk_unref_partition(pa);
+		}
+		i++;
+	}
+
+	/*
+	 * Everything done, write into disk
+	 */
+	ret = fdisk_write_disklabel(cxt);
+
+handler_exit:
+	if (fdisk_deassign_device(cxt, 0))
+		WARN("Error deassign device %s", img->device);
+
+handler_release:
+	LIST_FOREACH(part, &priv.listparts, next) {
+		LIST_REMOVE(part, next);
+		free(part);
+	}
+
+	fdisk_unref_context(cxt);
+
+	return ret;
+}
+
+__attribute__((constructor))
+void diskpart_handler(void)
+{
+	register_handler("diskpart", diskpart,
+				PARTITION_HANDLER | NO_DATA_HANDLER, NULL);
+}