From cb76e39cef7637bd53c4acd3182eae00fbc0d777 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20H=C3=B6ppner?= <hoeppner@linux.ibm.com>
Date: Wed, 21 Oct 2020 17:24:01 +0200
Subject: [PATCH] dasdfmt: Fix bad file descriptor error when running on
 symlinks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When calling dasdfmt on device node symlinks like
/dev/disk/by-id/ccw-0X9300, dasdfmt fails with "dasdfmt: the ioctl to get
the blocksize of the device failed (Bad file descriptor)"

This is because before the actual formatting process starts, the disk
will be disabled calling the BIODASDDISABLE ioctl, resulting in the
removal of the symlink. Trying to open this file later in the process to
retrieve e.g. blocksize information results in the mentioned error, as
the file doesn't exist any longer.

This incorrect behaviour was introduced with commit 8826028bdce2
("dasdfmt: Use libdasd provided ioctl functions") at which the use of a
globally available file descriptor was omitted. Instead, the ioctl
library functions require a device node to be passed as a function
parameter and will open a file descriptor themselves.
So, before that change, the global file descriptor was always valid even
when the file was removed.

In order to fix this without modifying the behaviour of libdasd,
introduce the two global variables dev_node and dev_path. dev_path is
the original device path entered by the user. dev_node on the other hand
is the reliable device node under /dev/block/ using the major and minor
numbers and is determined in get_device_name().
The dev_path is used for message output only and the dev_node variable
is used for the actual disk operations.

As these two variables are global, the device parameters for several
functions are removed to make the code a bit cleaner.

Fixes: 8826028bdce2 ("dasdfmt: Use libdasd provided ioctl functions")
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
 dasdfmt/dasdfmt.c | 115 ++++++++++++++++++++++++----------------------
 1 file changed, 61 insertions(+), 54 deletions(-)

diff --git a/dasdfmt/dasdfmt.c b/dasdfmt/dasdfmt.c
index a14e99e..ef48c7a 100644
--- a/dasdfmt/dasdfmt.c
+++ b/dasdfmt/dasdfmt.c
@@ -15,6 +15,7 @@
 
 #include "lib/dasd_base.h"
 #include "lib/dasd_sys.h"
+#include "lib/util_libc.h"
 #include "lib/util_opt.h"
 #include "lib/util_prg.h"
 #include "lib/util_proc.h"
@@ -28,7 +29,6 @@
 #define SEC_PER_HOUR (60 * 60)
 
 static int filedes;
-static char dev_filename[PATH_MAX];
 static int disk_disabled;
 static format_data_t format_params;
 static format_mode_t mode;
