/*
 * fdasd - Create or modify partitions on ECKD DASDs
 *
 * Copyright IBM Corp. 2001, 2018
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include <getopt.h>
#include <stdio.h>
#include <sys/sysmacros.h>

#include "lib/dasd_base.h"
#include "lib/dasd_sys.h"
#include "lib/util_base.h"
#include "lib/util_opt.h"
#include "lib/util_prg.h"
#include "lib/vtoc.h"
#include "lib/zt_common.h"

#include "fdasd.h"

/* global variables */
static struct hd_geometry geo;
static char line_buffer[LINE_LENGTH];
static char *line_ptr = line_buffer;

/*
 * Array of all supported partition types and its corresponding name and
 * DSNAME (Data Set Name)
 */
static partition_type_t partition_types[] = {
	[PARTITION_NEW] = {
		.name = "Linux native",
		.dsname = "NEW",
		.type = PARTITION_NATIVE,
	},
	[PARTITION_NATIVE] = {
		.name = "Linux native",
		.dsname = "NATIVE",
		.type = PARTITION_NATIVE,
	},
	[PARTITION_SWAP] = {
		.name = "Linux swap",
		.dsname = "SWAP",
		.type = PARTITION_SWAP,
	},
	[PARTITION_RAID] = {
		.name = "Linux raid",
		.dsname = "RAID",
		.type = PARTITION_RAID,
	},
	[PARTITION_LVM] = {
		.name = "Linux lvm",
		.dsname = "LVM",
		.type = PARTITION_LVM,
	},
	[PARTITION_GPFS] = {
		.name = "GPFS NSD",
		.dsname = "GPFS",
		.type = PARTITION_GPFS,
	},
};

/*
 * Return the partition name corresponding to DSNAME (Data Set Name)
 */
static int get_part_name_by_dsname(char *dsname, char **name)
{
	unsigned int i;

	if (!dsname) {
		*name = partition_types[PARTITION_NATIVE].name;
		return 0;
	}

	for (i = 0; i < ARRAY_SIZE(partition_types); i++) {
		if (strstr(dsname, partition_types[i].dsname) != NULL) {
			*name = partition_types[i].name;
			return 0;
		}
	}

	return 1;
}

/*
 * Return the partition type corresponding to DSNAME (Data Set Name)
 */
static int get_part_type_by_dsname(char *dsname, int *type)
{
	unsigned int i;

	if (!dsname) {
		*type = PARTITION_NATIVE;
		return 0;
	}

	for (i = 0; i < ARRAY_SIZE(partition_types); i++) {
		if (strstr(dsname, partition_types[i].dsname) != NULL) {
			*type = partition_types[i].type;
			return 0;
		}
	}

	return 1;
}

/*
 * Return the DSNAME (Data Set Name) of a partition corresponding to its type
 */
static int get_part_dsname_by_type(unsigned int type, char **dsname)
{
	if (type > ARRAY_SIZE(partition_types))
		return 1;

	if (type == 0)
		*dsname = partition_types[PARTITION_NATIVE].dsname;
	else
		*dsname = partition_types[type].dsname;

	return 0;
}

static const struct util_prg prg = {
	.desc = "Manage partitions on DASD volumes.\n"
		"DEVICE is the node of the device (e.g. '/dev/dasda').",
	.args = "DEVICE",
	.copyright_vec = {
		{
			.owner = "IBM Corp.",
			.pub_first = 2001,
			.pub_last = 2017,
		},
		UTIL_PRG_COPYRIGHT_END
	}
};

static struct util_opt opt_vec[] = {
	UTIL_OPT_SECTION("NON-INTERACTIVE MODE"),
	{
		.option = { "auto", no_argument, NULL, 'a' },
		.desc = "Create a single partition spanning the entire disk",
	},
	{
		.option = { "config", required_argument, NULL, 'c' },
		.argument = "FILE",
		.desc = "Create partitions(s) based on content of FILE",
	},
	{
		.option = { "keep_volser", no_argument, NULL, 'k' },
		.desc = "Do not change the current volume serial",
	},
	{
		.option = { "label", required_argument, NULL, 'l' },
		.argument = "VOLSER",
		.desc = "Set the volume serial to VOLSER",
	},
	UTIL_OPT_SECTION("MISC"),
	{
		.option = { "check_host_count", no_argument, NULL, 'C' },
		.desc = "Check if device is in use by other hosts",
	},
	{
		.option = { "force", optional_argument, NULL, 'f' },
		.argument = "TYPE,SIZE",
		.desc = "Force fdasd to work on non DASD devices with assumed "
			"TYPE (3390, 3380, or 9345) and blocksize SIZE",
	},
	{
		.option = { "volser", no_argument, NULL, 'i' },
		.desc = "Print volume serial",
	},
	{
		.option = { "table", no_argument, NULL, 'p' },
		.desc = "Print partition table",
	},
	{
		.option = { "verbose", no_argument, NULL, 'r' },
		.desc = "Provide more verbose output",
	},
	{
		.option = { "silent", no_argument, NULL, 's' },
		.desc = "Suppress messages",
	},
	UTIL_OPT_HELP,
	UTIL_OPT_VERSION,
	UTIL_OPT_END
};

static int getpos(fdasd_anchor_t *anc, int dsn)
{
	return anc->partno[dsn];
}

static int getdsn(fdasd_anchor_t *anc, int pos)
{
	int i;

	for (i = 0; i < USABLE_PARTITIONS; i++) {
		if (anc->partno[i] == pos)
			return i;
	}

	return -1;
}

static void setpos(fdasd_anchor_t *anc, int dsn, int pos)
{
	anc->partno[dsn] = pos;
}

static u_int32_t get_usable_cylinders(fdasd_anchor_t *anc)
{
	u_int32_t cyl;

	/* large volume */
	if (anc->f4->DS4DEVCT.DS4DSCYL == LV_COMPAT_CYL &&
	    anc->f4->DS4DCYL > anc->f4->DS4DEVCT.DS4DSCYL)
		return anc->f4->DS4DCYL;
	/* normal volume */
	if (anc->f4->DS4DEVCT.DS4DEVFG & ALTERNATE_CYLINDERS_USED)
		cyl = anc->f4->DS4DEVCT.DS4DSCYL - (u_int16_t)anc->f4->DS4DEVAC;
	else
		cyl = anc->f4->DS4DEVCT.DS4DSCYL;

	return cyl;
}

static void get_addr_of_highest_f1_f8_label(fdasd_anchor_t *anc, cchhb_t *addr)
{
	u_int8_t record;
	/* We have to count the following labels:
	 * one format 4
	 * one format 5
	 * format 7 only if we have moren then BIG_DISK_SIZE tracks
	 * one for each format 1 or format 8 label == one for each partition
	 * one for each format 9 label before the last format 8
	 * We assume that all partitions use format 8 labels when
	 *  anc->formatted_cylinders > LV_COMPAT_CYL
	 * Note: Record zero is special, so block 0 on our disk is record 1!
	 */

	record = anc->used_partitions + 2;
	if (anc->big_disk)
		record++;
	if (anc->formatted_cylinders > LV_COMPAT_CYL)
		record += anc->used_partitions - 1;
	vtoc_set_cchhb(addr, VTOC_START_CC, VTOC_START_HH, record);
}

/*
 * Check for valid volume serial characters (max. 6) - remove invalid.
 * If volser is empty, fill with default volser.
 */
static void fdasd_check_volser(char *volser, int devno)
{
	int from, to;

	for (from = 0, to = 0; from < VOLSER_LENGTH && volser[from]; from++)
		if ((volser[from] >= 0x23 &&
		     volser[from] <= 0x25) || /* # $ % */
		    (volser[from] >= 0x30 &&
		     volser[from] <= 0x39) || /* 0-9 */
		    (volser[from] >= 0x40 &&
		     volser[from] <= 0x5a) || /* @ A-Z */
		    (volser[from] >= 0x61 &&
		     volser[from] <= 0x7a))   /* a-z */
			volser[to++] = toupper(volser[from]);

	volser[to] = 0x00;

	if (volser[0] == 0x00)
		sprintf(volser, "0X%04x", devno);
}

/*
 * Free memory of fdasd anchor struct.
 */
static void fdasd_cleanup(fdasd_anchor_t *anchor)
{
	partition_info_t *part_info, *next;
	int i;

	if (anchor == NULL)
		return;

	if (anchor->f4 != NULL)
		free(anchor->f4);
	if (anchor->f5 != NULL)
		free(anchor->f5);
	if (anchor->f7 != NULL)
		free(anchor->f7);
	if (anchor->vlabel != NULL)
		free(anchor->vlabel);

	part_info = anchor->first;
	for (i = 1; i <= USABLE_PARTITIONS && part_info != NULL; i++) {
		next = part_info->next;
		free(part_info->f1);
		free(part_info);
		part_info = next;
	}
}

/*
 * Exit fdasd.
 */
static void __noreturn fdasd_exit(fdasd_anchor_t *anchor, int rc)
{
	fdasd_cleanup(anchor);
	exit(rc);
}

/*
 *
 */
static void fdasd_error(fdasd_anchor_t *anc, enum fdasd_failure why, char *str)
{
	char err_str[ERROR_STRING_SIZE];

	switch (why) {
	case parser_failed:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s parser error\n%s\n", FDASD_ERROR, str);
		break;
	case unable_to_open_disk:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s open error\n%s\n", FDASD_ERROR, str);
		break;
	case unable_to_seek_disk:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s seek error\n%s\n", FDASD_ERROR, str);
		break;
	case unable_to_read_disk:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s read error\n%s\n", FDASD_ERROR, str);
		break;
	case read_only_disk:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s write error\n%s\n", FDASD_ERROR, str);
		break;
	case unable_to_ioctl:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s IOCTL error\n%s\n", FDASD_ERROR, str);
		break;
	case wrong_disk_type:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s Unsupported disk type\n%s\n",
			 FDASD_ERROR, str);
		break;
	case wrong_disk_format:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s Unsupported disk format\n%s\n",
			 FDASD_ERROR, str);
		break;
	case disk_in_use:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s Disk in use\n%s\n", FDASD_ERROR, str);
		break;
	case config_syntax_error:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s Config file syntax error\n%s\n",
			 FDASD_ERROR, str);
		break;
	case vlabel_corrupted:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s Volume label is corrupted.\n%s\n",
			 FDASD_ERROR, str);
		break;
	case dsname_corrupted:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s a data set name is corrupted.\n%s\n",
			 FDASD_ERROR, str);
		break;
	case malloc_failed:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s space allocation\n%s\n", FDASD_ERROR, str);
		break;
	case device_verification_failed:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s device verification failed\n%s\n",
			 FDASD_ERROR, str);
		break;
	case volser_not_found:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s VOLSER not found on device %s\n",
			 FDASD_ERROR, str);
		break;
	default:
		snprintf(err_str, ERROR_STRING_SIZE,
			 "%s Fatal error\n%s\n",
			 FDASD_ERROR, str);
	}

	fputc('\n', stderr);
	fputs(err_str, stderr);

	fdasd_exit(anc, EXIT_FAILURE);
}

/*
 * Read line from stdin into global line_buffer
 * and set global line_ptr to first printing character except space.
 */
static int read_line(void)
{
	bzero(line_buffer, LINE_LENGTH);
	line_ptr = line_buffer;
	if (!fgets(line_buffer, LINE_LENGTH, stdin)) {
		clearerr(stdin);
		return 0;
	}
	while (*line_ptr && !isgraph(*line_ptr))
		line_ptr++;

	return *line_ptr;
}

/*
 *
 */
