From 35ca51182782193f555fbdcb06bb10766550d017 Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Wed, 30 Nov 2016 12:43:10 +0100
Subject: [PATCH] sfdisk: support empty label use-case

By default sfdisk creates partition table when a first partition is
specified, otherwise the device is not modified. This force users to
create at least one partition.

This commit allows to create empty label without partitions if "label:
<name>" header line is specified by script.

The commit also modifies "New situation:" output to list label name
and label identifier.

Addresses: https://github.com/karelzak/util-linux/issues/374
Signed-off-by: Karel Zak <kzak@redhat.com>
---
 disk-utils/fdisk-list.c    | 23 +++++++++++++++--------
 disk-utils/fdisk-list.h    |  1 +
 disk-utils/sfdisk.8        | 18 +++++++++++++++++-
 disk-utils/sfdisk.c        | 17 +++++++++++++++++
 libfdisk/src/libfdisk.h.in |  1 +
 libfdisk/src/libfdisk.sym  |  5 +++++
 libfdisk/src/script.c      | 20 +++++++++++++++++++-
 7 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/disk-utils/fdisk-list.c b/disk-utils/fdisk-list.c
index e6b2033..c9560f4 100644
--- a/disk-utils/fdisk-list.c
+++ b/disk-utils/fdisk-list.c
@@ -34,10 +34,23 @@ static int is_ide_cdrom_or_tape(char *device)
 	return ret;
 }
 
+void list_disk_identifier(struct fdisk_context *cxt)
+{
+	struct fdisk_label *lb = fdisk_get_label(cxt, NULL);
+	char *id = NULL;
+
+	if (fdisk_has_label(cxt))
+		fdisk_info(cxt, _("Disklabel type: %s"),
+				fdisk_label_get_name(lb));
+
+	if (!fdisk_is_details(cxt) && fdisk_get_disklabel_id(cxt, &id) == 0 && id) {
+		fdisk_info(cxt, _("Disk identifier: %s"), id);
+		free(id);
+	}
+}
 
 void list_disk_geometry(struct fdisk_context *cxt)
 {
-	char *id = NULL;
 	struct fdisk_label *lb = fdisk_get_label(cxt, NULL);
 	uint64_t bytes = fdisk_get_nsectors(cxt) * fdisk_get_sector_size(cxt);
 	char *strsz = size_to_human_string(SIZE_SUFFIX_SPACE
@@ -71,14 +84,8 @@ void list_disk_geometry(struct fdisk_context *cxt)
 	if (fdisk_get_alignment_offset(cxt))
 		fdisk_info(cxt, _("Alignment offset: %lu bytes"),
 				fdisk_get_alignment_offset(cxt));
-	if (fdisk_has_label(cxt))
-		fdisk_info(cxt, _("Disklabel type: %s"),
-				fdisk_label_get_name(lb));
 
-	if (!fdisk_is_details(cxt) && fdisk_get_disklabel_id(cxt, &id) == 0 && id) {
-		fdisk_info(cxt, _("Disk identifier: %s"), id);
-		free(id);
-	}
+	list_disk_identifier(cxt);
 }
 
 void list_disklabel(struct fdisk_context *cxt)
diff --git a/disk-utils/fdisk-list.h b/disk-utils/fdisk-list.h
index eddab92..4ed5c25 100644
--- a/disk-utils/fdisk-list.h
+++ b/disk-utils/fdisk-list.h
@@ -2,6 +2,7 @@
 #define UTIL_LINUX_FDISK_LIST_H
 
 extern void list_disklabel(struct fdisk_context *cxt);
+extern void list_disk_identifier(struct fdisk_context *cxt);
 extern void list_disk_geometry(struct fdisk_context *cxt);
 extern void list_freespace(struct fdisk_context *cxt);
 
diff --git a/disk-utils/sfdisk.8 b/disk-utils/sfdisk.8
index fcde872..efe4a86 100644
--- a/disk-utils/sfdisk.8
+++ b/disk-utils/sfdisk.8
@@ -212,7 +212,10 @@ Deprecated option.  Only the sector unit is supported.
 .BR \-X , " \-\-label " \fItype
 Specify the disk label type (e.g. \fBdos\fR, \fBgpt\fR, ...).  If this option
 is not given, then \fBsfdisk\fR defaults to the existing label, but if there
-is no label on the device yet, then the type defaults to \fBdos\fR.
+is no label on the device yet, then the type defaults to \fBdos\fR. The default
+or the current label may be overwritten by the "label: <name>" script header
+line. The option \fB\-\-label\fR does not force \fBsfdisk\fR to create empty
+disk label (see the \fBEMPTY DISK LABEL\fR section below).
 .TP
 .BR \-Y , " \-\-label\-nested " \fItype
 Force editing of a nested disk label.  The primary disk label has to exist already.
@@ -404,6 +407,19 @@ For backward compatibility the \fBId=\fR field has the same meaning.
 .RE
 .RE
 
+.SH "EMPTY DISK LABEL"
+.B sfdisk
+does not create partition table without partitions by default. The lines with
+partitions are expected in the script by default. The empty partition table has
+to be explicitly requested by "label: <name>" script header line without any
+partitions lines. For example:
+.RS
+.sp
+.B "echo 'label: gpt' | sfdisk /dev/sdb"
+.sp
+.RE
+creates empty GPT partition table. Note that the \fB\-\-append\fR disables this feature.
+
 .SH "BACKING UP THE PARTITION TABLE"
 It is recommended to save the layout of your devices.
 .B sfdisk
diff --git a/disk-utils/sfdisk.c b/disk-utils/sfdisk.c
index 10307ad..2d65974 100644
--- a/disk-utils/sfdisk.c
+++ b/disk-utils/sfdisk.c
@@ -1766,8 +1766,25 @@ static int command_fdisk(struct sfdisk *sf, int argc, char **argv)
 		}
 	} while (1);
 