@@ -55,6 +55,8 @@ static const struct util_prg prg = {
  */
 static struct dasdfmt_globals {
 	dasd_information2_t dasd_info;
+	char *dev_path; /* device path entered by user */
+	char *dev_node; /* reliable device node determined by dasdfmt */
 	int   verbosity;
 	int   testmode;
 	int   withoutprompt;
@@ -473,7 +475,7 @@ static void program_interrupt_signal(int sig)
 	}
 
 	printf("Rereading the partition table...\n");
-	rc = dasd_reread_partition_table(dev_filename, 5);
+	rc = dasd_reread_partition_table(g.dev_node, 5);
 	if (rc) {
 		ERRMSG("%s: (signal handler) Re-reading partition table "
 		       "failed. (%s)\n", prog_name, strerror(rc));
@@ -486,12 +488,14 @@ static void program_interrupt_signal(int sig)
 }
 
 /*
- * check given device name for blanks and some special characters
+ * Check given device name for blanks and some special characters.
+ * Retrieve reliable device node and store device information in global
+ * dev_node and dev_path accordingly.
  */
-static void get_device_name(char *devname,
-			    int optind, int argc, char *argv[])
+static void get_device_name(int optind, int argc, char *argv[])
 {
 	struct util_proc_dev_entry dev_entry;
+	unsigned int maj, min;
 	struct stat dev_stat;
 
 	if (optind + 1 < argc)
@@ -505,17 +509,19 @@ static void get_device_name(char *devname,
 	if (strlen(argv[optind]) >= PATH_MAX)
 		ERRMSG_EXIT(EXIT_MISUSE, "%s: device name too long!\n",
 			    prog_name);
-	strcpy(devname, argv[optind]);
+	util_asprintf(&g.dev_path, argv[optind]);
 
-	if (stat(devname, &dev_stat) != 0)
+	if (stat(g.dev_path, &dev_stat) != 0)
 		ERRMSG_EXIT(EXIT_MISUSE, "%s: Could not get information for "
-			    "device node %s: %s\n", prog_name, devname,
+			    "device node %s: %s\n", prog_name, g.dev_path,
 			    strerror(errno));
 
-	if (minor(dev_stat.st_rdev) & PARTN_MASK) {
+	maj = major(dev_stat.st_rdev);
+	min = minor(dev_stat.st_rdev);
+	if (min & PARTN_MASK) {
 		ERRMSG_EXIT(EXIT_MISUSE, "%s: Unable to format partition %s. "
 			    "Please specify a device.\n", prog_name,
-			    devname);
+			    g.dev_path);
 	}
 
 	if (util_proc_dev_get_entry(dev_stat.st_rdev, 1, &dev_entry) == 0) {
@@ -525,15 +531,17 @@ static void get_device_name(char *devname,
 				    prog_name, dev_entry.name);
 	} else {
 		printf("%s WARNING: Unable to get driver name for device node %s",
-		       prog_name, devname);
+		       prog_name, g.dev_path);
 	}
+	/* Get reliable device node */
+	util_asprintf(&g.dev_node, "/dev/block/%d:%d", maj, min);
 }
 
-static void get_blocksize(const char *device, unsigned int *blksize)
+static void get_blocksize(unsigned int *blksize)
 {
 	int err;
 
-	err = dasd_get_blocksize(device, blksize);
+	err = dasd_get_blocksize(g.dev_node, blksize);
 	if (err != 0)
 		ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl to get the blocksize "
 			    "of the device failed (%s).\n", prog_name,
@@ -551,7 +559,7 @@ static void check_blocksize(unsigned int blksize)
 	    g.dasd_info.format == DASD_FORMAT_NONE)
 		return;
 
-	get_blocksize(dev_filename, &dev_blksize);
+	get_blocksize(&dev_blksize);
 	if (dev_blksize != blksize) {
 		ERRMSG_EXIT(EXIT_FAILURE, "WARNING: Device is formatted with a "
 			    "different blocksize (%d).\nUse --mode=full to "
@@ -591,12 +599,12 @@ static void check_layout(unsigned int intensity)
 /*
  * check for disk type and set some variables (e.g. usage count)
  */
-static void check_disk(char *devname)
+static void check_disk(void)
 {
 	int err;
 	bool ro;
 
-	err = dasd_is_ro(devname, &ro);
+	err = dasd_is_ro(g.dev_node, &ro);
 	if (err != 0)
 		ERRMSG_EXIT(EXIT_FAILURE,
 			    "%s: the ioctl call to retrieve read/write "
@@ -613,13 +621,13 @@ static void check_disk(char *devname)
 	if (strncmp(g.dasd_info.type, "ECKD", 4) != 0) {
 		ERRMSG_EXIT(EXIT_FAILURE,
 			    "%s: Unsupported disk type\n%s is not an "
-			    "ECKD disk!\n", prog_name, devname);
+			    "ECKD disk!\n", prog_name, g.dev_path);
 	}
 
-	if (dasd_sys_raw_track_access(devname)) {
+	if (dasd_sys_raw_track_access(g.dev_node)) {
 		ERRMSG_EXIT(EXIT_FAILURE,
 			    "%s: Device '%s' is in raw-track access mode\n",
-			    prog_name, devname);
+			    prog_name, g.dev_path);
 	}
 }
 
@@ -793,7 +801,7 @@ static format_check_t check_track_format(format_data_t *p)
 	};
 	int err;
 
-	err = dasd_check_format(dev_filename, &cdata);
+	err = dasd_check_format(g.dev_node, &cdata);
 	if (err != 0) {
 		if (err == ENOTTY) {
 			ERRMSG("%s: Missing kernel support for format checking",
@@ -942,9 +950,8 @@ static format_data_t ask_user_for_blksize(format_data_t params)
 /*
  * print all information needed to format the device
  */
-static void dasdfmt_print_info(char *devname, volume_label_t *vlabel,
-			       unsigned int cylinders, unsigned int heads,
-			       format_data_t *p)
+static void dasdfmt_print_info(volume_label_t *vlabel, unsigned int cylinders,
+			       unsigned int heads, format_data_t *p)
 {
 	char volser[6], vollbl[4];
 
@@ -954,7 +961,7 @@ static void dasdfmt_print_info(char *devname, volume_label_t *vlabel,
 	printf("Device Type: %s Provisioned\n",
 	       g.ese ? "Thinly" : "Fully");
 	printf("\nI am going to format the device ");
-	printf("%s in the following way:\n", devname);
+	printf("%s in the following way:\n", g.dev_path);
 	printf("   Device number of device : 0x%x\n", g.dasd_info.devno);
 	printf("   Labelling device        : %s\n",
 	       (g.writenolabel) ? "no" : "yes");
@@ -982,17 +989,17 @@ static void dasdfmt_print_info(char *devname, volume_label_t *vlabel,
 /*
  * get volser
  */
-static int dasdfmt_get_volser(char *devname, char *volser)
+static int dasdfmt_get_volser(char *volser)
 {
 	unsigned int blksize;
 	volume_label_t vlabel;
 
-	get_blocksize(devname, &blksize);
+	get_blocksize(&blksize);
 
 	if ((strncmp(g.dasd_info.type, "ECKD", 4) == 0) &&
 	    !g.dasd_info.FBA_layout) {
 		/* OS/390 and zOS compatible disk layout */
-		vtoc_read_volume_label(devname,
+		vtoc_read_volume_label(g.dev_node,
 				       g.dasd_info.label_block * blksize,
 				       &vlabel);
 		vtoc_volume_label_get_volser(&vlabel, volser);
@@ -1021,14 +1028,14 @@ static void dasdfmt_write_labels(volume_label_t *vlabel,
 	if (g.verbosity > 0)
 		printf("Retrieving dasd information... ");
 
-	get_blocksize(dev_filename, &blksize);
+	get_blocksize(&blksize);
 
 	/*
 	 * Don't rely on the cylinders returned by HDIO_GETGEO, they might be
 	 * to small. geo is only used to get the number of sectors, which may
 	 * vary depending on the format.
 	 */
-	rc = dasd_get_geo(dev_filename, &geo);
+	rc = dasd_get_geo(g.dev_node, &geo);
 	if (rc != 0)
 		ERRMSG_EXIT(EXIT_FAILURE, "%s: (write labels) IOCTL "
 			    "HDIO_GETGEO failed (%s).\n",
@@ -1058,10 +1065,10 @@ static void dasdfmt_write_labels(volume_label_t *vlabel,
 		ipl2_record_len	= sizeof(ipl2.data);
 	}
 
-	fd = open(dev_filename, O_RDWR);
+	fd = open(g.dev_node, O_RDWR);
 	if (fd < 0)
 		ERRMSG_EXIT(EXIT_FAILURE, "%s: Unable to open device "
-			    "'%s' (%s)\n", prog_name, dev_filename,
+			    "'%s' (%s)\n", prog_name, g.dev_path,
 			    strerror(errno));
 
 	if (lseek(fd, 0, SEEK_SET) != 0) {
@@ -1272,7 +1279,7 @@ static void dasdfmt_release_space(void)
 		return;
 
 	printf("Releasing space for the entire device...\n");
-	err = dasd_release_space(dev_filename, &r);
+	err = dasd_release_space(g.dev_node, &r);
 	if (err) {
 		ERRMSG_EXIT(EXIT_FAILURE, "%s: Could not release space (%s)\n",
 			    prog_name, strerror(err));
@@ -1298,7 +1305,7 @@ static void dasdfmt_prepare_and_format(unsigned int cylinders, unsigned int head
 	if (g.verbosity > 0)
 		printf("Detaching the device...\n");
 
-	disk_disable(dev_filename);
+	disk_disable(g.dev_node);
 
 	if (g.verbosity > 0)
 		printf("Invalidate first track...\n");
@@ -1347,7 +1354,7 @@ static void dasdfmt_expand_format(unsigned int cylinders, unsigned int heads,
 	if (g.verbosity > 0)
 		printf("Detaching the device...\n");
 
-	disk_disable(dev_filename);
+	disk_disable(g.dev_node);
 
 	process_tracks(cylinders, heads, p);
 
@@ -1402,7 +1409,7 @@ static void dasdfmt_quick_format(unsigned int cylinders, unsigned int heads,
 		printf("Formatting the first two tracks of the device.\n");
 
 	/* Disable the device before we do anything */
-	disk_disable(dev_filename);
+	disk_disable(g.dev_node);
 
 	/* Now do the actual formatting of our first two tracks */
 	err = dasd_format_disk(filedes, p);
@@ -1414,10 +1421,8 @@ static void dasdfmt_quick_format(unsigned int cylinders, unsigned int heads,
 	disk_enable();
 }
 
-static void do_format_dasd(char *devname,
-			   volume_label_t *vlabel,
-			   format_data_t *p, unsigned int cylinders,
-			   unsigned int heads)
+static void do_format_dasd(volume_label_t *vlabel, format_data_t *p,
+			   unsigned int cylinders, unsigned int heads)
 {
 	char inp_buffer[5];
 	int count, err;
@@ -1438,19 +1443,19 @@ static void do_format_dasd(char *devname,
 	}
 
 	if (g.verbosity > 0 || !g.withoutprompt || g.testmode)
-		dasdfmt_print_info(devname, vlabel, cylinders, heads, p);
+		dasdfmt_print_info(vlabel, cylinders, heads, p);
 
-	count = dasd_get_host_access_count(devname);
+	count = dasd_get_host_access_count(g.dev_node);
 	if (g.force_host) {
 		if (count > 1) {
 			ERRMSG_EXIT(EXIT_FAILURE,
 				    "\n%s: Disk %s is online on OS instances in %d different LPARs.\n"
 				    "Note: Your installation might include z/VM systems that are configured to\n"
 				    "automatically vary on disks, regardless of whether they are subsequently used.\n\n",
-				    prog_name, devname, count);
+				    prog_name, g.dev_path, count);
 		} else if (count < 0) {
 			ERRMSG("\nHosts access information not available for disk %s.\n\n",
-			       devname);
+			       g.dev_path);
 			return;
 		}
 	} else if (count > 1)
@@ -1459,7 +1464,7 @@ static void do_format_dasd(char *devname,
 		       "Ensure that the disk is not being used by a system outside your LPAR.\n"
 		       "Note: Your installation might include z/VM systems that are configured to\n"
 		       "automatically vary on disks, regardless of whether they are subsequently used.\n",
-		       devname, count);
+		       g.dev_path, count);
 
 	if (!g.testmode) {
 		if (!g.withoutprompt) {
@@ -1498,7 +1503,7 @@ static void do_format_dasd(char *devname,
 			dasdfmt_write_labels(vlabel, cylinders, heads);
 
 		printf("Rereading the partition table... ");
-		err = dasd_reread_partition_table(dev_filename, 5);
+		err = dasd_reread_partition_table(g.dev_node, 5);
 		if (err != 0) {
 			ERRMSG("%s: error during rereading the partition "
 			       "table: %s.\n", prog_name, strerror(err));
@@ -1696,22 +1701,22 @@ int main(int argc, char *argv[])
 	if (g.print_hashmarks)
 		PARSE_PARAM_INTO(g.hashstep, hashstep_str, 10, "hashstep");
 
-	get_device_name(dev_filename, optind, argc, argv);
+	get_device_name(optind, argc, argv);
 
-	rc = dasd_get_info(dev_filename, &g.dasd_info);
+	rc = dasd_get_info(g.dev_node, &g.dasd_info);
 	if (rc != 0)
 		ERRMSG_EXIT(EXIT_FAILURE, "%s: the ioctl call to retrieve "
 			    "device information failed (%s).\n",
 			    prog_name, strerror(rc));
 
-	g.ese = dasd_sys_ese(dev_filename);
+	g.ese = dasd_sys_ese(g.dev_node);
 	eval_format_mode();
 
 	/* Either let the user specify the blksize or get it from the kernel */
 	if (!g.blksize_specified) {
 		if (!(mode == FULL ||
 		      g.dasd_info.format == DASD_FORMAT_NONE) || g.check)
-			get_blocksize(dev_filename, &format_params.blksize);
+			get_blocksize(&format_params.blksize);
 		else
 			format_params = ask_user_for_blksize(format_params);
 	}
@@ -1727,15 +1732,15 @@ int main(int argc, char *argv[])
 			exit(1);
 		}
 
-		if (dasdfmt_get_volser(dev_filename, old_volser) == 0)
+		if (dasdfmt_get_volser(old_volser) == 0)
 			vtoc_volume_label_set_volser(&vlabel, old_volser);
 		else
 			ERRMSG_EXIT(EXIT_FAILURE,
 				    "%s: VOLSER not found on device %s\n",
-				    prog_name, dev_filename);
+				    prog_name, g.dev_path);
 	}
 
-	check_disk(dev_filename);
+	check_disk();
 
 	if (check_param(str, ERR_LENGTH, &format_params) < 0)
 		ERRMSG_EXIT(EXIT_MISUSE, "%s: %s\n", prog_name, str);
@@ -1746,8 +1751,10 @@ int main(int argc, char *argv[])
 	if (g.check)
 		check_disk_format(cylinders, heads, &format_params);
 	else
-		do_format_dasd(dev_filename, &vlabel, &format_params,
-			       cylinders, heads);
+		do_format_dasd(&vlabel, &format_params, cylinders, heads);
+
+	free(g.dev_path);
+	free(g.dev_node);
 
 	return 0;
 }
-- 
2.26.2