static char read_char(char *mesg)
{
	fputs(mesg, stdout);
	read_line();

	return *line_ptr;
}

/*
 * Print question string an enforce y/n answer.
 */
static int yes_no(char *question_str)
{
	ssize_t bytes_read;
	char *answer;
	size_t size;

	size = 0;
	answer = NULL;
	while (1) {
		printf("%s (y/n): ", question_str);
		bytes_read = getline(&answer, &size, stdin);
		if (bytes_read < 0)
			return -1;
		if (answer[0] == 'y')
			return 0;
		if (answer[0] == 'n')
			return 1;
	}
	free(answer);
}

static char *fdasd_partition_type(char *dsname)
{
	char *name = NULL;

	if (get_part_name_by_dsname(dsname, &name))
		name = "unknown";

	return name;
}

/*
 * prints the menu
 */
static void fdasd_menu(void)
{
	printf("Command action\n"
	       "   m   print this menu\n"
	       "   p   print the partition table\n"
	       "   n   add a new partition\n"
	       "   d   delete a partition\n"
	       "   l   list known partition types\n"
	       "   v   change volume serial\n"
	       "   t   change partition type\n"
	       "   r   re-create VTOC and delete all partitions\n"
	       "   u   re-create VTOC re-using existing partition sizes\n"
	       "   s   show mapping (partition number - data set name)\n"
	       "   q   quit without saving changes\n"
	       "   w   write table to disk and exit\n");
}

/*
 * initializes the anchor structure and allocates some
 * memory for the labels
 */
static void fdasd_initialize_anchor(fdasd_anchor_t *anc)
{
	partition_info_t *part_info, *prev_part_info = NULL;
	volume_label_t *vlabel;
	int i;

	bzero(anc, sizeof(fdasd_anchor_t));

	for (i = 0; i < USABLE_PARTITIONS; i++)
		setpos(anc, i, -1);

	anc->f4 = malloc(sizeof(format4_label_t));
	if (anc->f4 == NULL)
		fdasd_error(anc, malloc_failed,
			    "FMT4 DSCB memory allocation failed.");

	anc->f5 = malloc(sizeof(format5_label_t));
	if (anc->f5 == NULL)
		fdasd_error(anc, malloc_failed,
			    "FMT5 DSCB memory allocation failed.");

	anc->f7 = malloc(sizeof(format7_label_t));
	if (anc->f7 == NULL)
		fdasd_error(anc, malloc_failed,
			    "FMT7 DSCB memory allocation failed.");

	/* template for all format 9 labels */
	anc->f9 = malloc(sizeof(format9_label_t));
	if (anc->f9 == NULL)
		fdasd_error(anc, malloc_failed,
			    "FMT9 DSCB memory allocation failed.");

	bzero(anc->f4, sizeof(format4_label_t));
	bzero(anc->f5, sizeof(format5_label_t));
	bzero(anc->f7, sizeof(format7_label_t));
	bzero(anc->f9, sizeof(format9_label_t));
	vtoc_init_format9_label(anc->f9);

	vlabel = malloc(sizeof(volume_label_t));
	if (vlabel == NULL)
		fdasd_error(anc, malloc_failed,
			    "Volume label memory allocation failed.");
	bzero(vlabel, sizeof(volume_label_t));
	anc->vlabel = vlabel;

	for (i = 1; i <= USABLE_PARTITIONS; i++) {
		part_info = malloc(sizeof(partition_info_t));
		if (part_info == NULL)
			fdasd_error(anc, malloc_failed,
				    "Partition info memory allocation failed.");
		memset(part_info, 0, sizeof(partition_info_t));

		/* add part_info to double pointered list */
		if (i == 1) {
			anc->first = part_info;
		} else if (i == USABLE_PARTITIONS) {
			anc->last = part_info;
			part_info->next = NULL;
		}

		part_info->f1 = malloc(sizeof(format1_label_t));
		if (part_info->f1 == NULL)
			fdasd_error(anc, malloc_failed,
				    "FMT1 DSCB memory allocation failed.");
		bzero(part_info->f1, sizeof(format1_label_t));

		if (prev_part_info) {
			prev_part_info->next = part_info;
			part_info->prev = prev_part_info;
		} else {
			part_info->prev = NULL;
		}
		prev_part_info = part_info;
	}
	anc->hw_cylinders = 0;
	anc->formatted_cylinders = 0;
}

static void fdasd_parse_force_options(fdasd_anchor_t *anc, char *optarg)
{
	char err_str[ERROR_STRING_SIZE];
	unsigned int devtype, blksize;
	int rc;

	if (optarg) {
		rc = sscanf(optarg, "%x,%d", &devtype, &blksize);
		if (rc != 2) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Force parameter '%s' could not be parsed.\n",
				 optarg);
			fdasd_error(anc, parser_failed, err_str);
		}
		if (devtype == DASD_3390_TYPE || devtype == DASD_3380_TYPE ||
		    devtype == DASD_9345_TYPE) {
			anc->dev_type = devtype;
		} else {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Force parameter '%x' is not a supported"
				 " device type.\n", devtype);
			fdasd_error(anc, parser_failed, err_str);
		}
		if (blksize == 4096 || blksize == 2048 ||
		    blksize == 1024 || blksize ==  512) {
			anc->blksize = blksize;
		} else {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Force parameter '%d' is not a supported"
				 " block size.\n", blksize);
			fdasd_error(anc, parser_failed, err_str);
		}
	} else {
		/* force option was used without the optional argument */
		anc->dev_type = DASD_3390_TYPE;
		anc->blksize = 4096;
	}
}

/*
 * parses the command line options
 */
static void fdasd_parse_options(fdasd_anchor_t *anc,
				struct fdasd_options *options,
				int argc, char *argv[])
{
	int opt;

	util_prg_init(&prg);
	util_opt_init(opt_vec, NULL);

	do {
		opt = util_opt_getopt_long(argc, argv);
		switch (opt) {
		case 'v':
			util_prg_print_version();
			fdasd_exit(anc, 0);
		case 'h':
			util_prg_print_help();
			util_opt_print_help();
			fdasd_exit(anc, 0);
		case 'l':
			if (options->volser)
				fdasd_error(anc, parser_failed,
					    "Option 'label' specified more "
					    "than once.\n");
			options->volser = optarg;
			break;
		case 'a':
			anc->auto_partition++;
			break;
		case 's':
			anc->silent++;
			break;
		case 'r':
			anc->verbose++;
			break;
		case 'p':
			anc->print_table++;
			break;
		case 'i':
			anc->print_volser++;
			anc->silent++;
			break;
		case 'c':
			if (options->conffile)
				fdasd_error(anc, parser_failed,
					    "Option 'config' specified more"
					    " than once.\n");
			options->conffile = optarg;
			break;
		case 'k':
			anc->keep_volser++;
			break;
		case 'f':
			anc->force_virtual++;
			fdasd_parse_force_options(anc, optarg);
			break;
		case 'C':
			anc->force_host++;
			break;
		case -1:
			/* End of options string - start of devices list */
			break;
		default:
			fprintf(stderr, "Try 'fdasd --help' for more"
					" information.\n");
			fdasd_exit(anc, 1);
		}
	} while (opt != -1);

	/* save device */
	if (optind >= argc)
		fdasd_error(anc, parser_failed,
			    "No device specified.\n");
	if (optind + 1 < argc)
		fdasd_error(anc, parser_failed,
			    "More than one device specified.\n");
	options->device = argv[optind];
}

static int gettoken(char *str, char *ch, char *token[], int max)
{
	int i;

	token[0] = strtok(str, ch);
	if (!token[0])
		return 0;

	for (i = 1; i < max; i++) {
		token[i] = strtok(NULL, ch);
		if (!token[i])
			break;
	}
	return i;
}

/*
 * parses config file
 */
static int fdasd_parse_conffile(fdasd_anchor_t *anc,
				struct fdasd_options *options)
{
	char err_str[ERROR_STRING_SIZE], *c1, *c2, *token[CONFIG_MAX];
	char buffer[CONFIG_FILE_SIZE + 1];
	int fd, rc;
	int i;

	/* if name of config file was not specified, select the default */
	if (options->conffile == NULL)
		options->conffile = DEFAULT_FDASD_CONF;

	if (!anc->silent)
		printf("parsing config file '%s'...\n", options->conffile);
	fd = open(options->conffile, O_RDONLY);
	if (fd < 0) {
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Could not open config file '%s' "
			 "in read-only mode!\n", options->conffile);
		fdasd_error(anc, unable_to_open_disk, err_str);
	}

	memset(buffer, 0, sizeof(buffer));
	rc = read(fd, buffer, sizeof(buffer) - 1);
	close(fd);
	if (rc < 0)
		return -1;

	for (i = 0; i < rc; i++)
		buffer[i] = toupper(buffer[i]);

	c1 = buffer;

	for (i = 0; i < USABLE_PARTITIONS; i++) {
		c1 = strchr(c1, '[');
		if (c1 == NULL) {
			if (!anc->silent)
				printf("no config file entry for "
				       "partition %d found...\n", i + 1);
			break;
		}
		c1 += 1;

		c2 = strchr(c1, ']');
		if (c2 == NULL) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "']' missing in config file "
				 "%s\n", options->conffile);
			fdasd_error(anc, config_syntax_error, err_str);
		}
		strcpy(c2, "");

		memset(token, 0, sizeof(token));
		if (gettoken(c1, ",", token, CONFIG_MAX) < 2) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Missing parameter in config file "
				 "%s\n", options->conffile);
			fdasd_error(anc, config_syntax_error, err_str);
		}

		if (strstr(token[0], "FIRST") != NULL) {
			anc->confdata[i].start = FIRST_USABLE_TRK;
		} else {
			errno = 0;
			anc->confdata[i].start = strtol(token[0],
							(char **)NULL, 10);
			if (errno != 0 || anc->confdata[i].start == 0) {
				snprintf(err_str, ERROR_STRING_SIZE,
					 "invalid partition start in config "
					 "file %s\n", options->conffile);
				fdasd_error(anc, config_syntax_error, err_str);
			}
		}

		if (strstr(token[1], "LAST") != NULL) {
			anc->confdata[i].stop =
				anc->formatted_cylinders * geo.heads - 1;
		} else {
			errno = 0;
			anc->confdata[i].stop = strtol(token[1],
						       (char **)NULL, 10);
			if (errno != 0 || anc->confdata[i].stop == 0) {
				snprintf(err_str, ERROR_STRING_SIZE,
					 "invalid partition end in config "
					 "file %s\n", options->conffile);
				fdasd_error(anc, config_syntax_error, err_str);
			}
		}

		if (get_part_type_by_dsname(token[2], &anc->confdata[i].type)) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "invalid partition type in config file %s\n",
				 options->conffile);
			fdasd_error(anc, config_syntax_error, err_str);
		}

		c1 = c2 + 1;
	}

	return 0;
}

/*
 * checks input from config file
 */
static void fdasd_check_conffile_input(fdasd_anchor_t *anc,
				       struct fdasd_options *options)
{
	unsigned long start, stop, first_trk, last_trk;
	partition_info_t *part_info = anc->first;
	char err_str[ERROR_STRING_SIZE];
	int i;

	if (anc->verbose)
		printf("checking config file data...\n");