+	/* create empty disk label if label, but no partition specified */
+	if (rc == SFDISK_DONE_EOF && created == 0
+	    && fdisk_script_has_force_label(dp) == 1
+	    && fdisk_table_get_nents(tb) == 0
+	    && fdisk_script_get_header(dp, "label")) {
+
+		int xrc = fdisk_apply_script_headers(sf->cxt, dp);
+		created = !xrc;
+		if (xrc) {
+			fdisk_warnx(sf->cxt, _(
+				  "Failed to apply script headers, "
+				  "disk label not created."));
+			rc = SFDISK_DONE_ABORT;
+		}
+	}
+
 	if (!sf->quiet && rc != SFDISK_DONE_ABORT) {
 		fdisk_info(sf->cxt, _("\nNew situation:"));
+		list_disk_identifier(sf->cxt);
 		list_disklabel(sf->cxt);
 	}
 
diff --git a/libfdisk/src/libfdisk.h.in b/libfdisk/src/libfdisk.h.in
index 9154f5b..59cce19 100644
--- a/libfdisk/src/libfdisk.h.in
+++ b/libfdisk/src/libfdisk.h.in
@@ -642,6 +642,7 @@ const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name);
 int fdisk_script_set_header(struct fdisk_script *dp, const char *name, const char *data);
 struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp);
 int fdisk_script_get_nlines(struct fdisk_script *dp);
+int fdisk_script_has_force_label(struct fdisk_script *dp);
 
 int fdisk_script_set_userdata(struct fdisk_script *dp, void *data);
 void *fdisk_script_get_userdata(struct fdisk_script *dp);
diff --git a/libfdisk/src/libfdisk.sym b/libfdisk/src/libfdisk.sym
index 02cd7a8..d6d4ac5 100644
--- a/libfdisk/src/libfdisk.sym
+++ b/libfdisk/src/libfdisk.sym
@@ -274,3 +274,8 @@ FDISK_2.29 {
 	fdisk_labelitem_is_number;
 	fdisk_gpt_set_npartitions;
 } FDISK_2.28;
+
+
+FDISK_2.30 {
+	fdisk_script_has_force_label;
+} FDISK_2.29;
diff --git a/libfdisk/src/script.c b/libfdisk/src/script.c
index ae7e99a..0d1f260 100644
--- a/libfdisk/src/script.c
+++ b/libfdisk/src/script.c
@@ -36,7 +36,8 @@ struct fdisk_script {
 	size_t			nlines;
 	struct fdisk_label	*label;
 
-	unsigned int		json : 1;		/* JSON output */
+	unsigned int		json : 1,		/* JSON output */
+				force_label : 1;	/* label: <name> specified */
 };
 
 
@@ -354,6 +355,22 @@ int fdisk_script_get_nlines(struct fdisk_script *dp)
 }
 
 /**
+ * fdisk_script_has_force_label:
+ * @dp: script
+ *
+ * Note that fdisk_script_set_header(dp, "label", name) does not modify
+ * force_label status. The label has to be specified by script.
+ *
+ * Returns: true if "label: <name>" has been parsed.
+ */
+int fdisk_script_has_force_label(struct fdisk_script *dp)
+{
+	assert(dp);
+	return dp->force_label;
+}
+
+
+/**
  * fdisk_script_read_context:
  * @dp: script
  * @cxt: context
@@ -706,6 +723,7 @@ static int parse_line_header(struct fdisk_script *dp, char *s)
 	if (strcmp(name, "label") == 0) {
 		if (dp->cxt && !fdisk_get_label(dp->cxt, value))
 			goto done;			/* unknown label name */
+		dp->force_label = 1;
 	} else if (strcmp(name, "unit") == 0) {
 		if (strcmp(value, "sectors") != 0)
 			goto done;			/* only "sectors" supported */
-- 
2.10.2