	for (i = 0; i < USABLE_PARTITIONS; i++) {
		start = anc->confdata[i].start;
		stop = anc->confdata[i].stop;

		if (start == 0 || stop == 0)
			break;

		first_trk = FIRST_USABLE_TRK;
		last_trk = anc->formatted_cylinders * geo.heads - 1;

		if (start < first_trk || start > last_trk) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "One of the lower partition limits "
				 "(%ld) is not within the range of\n"
				 "available tracks on disk (%ld-%ld)!\n",
				 start, first_trk, last_trk);
			fdasd_error(anc, config_syntax_error, err_str);
		}

		if (stop < first_trk || stop > last_trk) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "One of the upper partition limits "
				 "(%ld) is not within the range of\n "
				 "available tracks on disk (%ld-%ld)!\n",
				 stop, first_trk, last_trk);
			fdasd_error(anc, config_syntax_error, err_str);
		}

		if (start >= stop) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Lower partition limit (%ld) is not "
				 "less than upper partition\nlimit (%ld) "
				 "in config file %s!\n",
				 start, stop, options->conffile);
			fdasd_error(anc, config_syntax_error, err_str);
		}

		if (i > 0 && start <= anc->confdata[i - 1].stop) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Partitions overlap or are not in "
				 "ascending order!\n");
			fdasd_error(anc, config_syntax_error, err_str);
		}

		if (i < (USABLE_PARTITIONS - 1) &&
		    anc->confdata[i + 1].start > 0 &&
		    stop >= anc->confdata[i + 1].start) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Partitions overlap or are not in "
				 "ascending order!\n");
			fdasd_error(anc, config_syntax_error, err_str);
		}

		part_info->used      = 0x01;
		part_info->start_trk = start;
		part_info->end_trk   = stop;
		part_info->len_trk   = stop - start + 1;
		part_info->type      = anc->confdata[i].type;

		/* update the current free space counter */
		if (i == 0)
			anc->fspace_trk = start - FIRST_USABLE_TRK;

		if (i < USABLE_PARTITIONS - 1) {
			if (anc->confdata[i + 1].start != 0)
				part_info->fspace_trk =
					anc->confdata[i + 1].start - stop - 1;
			else
				part_info->fspace_trk = last_trk - stop;
		} else if (i == USABLE_PARTITIONS - 1) {
			part_info->fspace_trk = last_trk - stop;
		}

		part_info = part_info->next;
	}
}

/*
 * Verifies the specified block device.
 */
static void fdasd_verify_device(fdasd_anchor_t *anc, char *name)
{
	char err_str[ERROR_STRING_SIZE];
	struct stat dst;
	int count;

	if (stat(name, &dst) < 0) {
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Unable to get device status for device '%s'\n",
			 name);
		fdasd_error(anc, device_verification_failed, err_str);
	}

	if (!S_ISBLK(dst.st_mode)) {
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Device '%s' (%d/%d) is not a block device\n", name,
			 (unsigned short)major(dst.st_rdev),
			 (unsigned short)minor(dst.st_rdev));
		fdasd_error(anc, device_verification_failed, err_str);
	}

	if (!anc->force_virtual && minor(dst.st_rdev) & PARTN_MASK) {
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Partition '%s' (%d/%d) detected where device is "
			 "required\n", name,
			 (unsigned short)major(dst.st_rdev),
			 (unsigned short)minor(dst.st_rdev));
		fdasd_error(anc, device_verification_failed, err_str);
	}

	if (dasd_sys_raw_track_access(name)) {
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Device '%s' is in raw-track access mode\n", name);
		fdasd_error(anc, device_verification_failed, err_str);
	}

	count = dasd_get_host_access_count(name);
	if (anc->force_host) {
		if (count > 1) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Disk %s is online on operating system 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",
				 name, count);
			fdasd_error(anc, device_verification_failed, err_str);
		} else if (count < 0) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "Hosts access information not available for disk %s.\n",
				 name);
			fdasd_error(anc, device_verification_failed, err_str);
		}
	} else if (count > 1) {
		printf("\nWARNING:\n"
		       "Disk %s is online on operating system instances in %d different LPARs.\n"
		       "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\n",
		       name, count);
	}

	if (anc->verbose) {
		printf("Verification successful for '%s' (%d/%d)\n", name,
		       (unsigned short)major(dst.st_rdev),
		       (unsigned short)minor(dst.st_rdev));
	}
}

/*
 * Verifies the specified fdasd command line option
 * combinations.
 *
 * Note:
 *  - 'version' and 'help' are priority options.
 *	 All other parameters are ignored in that case.
 *  - 'silent' and 'verbose' are allways allowed in any
 *	 combination.
 *
 */
static void fdasd_verify_options(fdasd_anchor_t *anc)
{
	/* Checked option combinations			     */
	/* (inv = invalid / req = required / opt = optional) */
	/*						     */
	/*		vols labe keep auto conf tabl	     */
	/*		er   l	  _vol	    if	 e	     */
	/*			  ser			     */
	/*						     */
	/* volser	-    inv  INV  inv  inv  inv	     */
	/* label	     -	  inv  REQ  REQ  inv	     */
	/* keep_volser		  -    REQ  REQ  inv	     */
	/* auto		     opt  opt  -    inv  inv	     */
	/* config	     opt  opt	    -	 inv	     */
	/* table				 -	     */

	if (anc->print_volser &&
	    (options.volser || anc->keep_volser || anc->auto_partition ||
	     options.conffile || anc->print_table)) {
		fdasd_error(anc, parser_failed,
			    "Option 'volser' cannot be used with other"
			    " options.\n");
	}

	if (options.volser) {
		if (!anc->auto_partition && !options.conffile) {
			fdasd_error(anc, parser_failed,
				    "Option 'auto' or 'config' required when"
				    " specifying 'label'\n");
		}
		if (anc->keep_volser || anc->print_table) {
			fdasd_error(anc, parser_failed,
				    "Option 'label' cannot be used with "
				    "'keep_volser' and 'table'.\n");
		}
	}

	if (anc->keep_volser) {
		if (!anc->auto_partition && !options.conffile) {
			fdasd_error(anc, parser_failed,
				    "Option 'auto' or 'config' required when"
				    " specifying 'keep_volser'\n");
		}
		if (anc->print_table) {
			fdasd_error(anc, parser_failed,
				    "Option 'keep_volser' cannot be used"
				    " with 'table'.\n");
		}
	}

	if (anc->auto_partition && (options.conffile || anc->print_table)) {
		fdasd_error(anc, parser_failed,
			    "Option 'auto' cannot be used with "
			    "'config' and 'table'.\n");
	}

	if (options.conffile && anc->print_table) {
		fdasd_error(anc, parser_failed,
			    "Option 'config' cannot be used with"
			    " 'table'.\n");
	}
}

/*
 * print mapping: partition number - data set name
 */
static void fdasd_show_mapping(fdasd_anchor_t *anc)
{
	char str[20], *dev, dsname[45], *strp;
	partition_info_t *part_info;
	int i = 0, j = 0, dev_len;

	printf("\ndevice .........: %s\n", options.device);
	bzero(str, sizeof(str));
	vtoc_volume_label_get_label(anc->vlabel, str);
	printf("volume label ...: %.4s\n", str);
	bzero(str, sizeof(str));
	vtoc_volume_label_get_volser(anc->vlabel, str);
	printf("volume serial ..: %s\n\n", str);

	dev_len = strlen(options.device);
	dev = malloc(dev_len + 10);
	if (!dev)
		fdasd_error(anc, malloc_failed,
			    "Show mapping: memory allocation failed.");
	strcpy(dev, options.device);
	strp = strstr(dev, DISC);
	if (strp == NULL)
		strp = strstr(dev, DEVICE);
	if (strp != NULL)
		strcpy(strp, PART);

	printf("WARNING: This mapping may be NOT up-to-date,\n"
	       "         if you have NOT saved your last changes!\n\n");

	for (part_info = anc->first; part_info != NULL;
	     part_info = part_info->next) {
		i++;
		if (part_info->used != 0x01)
			continue;

		bzero(dsname, sizeof(dsname));
		strncpy(dsname, part_info->f1->DS1DSNAM, 44);
		vtoc_ebcdic_dec(dsname, dsname, 44);

		if (getdsn(anc, i - 1) < 0)
			sprintf(dsname, "new data set");

		printf("%s%-2d -  %-44s\n", dev, i, dsname);
		j++;
	}

	if (j == 0)
		printf("No partitions defined.\n");
	free(dev);
}

/*
 * prints only the volume serial
 */
static void fdasd_print_volser(fdasd_anchor_t *anc)
{
	char volser[VOLSER_LENGTH + 1];

	bzero(volser, VOLSER_LENGTH);
	vtoc_ebcdic_dec(anc->vlabel->volid, volser, VOLSER_LENGTH);
	printf("%6.6s\n", volser);
}

/*
 * print partition table
 */
static void fdasd_list_partition_table(fdasd_anchor_t *anc)
{
	int i = 0, dev_len = strlen(options.device);
	char str[20], *dev, *strp, *ch;
	partition_info_t *part_info;

	if (!anc->silent) {
		printf("\nDisk %s:\n"
		       "  cylinders ............: %d\n"
		       "  tracks per cylinder ..: %d\n"
		       "  blocks per track .....: %d\n"
		       "  bytes per block ......: %d\n",
		       options.device, anc->formatted_cylinders, geo.heads,
		       geo.sectors, anc->blksize);

		vtoc_volume_label_get_label(anc->vlabel, str);
		printf("  volume label .........: %s\n", str);

		vtoc_volume_label_get_volser(anc->vlabel, str);

		printf("  volume serial ........: %s\n", str);
		printf("  max partitions .......: %d\n\n", USABLE_PARTITIONS);
	}

	if (dev_len < 20)
		dev_len = 20;

	if (!anc->silent) {
		printf(" ------------------------------- tracks"
		       " -------------------------------\n");
		printf("%*s      start      end   length   Id  System\n",
		       dev_len + 1, "Device");
	}

	dev = malloc(dev_len + 10);
	if (!dev)
		fdasd_error(anc, malloc_failed,
			    "Print partition table: memory allocation failed.");
	strcpy(dev, options.device);

	strp = strstr(dev, DISC);
	if (strp == NULL)
		strp = strstr(dev, DEVICE);
	if (strp != NULL)
		strcpy(strp, PART);

	for (part_info = anc->first; part_info != NULL;
	     part_info = part_info->next) {
		i++;

		if (part_info == anc->first && anc->fspace_trk > 0) {
			printf("%*s   %9ld%9ld%9ld       unused\n", dev_len, "",
			       (unsigned long)FIRST_USABLE_TRK,
			       (unsigned long)FIRST_USABLE_TRK +
			       anc->fspace_trk - 1,
			       anc->fspace_trk);
		}

		if (part_info->used != 0x01)
			continue;

		vtoc_ebcdic_dec(part_info->f1->DS1DSNAM,
				part_info->f1->DS1DSNAM, 44);
		ch = strstr(part_info->f1->DS1DSNAM, "PART");
		if (ch != NULL) {
			strncpy(str, ch + 9, 6);
			str[6] = '\0';
		} else {
			strcpy(str, "error");
		}

		vtoc_ebcdic_enc(part_info->f1->DS1DSNAM,
				part_info->f1->DS1DSNAM, 44);

		printf("%*s%-2d %9ld%9ld%9ld   %2x  %6s\n",
		       dev_len, dev, i, part_info->start_trk,
		       part_info->end_trk, part_info->len_trk, i,
		       fdasd_partition_type(str));

		if (part_info->fspace_trk > 0)
			printf("%*s   %9ld%9ld%9ld       unused\n",
			       dev_len, "", part_info->end_trk + 1,
			       part_info->end_trk + part_info->fspace_trk,
			       part_info->fspace_trk);
	}
	free(dev);
}

/*
 * List all supported partition types.
 */
static void fdasd_list_known_partitions(void)
{
	unsigned int i;

	for (i = VALID_PARTITION_OFFSET;
	     i < ARRAY_SIZE(partition_types); i++) {
		printf("%3d %s\n",
		       partition_types[i].type, partition_types[i].name);
	}
}

/*
 * get volser from vtoc
 */
static int fdasd_get_volser(fdasd_anchor_t *anc, char *volser)
{
	volume_label_t vlabel;

	vtoc_read_volume_label(options.device, anc->label_pos, &vlabel);
	vtoc_volume_label_get_volser(&vlabel, volser);

	return 0;
}

/*
 * call IOCTL to re-read the partition table
 */
static void fdasd_reread_partition_table(fdasd_anchor_t *anc)
{
	if (!anc->silent)
		printf("rereading partition table...\n");

	if (dasd_reread_partition_table(options.device, 5) != 0) {
		fdasd_error(anc, unable_to_ioctl, "Error while rereading "
			    "partition table.\nPlease reboot!");
	}
}

/*
 * writes all changes to dasd
 */
static void fdasd_write_vtoc_labels(fdasd_anchor_t *anc)
{
	char dsno[6], volser[VOLSER_LENGTH + 1], s2[45], *c1, *c2, *ch;
	partition_info_t *part_info;
	unsigned long blk, maxblk;
	format1_label_t emptyf1;
	char *dsname = NULL;
	cchhb_t f9addr;
	int i = 0, k = 0;

	if (!anc->silent)
		printf("writing VTOC...\n");
	if (anc->verbose)
		printf("DSCBs: ");

	blk = (cchhb2blk(&anc->vlabel->vtoc, &geo) - 1) * anc->blksize;
	if (cchhb2blk(&anc->vlabel->vtoc, &geo) == 0 || blk == 0)
		fdasd_error(anc, vlabel_corrupted, "");
	maxblk = blk + anc->blksize * 9; /* f4+f5+f7+3*f8+3*f9 */

	/* write FMT4 DSCB */
	vtoc_write_label(options.device, blk, NULL, anc->f4, NULL, NULL, NULL);
	if (anc->verbose)
		printf("f4 ");
	blk += anc->blksize;

	/* write FMT5 DSCB */
	vtoc_write_label(options.device, blk, NULL, NULL, anc->f5, NULL, NULL);
	if (anc->verbose)
		printf("f5 ");
	blk += anc->blksize;

	/* write FMT7 DSCB */
	if (anc->big_disk) {
		vtoc_write_label(options.device, blk,  NULL, NULL,
				 NULL, anc->f7, NULL);
		if (anc->verbose)
			printf("f7 ");
		blk += anc->blksize;
	}

	/* loop over all partitions (format 1 or format 8 DCB) */
	for (part_info = anc->first; part_info != NULL;
	     part_info = part_info->next) {

		if (part_info->used != 0x01)
			continue;

		i++;
		strncpy((char *)part_info->f1->DS1DSSN, anc->vlabel->volid,
			VOLSER_LENGTH);

		ch = part_info->f1->DS1DSNAM;
		vtoc_ebcdic_dec(ch, ch, 44);
		c1 = ch + 7;

		if (getdsn(anc, i - 1) > -1) {
			/* re-use the existing data set name */
			c2 = strchr(c1, '.');
			if (c2 != NULL)
				strncpy(s2, c2, 31);
			else
				fdasd_error(anc, dsname_corrupted, "");

			strncpy(volser, anc->vlabel->volid, VOLSER_LENGTH);
			vtoc_ebcdic_dec(volser, volser, VOLSER_LENGTH);
			volser[VOLSER_LENGTH] = ' ';
			strncpy(c1, volser, VOLSER_LENGTH + 1);
			c1 = strchr(ch, ' ');
			memcpy(c1, s2, 31);
		} else {
			if (get_part_type_by_dsname(ch, &part_info->type))
				part_info->type = PARTITION_NATIVE;

			/* create a new data set name */
			while (getpos(anc, k) > -1)
				k++;

			setpos(anc, k, i - 1);

			memcpy(ch, "LINUX.V               "
				"                      ", 44);

			strncpy(volser, anc->vlabel->volid, VOLSER_LENGTH);
			vtoc_ebcdic_dec(volser, volser, VOLSER_LENGTH);
			memcpy(c1, volser, VOLSER_LENGTH);

			c1 = strchr(ch, ' ');
			memcpy(c1, ".PART", 5);
			c1 += 5;

			sprintf(dsno, "%04d.", k + 1);
			memcpy(c1, dsno, 5);
			c1 += 5;

			get_part_dsname_by_type(part_info->type, &dsname);
			memcpy(c1, dsname, strlen(dsname)); /* We don't want \0 */
		}
		vtoc_ebcdic_enc(ch, ch, 44);
		if (anc->verbose)
			printf("%2x ", part_info->f1->DS1FMTID);

		if (part_info->f1->DS1FMTID == 0xf8) {
			/* Now as we know where which label will be written, we
			 * can add the address of the format 9 label to the
			 * format 8 label. The f9 record will be written to the
			 * block after the current blk. Remember: records are of
			 * by one, so we have to add 2 and not just one.
			 */
			vtoc_set_cchhb(&f9addr, VTOC_START_CC, VTOC_START_HH,
				       ((blk / anc->blksize) % geo.sectors)
				       + 2);
			vtoc_update_format8_label(&f9addr, part_info->f1);
			vtoc_write_label(options.device, blk, part_info->f1,
					 NULL, NULL, NULL, NULL);
			blk += anc->blksize;
			vtoc_write_label(options.device, blk,  NULL, NULL,
					 NULL, NULL, anc->f9);
			if (anc->verbose)
				printf("f9 ");
			blk += anc->blksize;
		} else {
			vtoc_write_label(options.device, blk, part_info->f1,
					 NULL, NULL, NULL, NULL);
			blk += anc->blksize;
		}
	}

	/* write empty labels to the rest of the blocks */
	bzero(&emptyf1, sizeof(emptyf1));
	while (blk < maxblk) {
		vtoc_write_label(options.device, blk, &emptyf1, NULL,
				 NULL, NULL, NULL);
		if (anc->verbose)
			printf("empty ");
		blk += anc->blksize;
	}

	if (anc->verbose)
		printf("\n");
}

/*
 * writes all changes to dasd
 */
static void fdasd_write_labels(fdasd_anchor_t *anc)
{
	if (anc->vlabel_changed) {
		if (!anc->silent)
			printf("writing volume label...\n");
		vtoc_write_volume_label(options.device, anc->label_pos,
					anc->vlabel);
	}

	if (anc->vtoc_changed)
		fdasd_write_vtoc_labels(anc);

	if (anc->vtoc_changed || anc->vlabel_changed)
		fdasd_reread_partition_table(anc);
}

/*
 * re-creates the VTOC and deletes all partitions
 */
static void fdasd_recreate_vtoc_unconditional(fdasd_anchor_t *anc)
{
	partition_info_t *part_info = anc->first;
	int i;

	vtoc_init_format4_label(anc->f4, geo.cylinders,
				anc->formatted_cylinders,
				geo.heads, geo.sectors,
				anc->blksize, anc->dev_type);

	vtoc_init_format5_label(anc->f5);
	vtoc_init_format7_label(anc->f7);
	vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose,
			   FIRST_USABLE_TRK,
			   anc->formatted_cylinders * geo.heads - 1,
			   anc->formatted_cylinders, geo.heads);

	while (part_info != NULL) {
		bzero(part_info->f1, sizeof(format1_label_t));

		if (part_info->used == 0x01) {
			part_info->used       = 0x00;
			part_info->start_trk  = 0;
			part_info->end_trk    = 0;
			part_info->len_trk    = 0;
			part_info->fspace_trk = 0;
		}

		part_info = part_info->next;
	}

	anc->used_partitions = 0;
	anc->fspace_trk =
		anc->formatted_cylinders * geo.heads - FIRST_USABLE_TRK;

	for (i = 0; i < USABLE_PARTITIONS; i++)
		setpos(anc, i, -1);

	anc->vtoc_changed++;
}

/*
 * asks user for confirmation before recreating the vtoc
 */
static void fdasd_recreate_vtoc(fdasd_anchor_t *anc)
{
	char str[INPUT_BUF_SIZE];

	if (!anc->silent) {
		snprintf(str, INPUT_BUF_SIZE,
			 "WARNING: All partitions on device '%s' will be "
			 "deleted!\nDo you want to continue?",
			 options.device);

		if (yes_no(str) != 0)
			return;

		printf("creating new VTOC... ");
	}
	fdasd_recreate_vtoc_unconditional(anc);
	if (!anc->silent)
		printf("ok\n");
}

/*
 * re-create all VTOC labels, but use the partition information
 * from existing VTOC
 */
static void fdasd_reuse_vtoc(fdasd_anchor_t *anc)
{
	partition_info_t *part_info = anc->first;
	char str[INPUT_BUF_SIZE];
	format1_label_t f1;
	format4_label_t f4;
	format5_label_t f5;
	format7_label_t f7;

	if (!anc->silent) {
		snprintf(str, INPUT_BUF_SIZE,
			 "WARNING: this will re-create your VTOC "
			 "entries using the partition\n           "
			 "information of your existing VTOC. Continue?");

		if (yes_no(str) != 0)
			return;
	}

	if (!anc->silent)
		printf("re-creating VTOC... ");

	vtoc_init_format4_label(&f4, geo.cylinders, anc->formatted_cylinders,
				geo.heads, geo.sectors,
				anc->blksize, anc->dev_type);

	/* reuse some FMT4 values */
	f4.DS4HPCHR = anc->f4->DS4HPCHR;
	f4.DS4DSREC = anc->f4->DS4DSREC;

	/* re-initialize both free-space labels */
	vtoc_init_format5_label(&f5);
	vtoc_init_format7_label(&f7);

	if (anc->fspace_trk > 0) {
		vtoc_set_freespace(&f4, &f5, &f7, '+', anc->verbose,
				   FIRST_USABLE_TRK,
				   FIRST_USABLE_TRK + anc->fspace_trk - 1,
				   anc->formatted_cylinders, geo.heads);
	}

	while (part_info != NULL) {
		if (part_info->used != 0x01) {
			part_info = part_info->next;
			continue;
		}

		if (anc->formatted_cylinders > LV_COMPAT_CYL)
			vtoc_init_format8_label(anc->blksize,
						&part_info->f1->DS1EXT1, &f1);
		else
			vtoc_init_format1_label(anc->blksize,
						&part_info->f1->DS1EXT1, &f1);

		strncpy(f1.DS1DSNAM, part_info->f1->DS1DSNAM, 44);
		strncpy((char *)f1.DS1DSSN, (char *)part_info->f1->DS1DSSN, 6);
		f1.DS1CREDT = part_info->f1->DS1CREDT;

		memcpy(part_info->f1, &f1, sizeof(format1_label_t));

		if (part_info->fspace_trk > 0) {
			vtoc_set_freespace(&f4, &f5, &f7, '+', anc->verbose,
					   part_info->end_trk + 1,
					   part_info->end_trk +
					   part_info->fspace_trk,
					   anc->formatted_cylinders, geo.heads);
		}

		part_info = part_info->next;
	}

	/* over-write old labels with new ones */
	memcpy(anc->f4, &f4, sizeof(format4_label_t));
	memcpy(anc->f5, &f5, sizeof(format5_label_t));
	memcpy(anc->f7, &f7, sizeof(format7_label_t));

	if (!anc->silent)
		printf("ok\n");
	anc->vtoc_changed++;
}

/*
 * Changes the volume serial (menu option)
 */
static void fdasd_change_volser(fdasd_anchor_t *anc)
{
	char volser[VOLSER_LENGTH + 1];

	vtoc_volume_label_get_volser(anc->vlabel, volser);
	printf("Please specify new volume serial (6 characters).\n");
	printf("current     : %-6.6s\nnew [0X%04x]: ", volser, anc->devno);

	read_line();
	fdasd_check_volser(line_ptr, anc->devno);

	printf("\nvolume identifier changed to '%-6s'\n", line_ptr);
	vtoc_volume_label_set_volser(anc->vlabel, line_ptr);

	vtoc_set_cchhb(&anc->vlabel->vtoc, VTOC_START_CC, VTOC_START_HH, 0x01);
	anc->vlabel_changed++;
	anc->vtoc_changed++;
}

/*
 * changes the partition type
 */
static void fdasd_change_part_type(fdasd_anchor_t *anc)
{
	unsigned int part_id, part_type, i;
	partition_info_t *part_info;
	char *dsname = NULL;
	char str[20], *ch;

	fdasd_list_partition_table(anc);

	/* ask for partition number */
	printf("\nchange partition type\n");
	while (!isdigit(part_id = read_char("partition id (use 0 to exit): ")))
		printf("Invalid partition id '%c' detected.\n", part_id);

	part_id -= 48;
	printf("\n");
	if (part_id == 0)
		return;
	if (part_id > anc->used_partitions) {
		printf("'%d' is not a valid partition id!\n", part_id);
		return;
	}

	part_info = anc->first;
	for (i = 1; i < part_id; i++)
		part_info = part_info->next;

	/* ask for partition type */
	vtoc_ebcdic_dec(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44);
	ch = strstr(part_info->f1->DS1DSNAM, "PART") + 9;
	if (ch != NULL) {
		strncpy(str, ch, 6);
		str[6] = '\0';
	} else {
		strcpy(str, "error");
	}

	printf("current partition type is: %s\n\n", fdasd_partition_type(str));
	fdasd_list_known_partitions();
	printf("\n");

	part_type = 0;
	while (part_type < 1 ||
	       part_type > ARRAY_SIZE(partition_types) - 1) {
		do {
			part_type = read_char("new partition type: ");
		} while (!isdigit(part_type));
		part_type -= 48;
	}

	if (get_part_dsname_by_type(part_type, &dsname))
		printf("'%d' is not supported!\n", part_type);
	else
		snprintf(str, 7, "%-6s", dsname);

	ch = strstr(part_info->f1->DS1DSNAM, "PART") + 9;
	if (ch != NULL)
		memcpy(ch, str, 6);
	vtoc_ebcdic_enc(part_info->f1->DS1DSNAM, part_info->f1->DS1DSNAM, 44);
	anc->vtoc_changed++;
}

/*
 * initialize the VOL1 volume label
 */
static void fdasd_init_volume_label(fdasd_anchor_t *anc)
{
	volume_label_t *vlabel = anc->vlabel;
	char volser[VOLSER_LENGTH + 1];

	vtoc_volume_label_init(vlabel);
	vtoc_volume_label_set_key(vlabel, "VOL1");
	vtoc_volume_label_set_label(vlabel, "VOL1");

	if (anc->keep_volser) {
		if (fdasd_get_volser(anc, volser) == 0)
			vtoc_volume_label_set_volser(vlabel, volser);
		else
			fdasd_error(anc, volser_not_found, options.device);
	} else if (options.volser) {
		fdasd_check_volser(options.volser, anc->devno);
		vtoc_volume_label_set_volser(vlabel, options.volser);
	} else if (anc->auto_partition || options.conffile) {
		sprintf(volser, "0X%04x", anc->devno);
		vtoc_volume_label_set_volser(vlabel, volser);
	} else {
		printf("\nPlease specify volume serial (6 characters)"
		       "[0X%04x]: ", anc->devno);
		read_line();
		fdasd_check_volser(line_ptr, anc->devno);
		vtoc_volume_label_set_volser(vlabel, line_ptr);
	}

	vtoc_set_cchhb(&vlabel->vtoc, VTOC_START_CC, VTOC_START_HH, 0x01);
	anc->vlabel_changed++;
}

/*
 * sets some important partition data
 * (like used, start_trk, end_trk, len_trk)
 * by calculating these values with the
 * information provided in the labels
 */
static void fdasd_update_partition_info(fdasd_anchor_t *anc)
{
	partition_info_t *prev_part_info = NULL, *part_info = anc->first;
	unsigned long max = anc->formatted_cylinders * geo.heads - 1;
	int i;

	anc->used_partitions = geo.sectors - 2 - anc->f4->DS4DSREC;

	for (i = 1; i <= USABLE_PARTITIONS; i++) {
		if (part_info->f1->DS1FMTID != 0xf1 &&
		    part_info->f1->DS1FMTID != 0xf8) {
			if (i == 1)
				/* there is no partition at all */
				anc->fspace_trk = max - FIRST_USABLE_TRK + 1;
			else
				/* previous partition was the last one */
				prev_part_info->fspace_trk =
					max - prev_part_info->end_trk;
			break;
		}

		/* this is a valid format 1 label */
		part_info->used = 0x01;
		part_info->start_trk = cchh2trk(&part_info->f1->DS1EXT1.llimit,
						&geo);
		part_info->end_trk = cchh2trk(&part_info->f1->DS1EXT1.ulimit,
					      &geo);

		part_info->len_trk = part_info->end_trk -
			part_info->start_trk + 1;

		if (i == 1) {
			/* first partition, there is at least one */
			anc->fspace_trk =
				part_info->start_trk - FIRST_USABLE_TRK;
		} else {
			if (i == USABLE_PARTITIONS)
				/* last possible partition */
				part_info->fspace_trk =
					max - part_info->end_trk;

			/* set free space values of previous partition */
			prev_part_info->fspace_trk = part_info->start_trk -
				prev_part_info->end_trk - 1;
		}

		prev_part_info = part_info;
		part_info = part_info->next;
	}
}

/*
 * reorganizes all FMT1s, move all empty labels to the end
 */
static void fdasd_reorganize_FMT1s(fdasd_anchor_t *anc)
{
	partition_info_t *part_info;
	format1_label_t *f1_label;
	int i, j;

	for (i = 1; i <= USABLE_PARTITIONS - 1; i++) {
		part_info = anc->first;
		for (j = 1; j <= USABLE_PARTITIONS - i; j++) {
			if (part_info->f1->DS1FMTID <
			    part_info->next->f1->DS1FMTID) {
				f1_label = part_info->f1;
				part_info->f1 = part_info->next->f1;
				part_info->next->f1 = f1_label;
			}
			part_info = part_info->next;
		}
	}
}

/*
 * we have a invalid FMT4 DSCB and therefore we will re-create the VTOC
 */
static void fdasd_process_invalid_vtoc(fdasd_anchor_t *anc)
{
	printf(" invalid\ncreating new VTOC...\n");
	if (anc->hw_cylinders > LV_COMPAT_CYL) {
		printf("Warning: Device has more then %u cylinders!\n",
		       LV_COMPAT_CYL);
		if (yes_no("Are you sure it was completely"
			   " formatted with dasdfmt?") == 1) {
			if (!anc->silent)
				printf("exiting...\n");
			fdasd_exit(anc, 0);
		}
	}
	anc->formatted_cylinders = anc->hw_cylinders;
	anc->fspace_trk = anc->formatted_cylinders * geo.heads
		- FIRST_USABLE_TRK;
	vtoc_init_format4_label(anc->f4, geo.cylinders,
				anc->formatted_cylinders,
				geo.heads, geo.sectors,
				anc->blksize, anc->dev_type);

	vtoc_init_format5_label(anc->f5);
	vtoc_init_format7_label(anc->f7);
	vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose,
			   FIRST_USABLE_TRK,
			   anc->formatted_cylinders * geo.heads - 1,
			   anc->formatted_cylinders, geo.heads);

	anc->vtoc_changed++;
}

/*
 *
 */
static void fdasd_process_valid_vtoc(fdasd_anchor_t *anc, unsigned long blk)
{
	int f1_counter = 0, f7_counter = 0, f5_counter = 0;
	int i, part_no, f1_size = sizeof(format1_label_t);
	partition_info_t *part_info = anc->first;
	char part_no_str[5], *part_pos;
	format1_label_t f1_label;

	if (!anc->silent)
		printf(" ok\n");

	if (anc->f4->DS4DEVCT.DS4DSCYL == LV_COMPAT_CYL &&
	    anc->f4->DS4DCYL > anc->f4->DS4DEVCT.DS4DSCYL)
		anc->formatted_cylinders = anc->f4->DS4DCYL;
	else
		anc->formatted_cylinders = anc->f4->DS4DEVCT.DS4DSCYL;
	anc->fspace_trk =
		anc->formatted_cylinders * geo.heads - FIRST_USABLE_TRK;
	/* skip f4 label, already read before */
	blk += anc->blksize;

	if (anc->formatted_cylinders < anc->hw_cylinders)
		printf("WARNING: This device is not fully formatted! "
		       "Only %u of %u cylinders are available.\n",
		       anc->formatted_cylinders, anc->hw_cylinders);

	if (anc->verbose)
		printf("VTOC DSCBs          : ");

	/* go through remaining labels, f4 label already done */
	for (i = 1; i < geo.sectors; i++) {
		bzero(&f1_label, f1_size);
		vtoc_read_label(options.device, blk, &f1_label, NULL, NULL,
				NULL);

		switch (f1_label.DS1FMTID) {
		case 0xf1:
		case 0xf8:
			if (anc->verbose)
				printf("%s ",
				       f1_label.DS1FMTID == 0xf1 ? "f1" : "f8");
			if (part_info == NULL)
				break;
			memcpy(part_info->f1, &f1_label, f1_size);

			part_no = -1;
			vtoc_ebcdic_dec(part_info->f1->DS1DSNAM,
					part_info->f1->DS1DSNAM, 44);
			part_pos = strstr(part_info->f1->DS1DSNAM, "PART");
			if (part_pos != NULL) {
				strncpy(part_no_str, part_pos + 4, 4);
				part_no_str[4] = '\0';
				part_no = atoi(part_no_str) - 1;
			}

			vtoc_ebcdic_enc(part_info->f1->DS1DSNAM,
					part_info->f1->DS1DSNAM, 44);

			if (part_no < 0 || part_no >= USABLE_PARTITIONS)
				printf("WARNING: partition number (%i) found "
				       "in data set name of an existing "
				       "partition\ndoes not match range of "
				       "possible partition numbers (1-%d)\n\n",
				       part_no + 1, USABLE_PARTITIONS);
			else
				setpos(anc, part_no, f1_counter);

			part_info = part_info->next;
			f1_counter++;
			break;
		case 0xf5:
			if (anc->verbose)
				printf("f5 ");
			memcpy(anc->f5, &f1_label, f1_size);
			f5_counter++;
			break;
		case 0xf7:
			if (anc->verbose)
				printf("f7 ");
			if (f7_counter == 0)
				memcpy(anc->f7, &f1_label, f1_size);
			f7_counter++;
			break;
		case 0xf9:
			/* each format 8 lable has an associated format 9 lable,
			 * but they are of no further use to us.
			 */
			if (anc->verbose)
				printf("f9 ");
			break;
		default:
			if (f1_label.DS1FMTID > 0)
				printf("'%d' is not supported!\n",
				       f1_label.DS1FMTID);
		}
		blk += anc->blksize;
	}

	if (anc->verbose)
		printf("\n");

	if (f5_counter == 0 || anc->big_disk)
		vtoc_init_format5_label(anc->f5);

	if (f7_counter == 0)
		vtoc_init_format7_label(anc->f7);

	fdasd_reorganize_FMT1s(anc);
	fdasd_update_partition_info(anc);
}

/*
 * we have a valid VTOC pointer, let's go and read the VTOC labels
 */
static int fdasd_valid_vtoc_pointer(fdasd_anchor_t *anc, unsigned long blk)
{
	/* VOL1 label contains valid VTOC pointer */
	if (!anc->silent)
		printf("reading vtoc ..........:");

	vtoc_read_label(options.device, blk, NULL, anc->f4, NULL, NULL);

	if (anc->f4->DS4IDFMT != 0xf4) {
		if (anc->print_table) {
			printf("Your VTOC is corrupted!\n");
			return -1;
		}
		fdasd_process_invalid_vtoc(anc);
	} else {
		fdasd_process_valid_vtoc(anc, blk);
	}

	return 0;
}

/*
 *
 */
static void fdasd_invalid_vtoc_pointer(fdasd_anchor_t *anc)
{
	/* VOL1 label doesn't contain valid VTOC pointer */
	if (yes_no("There is no VTOC yet, should I create one?") == 1) {
		if (!anc->silent)
			printf("exiting...\n");
		fdasd_exit(anc, 0);
	}

	if (anc->hw_cylinders > LV_COMPAT_CYL) {
		printf("Warning: Device has more then %u cylinders!\n",
		       LV_COMPAT_CYL);
		if (yes_no("Are you sure it was completely"
			   " formatted with dasdfmt?") == 1) {
			if (!anc->silent)
				printf("exiting...\n");
			fdasd_exit(anc, 0);
		}
	}
	anc->formatted_cylinders = anc->hw_cylinders;
	anc->fspace_trk = anc->formatted_cylinders * geo.heads
		- FIRST_USABLE_TRK;
	vtoc_init_format4_label(anc->f4, geo.cylinders,
				anc->formatted_cylinders,
				geo.heads, geo.sectors,
				anc->blksize, anc->dev_type);

	vtoc_init_format5_label(anc->f5);
	vtoc_init_format7_label(anc->f7);

	vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose,
			   FIRST_USABLE_TRK,
			   anc->formatted_cylinders * geo.heads - 1,
			   anc->formatted_cylinders, geo.heads);

	vtoc_set_cchhb(&anc->vlabel->vtoc, VTOC_START_CC, VTOC_START_HH, 0x01);

	anc->vtoc_changed++;
	anc->vlabel_changed++;
}

/*
 * check the dasd for a volume label
 */
static int fdasd_check_volume(fdasd_anchor_t *anc)
{
	volume_label_t *vlabel = anc->vlabel;
	char inp_buf[INPUT_BUF_SIZE];
	char str[LINE_LENGTH];
	long long blk = -1;
	int rc = 1;

	if (!anc->silent)
		printf("reading volume label ..:");

	vtoc_read_volume_label(options.device, anc->label_pos, vlabel);

	if (strncmp(vlabel->vollbl, vtoc_ebcdic_enc("VOL1", str, 4), 4) == 0) {
		/* found VOL1 volume label */
		if (!anc->silent)
			printf(" VOL1\n");

		blk = (cchhb2blk(&vlabel->vtoc, &geo) - 1) * anc->blksize;
		if (cchhb2blk(&vlabel->vtoc, &geo) > 0 && blk > 0) {
			rc = fdasd_valid_vtoc_pointer(anc, blk);

			if (anc->print_table && (rc < 0))
				return -1;
		} else {
			if (anc->print_table) {
				printf("\nFound invalid VTOC pointer.\n");
				return -1;
			}
			fdasd_invalid_vtoc_pointer(anc);
		}
	} else {
		/* didn't find VOL1 volume label */

		if (anc->print_table || anc->print_volser) {
			printf("\nCannot show requested information because "
			       "the disk label block is invalid\n");
			return -1;
		}

		if (strncmp(vlabel->vollbl,
			    vtoc_ebcdic_enc("LNX1", str, 4), 4) == 0) {
			if (!anc->silent)
				printf(" LNX1\n");
			strcpy(inp_buf, "Overwrite inapplicable label?");
		} else {
			if (!anc->silent)
				printf(" no known label\n");
			if (!anc->auto_partition && !options.conffile)
				rc = yes_no("Should I create a new one?");
			else
				rc = 0;
		}
		if (!anc->print_volser && !anc->print_table && rc == 1) {
			printf("Disc does not contain a VOL1 label, cannot "
			       "create partitions.\nexiting...\n");
			fdasd_exit(anc, -1);
		}

		if (anc->hw_cylinders > LV_COMPAT_CYL) {
			printf("Warning: Device has more than %u cylinders!\n",
			       LV_COMPAT_CYL);
			if (!anc->auto_partition && !options.conffile &&
			    yes_no("Are you sure it was completely"
				   " formatted with dasdfmt?") == 1) {
				if (!anc->silent)
					printf("exiting...\n");
				fdasd_exit(anc, 0);
			}
		}
		anc->formatted_cylinders = anc->hw_cylinders;
		anc->fspace_trk =
			anc->formatted_cylinders * geo.heads - FIRST_USABLE_TRK;

		fdasd_init_volume_label(anc);

		vtoc_init_format4_label(anc->f4, geo.cylinders,
					anc->formatted_cylinders,
					geo.heads, geo.sectors,
					anc->blksize, anc->dev_type);

		vtoc_init_format5_label(anc->f5);
		vtoc_init_format7_label(anc->f7);

		vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+',
				   anc->verbose, FIRST_USABLE_TRK,
				   anc->formatted_cylinders * geo.heads - 1,
				   anc->formatted_cylinders, geo.heads);

		anc->vtoc_changed++;
	}

	if (!anc->silent)
		printf("\n");

	return 0;
}

/*
 * check disk access
 */
static void fdasd_check_disk_access(fdasd_anchor_t *anc)
{
	char err_str[ERROR_STRING_SIZE];
	format1_label_t f1;
	int fd, pos;
	bool ro;

	fd = open(options.device, O_RDONLY);
	if (fd == -1) {
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Could not open device '%s' "
			 "in read-only mode!\n", options.device);
		fdasd_error(anc, unable_to_open_disk, err_str);
	}

	pos = anc->blksize * (2 * geo.heads - 1);
	/* last block in the second track */
	if (lseek(fd, pos, SEEK_SET) == -1) {
		close(fd);
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Could not seek device '%s'.", options.device);
		fdasd_error(anc, unable_to_seek_disk, err_str);
	}

	if (read(fd, &f1, sizeof(format1_label_t)) != sizeof(format1_label_t)) {
		close(fd);
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Could not read from device '%s'.", options.device);
		fdasd_error(anc, unable_to_read_disk, err_str);
	}

	if (lseek(fd, pos, SEEK_SET) == -1) {
		close(fd);
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Could not seek device '%s'.", options.device);
		fdasd_error(anc, unable_to_seek_disk, err_str);
	}

	close(fd);

	if (dasd_is_ro(options.device, &ro) != 0) {
		snprintf(err_str, ERROR_STRING_SIZE,
			 "Could not get read-only status for device '%s'.",
			 options.device);
		fdasd_error(anc, unable_to_ioctl, err_str);
	}
	if (ro && !anc->print_volser && !anc->print_table) {
		printf("\nWARNING: Device '%s' is a read-only device!\n"
		       "You will not be able to save any changes.\n\n",
		       options.device);
	}
}

/*
 * The following two functions match those in the DASD ECKD device driver.
 * They are used to compute how many records of a given size can be stored
 * in one track.
 */
static unsigned int ceil_quot(unsigned int d1, unsigned int d2)
{
	return (d1 + (d2 - 1)) / d2;
}

/* kl: key length, dl: data length */
static unsigned int recs_per_track(unsigned short dev_type, unsigned int kl,
				   unsigned int dl)
{
	int dn, kn;

	switch (dev_type) {
	case DASD_3380_TYPE:
		if (kl)
			return 1499 / (15 + 7 + ceil_quot(kl + 12, 32) +
				       ceil_quot(dl + 12, 32));
		else
			return 1499 / (15 + ceil_quot(dl + 12, 32));
	case DASD_3390_TYPE:
		dn = ceil_quot(dl + 6, 232) + 1;
		if (kl) {
			kn = ceil_quot(kl + 6, 232) + 1;
			return 1729 / (10 + 9 + ceil_quot(kl + 6 * kn, 34) +
				       9 + ceil_quot(dl + 6 * dn, 34));
		} else {
			return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34));
		}
	case DASD_9345_TYPE:
		dn = ceil_quot(dl + 6, 232) + 1;
		if (kl) {
			kn = ceil_quot(kl + 6, 232) + 1;
			return 1420 / (18 + 7 + ceil_quot(kl + 6 * kn, 34) +
				       ceil_quot(dl + 6 * dn, 34));
		} else {
			return 1420 / (18 + 7 + ceil_quot(dl + 6 * dn, 34));
		}
	}
	return 0;
}

/*
 * Verify that number of tracks (heads) per cylinder and number of
 * sectors per track match the expected values for a given device type
 * and block size.
 * Returns 1 for a valid match and 0 otherwise.
 */
static int fdasd_verify_geometry(unsigned short dev_type, int blksize,
				 struct hd_geometry *geometry)
{
	unsigned int expected_sectors;

	if (geometry->heads != 15)
		return 0;

	expected_sectors = recs_per_track(dev_type, 0, blksize);
	if (geometry->sectors == expected_sectors)
		return 1;

	return 0;
}

/*
 * reads dasd geometry data
 */
static void fdasd_get_geometry(fdasd_anchor_t *anc)
{
	struct dasd_eckd_characteristics *characteristics;
	unsigned long long size_in_bytes;
	char err_str[ERROR_STRING_SIZE];
	dasd_information2_t dasd_info;
	unsigned int blksize = 0;

	if (dasd_get_blocksize_in_bytes(options.device, &size_in_bytes) != 0)
		fdasd_error(anc, unable_to_ioctl,
			    "Could not retrieve disk size.");
	/*
	 * If anc->force_virtual is set, we do no real geometry detection.
	 * anc->dev_type and anc->blksize have already been set via command
	 * line parameter, and the rest of the geometry is now computed from
	 * these values.
	 */
	if (anc->force_virtual) {
		geo.heads = 15;
		geo.sectors = recs_per_track(anc->dev_type, 0, anc->blksize);
		anc->hw_cylinders = size_in_bytes /
			(anc->blksize * geo.heads * geo.sectors);
		if (anc->hw_cylinders < LV_COMPAT_CYL)
			geo.cylinders = anc->hw_cylinders;
		else
			geo.cylinders = LV_COMPAT_CYL;
		geo.start = 0;
		anc->label_pos = 2 * anc->blksize;
		anc->devno = 0;
		if (anc->verbose) {
			printf("The force option is active. "
			       "The following geometry will be used:\n"
			       "device type %x, block size %d, cylinders %d,"
			       " heads %d, sectors %d\n",
			       anc->dev_type, anc->blksize, anc->hw_cylinders,
			       geo.heads, geo.sectors);
		}
		return;
	}

	if (dasd_get_geo(options.device, &geo) != 0) {
		fdasd_error(anc, unable_to_ioctl,
			    "Could not retrieve disk geometry information.");
	}
	if (dasd_get_blocksize(options.device, &blksize) != 0) {
		fdasd_error(anc, unable_to_ioctl,
			    "Could not retrieve blocksize information.");
	}
	/* get disk type */
	if (dasd_get_info(options.device, &dasd_info) != 0) {
		if (anc->verbose)
			printf("BIODASDINFO ioctl failed,"
			       " use disk geometry only.\n");
		/* verify that the geometry matches a 3390 DASD */
		if (!fdasd_verify_geometry(DASD_3390_TYPE, blksize, &geo)) {
			fdasd_error(anc, wrong_disk_type,
				    "Disk geometry does not match a DASD device"
				    " of type 3390.");
		}
		anc->dev_type = DASD_3390_TYPE;
		anc->blksize = blksize;
		anc->hw_cylinders =
			size_in_bytes /	(blksize * geo.heads * geo.sectors);
		/* The VOL1 label on a CDL formatted ECKD DASD is in block 2
		 * It will be verified later, if this position actually holds a
		 * valid label record.
		 */
		anc->label_pos = 2 * blksize;
		/* A devno 0 is actually a valid devno, which could exist
		 * in the system. Since we use this number only to create
		 * a default volume serial, there is no serious conflict.
		 */
		anc->devno = 0;
		if (anc->verbose) {
			printf("The following device geometry will be used:\n"
			       "device type %x, block size %d, cylinders %d,"
			       " heads %d, sectors %d\n",
			       anc->dev_type, anc->blksize, anc->hw_cylinders,
			       geo.heads, geo.sectors);
		}
	} else {
		characteristics = (struct dasd_eckd_characteristics *)
			&dasd_info.characteristics;
		if (characteristics->no_cyl == LV_COMPAT_CYL &&
		    characteristics->long_no_cyl)
			anc->hw_cylinders = characteristics->long_no_cyl;
		else
			anc->hw_cylinders = characteristics->no_cyl;

		if (strncmp(dasd_info.type, "ECKD", 4) != 0) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "%s is not an ECKD disk! This disk type "
				 "is not supported!", options.device);
			fdasd_error(anc, wrong_disk_type, err_str);
		}

		if (anc->verbose)
			printf("disk type check     : ok\n");

		if (dasd_info.FBA_layout != 0) {
			snprintf(err_str, ERROR_STRING_SIZE,
				 "%s is not formatted with z/OS compatible "
				 "disk layout!", options.device);
			fdasd_error(anc, wrong_disk_format, err_str);
		}

		if (anc->verbose)
			printf("disk layout check   : ok\n");

		if (dasd_info.open_count > 1) {
			if (anc->auto_partition) {
				snprintf(err_str, ERROR_STRING_SIZE,
					 "DASD '%s' is in use. Unmount it first!",
					 options.device);
				fdasd_error(anc, disk_in_use, err_str);
			} else {
				printf("\nWARNING: Your DASD '%s' is in use.\n"
				       "         If you proceed, you can "
				       "heavily damage your system.\n"
				       "         If possible exit all"
				       " applications using this disk\n"
				       "         and/or unmount it.\n\n",
				       options.device);
			}
		}

		if (anc->verbose)
			printf("usage count check   : ok\n");

		anc->dev_type	= dasd_info.dev_type;
		anc->blksize	= blksize;
		anc->label_pos	= dasd_info.label_block * blksize;
		anc->devno	= dasd_info.devno;
	}
}

/*
 * asks for partition boundaries
 */
static unsigned long
fdasd_read_int(unsigned long low, unsigned long dflt, unsigned long high,
	       enum offset base, char *mesg, fdasd_anchor_t *anc)
{
	unsigned int use_default = 1;
	unsigned long long trk = 0;
	char msg_txt[70];

	switch (base) {
	case lower:
		sprintf(msg_txt, "%s ([%ld]-%ld): ", mesg, low, high);
		break;
	case upper:
		sprintf(msg_txt, "%s (%ld-[%ld]): ", mesg, low, high);
		break;
	default:
		sprintf(msg_txt, "%s (%ld-%ld): ", mesg, low, high);
		break;
	}

	while (1) {
		while (!isdigit(read_char(msg_txt)) &&
		       (*line_ptr != '-' && *line_ptr != '+' && *line_ptr != '\0'))
			continue;
		if ((*line_ptr == '+' || *line_ptr == '-') && base != lower) {
			if (*line_ptr == '+')
				++line_ptr;
			trk = atoi(line_ptr);
			while (isdigit(*line_ptr)) {
				line_ptr++;
				use_default = 0;
			}

			switch (*line_ptr) {
			case 'c':
			case 'C':
				trk *= geo.heads;
				break;
			case 'k':
			case 'K':
				trk *= 1024;
				trk /= anc->blksize;
				trk /= geo.sectors;
				break;
			case 'm':
			case 'M':
				trk *= (1024 * 1024);
				trk /= anc->blksize;
				trk /= geo.sectors;
				break;
			case 'g':
			case 'G':
				trk *= (1024 * 1024 * 1024);
				trk /= anc->blksize;
				trk /= geo.sectors;
				break;
			case 0x0a:
				break;
			default:
				printf("WARNING: '%c' is not a "
				       "valid appendix and probably "
				       "not what you want!\n",
				       *line_ptr);
				break;
			}

			trk += (low - 1);

		} else if (*line_ptr == '\0') {
			switch (base) {
			case lower:
				trk = low;
				break;
			case upper:
				trk = high;
				break;
			}
		} else {
			if (*line_ptr == '+' || *line_ptr == '-') {
				printf("\nWARNING: '%c' is not valid in\n"
				       "this case and will be ignored!\n",
				       *line_ptr);
				++line_ptr;
			}

			trk = atoi(line_ptr);
			while (isdigit(*line_ptr)) {
				line_ptr++;
				use_default = 0;
			}

			if (*line_ptr != 0x0a)
				printf("\nWARNING: '%c' is not a valid "
				       "appendix and probably not what "
				       "you want!\n", *line_ptr);
		}
		if (use_default)
			printf("Using default value %lld\n", trk = dflt);
		else
			printf("You have selected track %lld\n", trk);

		if (trk >= low && trk <= high)
			break;
		else
			printf("Value out of range.\n");
	}

	return trk;
}

/*
 * returns unused partition info pointer if there
 * is a free partition, otherwise NULL
 */
static partition_info_t *fdasd_get_empty_f1_label(fdasd_anchor_t *anc)
{
	if (anc->used_partitions < USABLE_PARTITIONS)
		return anc->last;
	else
		return NULL;
}

/*
 * asks for and sets some important partition data
 */
static int fdasd_get_partition_data(fdasd_anchor_t *anc, extent_t *part_extent,
				    partition_info_t *part_info)
{
	unsigned long start, stop, limit;
	partition_info_t *part_tmp;
	cchh_t llimit, ulimit;
	u_int16_t hh, head;
	u_int32_t cc, cyl;
	u_int8_t b1, b2;
	char mesg[48];

	start = FIRST_USABLE_TRK;

	cyl = get_usable_cylinders(anc);
	head = anc->f4->DS4DEVCT.DS4DSTRK;
	limit = (head * cyl - 1);

	sprintf(mesg, "First track (1 track = %d KByte)",
		geo.sectors * anc->blksize / 1024);

	/* find default start value */
	for (part_tmp = anc->first; part_tmp->next != NULL;
	     part_tmp = part_tmp->next) {
		if ((start >= part_tmp->start_trk) &&
		    (start <= part_tmp->end_trk))
			start = part_tmp->end_trk + 1;
	}

	if (start > limit) {
		printf("Not that easy, no free tracks available.\n");
		return -1;
	}

	/* read start value */
	start = fdasd_read_int(start, start, limit, lower, mesg, anc);

	/* check start value from user */
	for (part_tmp = anc->first; part_tmp->next != NULL;
	     part_tmp = part_tmp->next) {
		if (start >= part_tmp->start_trk &&
		    start <= part_tmp->end_trk) {
			/* start is within another partition */
			start = part_tmp->end_trk + 1;
			if (start > limit) {
				start = FIRST_USABLE_TRK;
				part_tmp = anc->first;
			}

			printf("value within another partition, "
			       "using %ld instead\n", start);
		}

		if (start < part_tmp->start_trk) {
			limit = part_tmp->start_trk - 1;
			break;
		}
	}

	if (start == limit) {
		stop = start;
	} else {
		sprintf(mesg, "Last track or +size[c|k|m|g]");
		stop = fdasd_read_int(start, limit, limit, upper, mesg, anc);
	}

	/* update partition info */
	part_info->len_trk    = stop - start + 1;
	part_info->start_trk  = start;
	part_info->end_trk    = stop;

	cc = start / geo.heads;
	hh = start - (cc * geo.heads);
	vtoc_set_cchh(&llimit, cc, hh);

	/* check for cylinder boundary */
	if (hh == 0)
		b1 = 0x81;
	else
		b1 = 0x01;

	cc = stop / geo.heads;
	hh = stop - cc * geo.heads;
	vtoc_set_cchh(&ulimit, cc, hh);

	/* it is always the 1st extent */
	b2 = 0x00;

	vtoc_set_extent(part_extent, b1, b2, &llimit, &ulimit);

	return 0;
}

/*
 *
 */
static void fdasd_enqueue_new_partition(fdasd_anchor_t *anc)
{
	partition_info_t *part_tmp = anc->first, *part_info = anc->last;
	int i, j, k = 0;

	for (i = 1; i < USABLE_PARTITIONS; i++) {
		if ((part_tmp->end_trk == 0) ||
		    (part_info->start_trk < part_tmp->start_trk)) {
			break;
		} else {
			part_tmp = part_tmp->next;
			k++;
		}
	}

	if (anc->first == part_tmp)
		anc->first = part_info;

	if (part_info != part_tmp) {
		anc->last->prev->next = NULL;
		anc->last = anc->last->prev;

		part_info->next = part_tmp;
		part_info->prev = part_tmp->prev;
		part_tmp->prev = part_info;

		if (part_info->prev != NULL)
			part_info->prev->next = part_info;
	}

	part_info->used = 0x01;

	for (i = 0; i < USABLE_PARTITIONS; i++) {
		j = getpos(anc, i);
		if (j >= k)
			setpos(anc, i, j + 1);
	}

	/* update free-space counters */
	if (anc->first == part_info) {
		/* partition is the first used partition */
		if (part_info->start_trk == FIRST_USABLE_TRK) {
			/* partition starts right behind VTOC */
			part_info->fspace_trk = anc->fspace_trk -
				part_info->len_trk;
			anc->fspace_trk = 0;
		} else {
			/* there is some space between VTOC and partition */

			part_info->fspace_trk = anc->fspace_trk -
				part_info->len_trk - part_info->start_trk +
				FIRST_USABLE_TRK;
			anc->fspace_trk = part_info->start_trk -
				FIRST_USABLE_TRK;
		}
	} else {
		/* there are partitions in front of the new one */
		if (part_info->start_trk == part_info->prev->end_trk + 1) {
			/* new partition is right behind the previous one */
			part_info->fspace_trk = part_info->prev->fspace_trk -
				part_info->len_trk;
			part_info->prev->fspace_trk = 0;
		} else {
			/* there is some space between new and prev. part. */
			part_info->fspace_trk = part_info->prev->fspace_trk -
				part_info->len_trk - part_info->start_trk +
				part_info->prev->end_trk + 1;
			part_info->prev->fspace_trk = part_info->start_trk -
				part_info->prev->end_trk - 1;
		}
	}
}

/*
 *
 */
static void fdasd_dequeue_old_partition(fdasd_anchor_t *anc,
					partition_info_t *part_info, int k)
{
	int i, j;

	if (part_info != anc->first && part_info != anc->last) {
		/* dequeue any non-special element */
		part_info->prev->next = part_info->next;
		part_info->next->prev = part_info->prev;
	}

	if (part_info == anc->first) {
		/* dequeue first element */
		anc->first = part_info->next;
		part_info->next->prev = NULL;
		anc->fspace_trk += (part_info->len_trk +
				    part_info->fspace_trk);
	} else {
		part_info->prev->fspace_trk += (part_info->len_trk +
						part_info->fspace_trk);
	}

	if (part_info != anc->last) {
		part_info->prev = anc->last;
		part_info->next = NULL;
		anc->last->next = part_info;
		anc->last	= part_info;
	}

	for (i = 0; i < USABLE_PARTITIONS; i++) {
		j = getpos(anc, i);
		if (j >= k)
			setpos(anc, i, j - 1);
	}

	part_info->used       = 0x00;
	part_info->len_trk    = 0x0;
	part_info->start_trk  = 0x0;
	part_info->end_trk    = 0x0;
	part_info->fspace_trk = 0x0;
	bzero(part_info->f1, sizeof(format1_label_t));
}

/*
 * adds a new partition to the 'partition table'
 */
static void fdasd_add_partition(fdasd_anchor_t *anc)
{
	partition_info_t *part_info;
	unsigned long start, stop;
	extent_t ext;
	cchhb_t hf1;

	part_info = fdasd_get_empty_f1_label(anc);
	if (part_info == NULL) {
		printf("No more free partitions left,\n"
		       "you have to delete one first!");
		return;
	}

	if (fdasd_get_partition_data(anc, &ext, part_info) != 0)
		return;

	if (anc->formatted_cylinders > LV_COMPAT_CYL)
		vtoc_init_format8_label(anc->blksize, &ext, part_info->f1);
	else
		vtoc_init_format1_label(anc->blksize, &ext, part_info->f1);

	fdasd_enqueue_new_partition(anc);
	anc->used_partitions += 1;

	get_addr_of_highest_f1_f8_label(anc, &hf1);
	vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC - 1);

	start = cchh2trk(&ext.llimit, &geo);
	stop = cchh2trk(&ext.ulimit, &geo);

	vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '-', anc->verbose,
			   start, stop, anc->formatted_cylinders, geo.heads);

	anc->vtoc_changed++;
}

/*
 * removes a partition from the 'partition table'
 */
static void fdasd_remove_partition(fdasd_anchor_t *anc)
{
	partition_info_t *part_info = anc->first;
	unsigned long start, stop;
	unsigned int part_id, i;
	cchhb_t hf1;

	fdasd_list_partition_table(anc);

	while (!isdigit(part_id = read_char("\ndelete partition with id "
					    "(use 0 to exit): ")))
		printf("Invalid partition id '%c' detected.\n", part_id);

	printf("\n");
	part_id -= 48;
	if (part_id == 0)
		return;
	if (part_id > anc->used_partitions) {
		printf("'%d' is not a valid partition id!\n", part_id);
		return;
	}

	printf("deleting partition number '%d'...\n", part_id);

	setpos(anc, part_id - 1, -1);
	for (i = 1; i < part_id; i++)
		part_info = part_info->next;

	start = cchh2trk(&part_info->f1->DS1EXT1.llimit, &geo);
	stop  = cchh2trk(&part_info->f1->DS1EXT1.ulimit, &geo);

	fdasd_dequeue_old_partition(anc, part_info, part_id - 1);
	anc->used_partitions -= 1;

	if (anc->used_partitions != 0)
		get_addr_of_highest_f1_f8_label(anc, &hf1);
	else
		bzero(&hf1, sizeof(struct cchhb));

	vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC + 1);
	vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', anc->verbose,
			   start, stop, anc->formatted_cylinders, geo.heads);

	anc->vtoc_changed++;
}

/*
 * writes a standard volume label and a standard VTOC with
 * only one partition to disc. With this function is it
 * possible to create one partition in non-interactive mode,
 * which can be used within shell scripts
 */
static void fdasd_auto_partition(fdasd_anchor_t *anc)
{
	partition_info_t *part_info = anc->first;
	cchh_t llimit, ulimit;
	u_int16_t head;
	u_int32_t cyl;
	extent_t ext;
	cchhb_t hf1;

	if (!anc->silent)
		printf("auto-creating one partition for the whole disk...\n");

	fdasd_init_volume_label(anc);

	if (anc->verbose)
		printf("initializing labels...\n");
	vtoc_init_format4_label(anc->f4, geo.cylinders,
				anc->formatted_cylinders,
				geo.heads, geo.sectors,
				anc->blksize, anc->dev_type);

	vtoc_init_format5_label(anc->f5);
	vtoc_init_format7_label(anc->f7);

	cyl = get_usable_cylinders(anc);
	head = anc->f4->DS4DEVCT.DS4DSTRK;

	part_info->used       = 0x01;
	part_info->fspace_trk = 0;
	part_info->len_trk    = head * cyl - FIRST_USABLE_TRK;
	part_info->start_trk  = FIRST_USABLE_TRK;
	part_info->end_trk    = head * cyl - 1;

	vtoc_set_cchh(&llimit, 0, FIRST_USABLE_TRK);
	vtoc_set_cchh(&ulimit, cyl - 1, head - 1);

	vtoc_set_extent(&ext, 0x01, 0x00, &llimit, &ulimit);

	if (anc->formatted_cylinders > LV_COMPAT_CYL)
		vtoc_init_format8_label(anc->blksize, &ext, part_info->f1);
	else
		vtoc_init_format1_label(anc->blksize, &ext, part_info->f1);

	anc->fspace_trk      = 0;
	anc->used_partitions = 1;

	get_addr_of_highest_f1_f8_label(anc, &hf1);
	vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC - 1);

	anc->vtoc_changed++;

	fdasd_write_labels(anc);
	fdasd_exit(anc, 0);
}

/*
 * does the partitioning regarding to the config file
 */
static void fdasd_auto_partition_conffile(fdasd_anchor_t *anc)
{
	partition_info_t *part_info = anc->first;
	unsigned long start, stop;
	cchh_t llimit, ulimit;
	char *dsname = NULL;
	extent_t ext;
	cchhb_t hf1;
	char *type;

	fdasd_init_volume_label(anc);

	if (anc->verbose)
		printf("initializing labels...\n");
	vtoc_init_format4_label(anc->f4, geo.cylinders,
				anc->formatted_cylinders,
				geo.heads, geo.sectors,
				anc->blksize, anc->dev_type);

	vtoc_init_format5_label(anc->f5);
	vtoc_init_format7_label(anc->f7);

	if (anc->fspace_trk != 0) {
		start = FIRST_USABLE_TRK;
		stop  = start + anc->fspace_trk - 1;

		vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+',
				   anc->verbose, start, stop,
				   anc->formatted_cylinders, geo.heads);
	}

	do {
		if (part_info->used != 0x01)
			continue;

		vtoc_set_cchh(&llimit,
			      part_info->start_trk / geo.heads,
			      part_info->start_trk % geo.heads);
		vtoc_set_cchh(&ulimit,
			      part_info->end_trk / geo.heads,
			      part_info->end_trk % geo.heads);

		vtoc_set_extent(&ext, (vtoc_get_head_from_cchh(&llimit) == 0
				       ? 0x81 : 0x01),
				0x00, &llimit, &ulimit);

		if (anc->formatted_cylinders > LV_COMPAT_CYL)
			vtoc_init_format8_label(anc->blksize, &ext,
						part_info->f1);
		else
			vtoc_init_format1_label(anc->blksize, &ext,
						part_info->f1);
		anc->used_partitions += 1;

		get_addr_of_highest_f1_f8_label(anc, &hf1);
		vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC - 1);

		/* update free space labels */
		if (part_info->fspace_trk != 0) {
			start = part_info->end_trk + 1;
			stop  = start + part_info->fspace_trk - 1;

			vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+',
					   anc->verbose, start, stop,
					   anc->formatted_cylinders, geo.heads);
		}

		/* write correct partition type */
		vtoc_ebcdic_dec(part_info->f1->DS1DSNAM,
				part_info->f1->DS1DSNAM, 44);
		type = strstr(part_info->f1->DS1DSNAM, ".NEW");
		get_part_dsname_by_type(part_info->type, &dsname);
		sprintf(type, ".%s", dsname);

		vtoc_ebcdic_enc(part_info->f1->DS1DSNAM,
				part_info->f1->DS1DSNAM, 44);
	} while ((part_info = part_info->next) != NULL);

	anc->vtoc_changed++;

	fdasd_write_labels(anc);
	fdasd_exit(anc, 0);
}

/*
 * quits fdasd without saving
 */
static void fdasd_quit(fdasd_anchor_t *anc)
{
	char str[INPUT_BUF_SIZE];

	if (anc->vtoc_changed || anc->vlabel_changed) {
		snprintf(str, INPUT_BUF_SIZE,
			 "All changes will be lost! "
			 "Do you really want to quit?");

		if (yes_no(str) == 1)
			return;

		printf("exiting without saving...\n");
	} else {
		if (!anc->silent)
			printf("exiting...\n");
	}

	fdasd_exit(anc, 0);
}

/*
 *
 */
int main(int argc, char *argv[])
{
	fdasd_anchor_t anchor;
	int rc = 0;

	fdasd_initialize_anchor(&anchor);

	fdasd_parse_options(&anchor, &options, argc, argv);
	fdasd_verify_device(&anchor, options.device);
	fdasd_verify_options(&anchor);
	fdasd_get_geometry(&anchor);
	fdasd_check_disk_access(&anchor);

	/* check dasd for labels and vtoc */
	rc = fdasd_check_volume(&anchor);

	if (anchor.formatted_cylinders * geo.heads > BIG_DISK_SIZE)
		anchor.big_disk++;

	if (anchor.auto_partition) {
		fdasd_recreate_vtoc_unconditional(&anchor);
		fdasd_auto_partition(&anchor);
	}

	if (options.conffile) {
		fdasd_recreate_vtoc_unconditional(&anchor);
		fdasd_parse_conffile(&anchor, &options);
		fdasd_check_conffile_input(&anchor, &options);
		fdasd_auto_partition_conffile(&anchor);
	}

	if (anchor.print_volser) {
		fdasd_print_volser(&anchor);
		fdasd_quit(&anchor);
	}

	if (anchor.print_table) {
		if (rc == 0)
			fdasd_list_partition_table(&anchor);
		fdasd_quit(&anchor);
	}

	fdasd_menu();

	while (1) {
		putchar('\n');
		switch (tolower(read_char("Command (m for help): "))) {
		case 'd':
			fdasd_remove_partition(&anchor);
			break;
		case 'n':
			fdasd_add_partition(&anchor);
			break;
		case 'v':
			fdasd_change_volser(&anchor);
			break;
		case 't':
			fdasd_change_part_type(&anchor);
			break;
		case 'p':
			fdasd_list_partition_table(&anchor);
			break;
		case 'l':
			printf("\n");
			fdasd_list_known_partitions();
			break;
		case 's':
			fdasd_show_mapping(&anchor);
			break;
		case 'u':
			anchor.option_reuse++;
			break;
		case 'r':
			anchor.option_recreate++;
			break;
		case 'm':
			fdasd_menu();
			break;
		case 'q':
			fdasd_quit(&anchor);
			break;
		case 'w':
			fdasd_write_labels(&anchor);
			fdasd_exit(&anchor, 0);
		default:
			printf("please use one of the following commands:\n");
			fdasd_menu();
		}

		if (anchor.option_reuse) {
			fdasd_reuse_vtoc(&anchor);
			anchor.option_reuse = 0;
		}

		if (anchor.option_recreate) {
			fdasd_recreate_vtoc(&anchor);
			anchor.option_recreate = 0;
		}
	}

	return EXIT_FAILURE;
}
