/*
 * File...........: s390-tools/fdasd/fdasd.c
 * Author(s)......: Volker Sameske <sameske@de.ibm.com>
 *                  Horst Hummel   <horst.hummel@de.ibm.com>
 *                  Gerhard Tonn   <ton@de.ibm.com>
 * Bugreports.to..: <Linux390@de.ibm.com>
 * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2001-2004
 */

#include <getopt.h>

#include "zt_common.h"
#include "vtoc.h"
#include "fdasd.h"

/* Full tool name */
static const char tool_name[] = "zSeries DASD partitioning program";

/* Copyright notice */
static const char copyright_notice[] = "(C) Copyright IBM Corp. 2001-2005";

/*
 * Print version information.
 */
static void
print_version (void)
{
	printf ("%s version %s.\n", tool_name, RELEASE_STRING);
	printf ("%s.\n", copyright_notice);
}

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;
}


/* 
 * 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; volser[from] && from < VOLSER_LENGTH; 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);
		part_info = next;
	}
}


/*
 * Exit fdasd.
 */
static void 
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, -1);
}


/*
 * 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))
		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) 
{
	char answer;
	
	while (1) {
		printf("%s (y/n): ", question_str);
		scanf("%c", &answer);
		if (answer == 'y') return 0;
		if (answer == 'n') return 1;
	}
}


/*
 * converts cyl-cyl-head-head-blk to blk
 */
static unsigned long
cchhb2blk (cchhb_t *p) 
{
        return (unsigned long) (p->cc * geo.heads * geo.sectors + 
                                p->hh * geo.sectors +
                                p->b);
}


/*
 *
 */
static char *
fdasd_partition_type (char *str) 
{
	if (strncmp("NATIVE", str, 6) == 0)
		strcpy(str, "Linux native");
	else if (strncmp("NEW   ", str, 6) == 0)
		strcpy(str, "Linux native");
	else if (strncmp("SWAP  ", str, 6) == 0)
		strcpy(str, "Linux swap");
	else
		strcpy(str, "unknown");

	return str;
}


/*
 * prints out the usage text
 */
static void
fdasd_usage (void) 
{
	printf ("\nUsage: fdasd [OPTIONS] [DEVICE]\n"
		"\n"
		"Partition a DASD device either in interactive mode or "
		"automatically.\n"
		"DEVICE is the node of the device (e.g. '/dev/dasda')\n"
		"\n"
		"-h, --help               Print this help, then exit\n"
		"-v, --version            Print version information, "
		                          "then exit\n"
		"-s, --silent             Suppress messages\n"
		"-r, --verbose            Provide more verbose output\n"
		"-a, --auto               Automatically create a partition "
		                          "using the entire disk\n"
		"-k, --keep_volser        Keep current volume serial when "
		                          "performing automatic\n"
		"                         partitioning\n"
		"-l, --label VOLSER       Set the volume serial to VOLSER "
		                          "when performing\n"
		"                         automatic partitioning\n"
		"-c, --config CONFIGFILE  Automatically create partition(s) "
		                          "using information\n"
                "                         found in CONFIGFILE\n"
		"-i, --volser             Print volume serial\n"
		"-p, --table              Print partition table\n"
		"\n"
		"Please report bugs to: linux390@de.ibm.com\n");
}


/*
 * 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"
	       "   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;
	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.");

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

	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.");
		part_info->used       = 0x00;
		part_info->len_trk    = 0;
		part_info->start_trk  = 0;
		part_info->fspace_trk = 0;

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

		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));
		
		prev_part_info = part_info;
	}
}


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

	do {
		opt = getopt_long(argc, argv, option_string, 
				  fdasd_long_options, &index);
		switch (opt) {
		case 'v':
			print_version();
			fdasd_exit(anc, 0);
		case 'h':
		case '?':
			fdasd_usage ();
			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 -1:
			/* End of options string - start of devices list */
			break;
		default:
			fdasd_error(anc, parser_failed, 
				    "Invalid command line detected\n");
		}
	} while (opt != -1);

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


/*
 * parses config file
 */
static int
fdasd_parse_conffile(fdasd_anchor_t *anc, struct fdasd_options *options) 
{
	char buffer[USABLE_PARTITIONS * LINE_LENGTH];
	char err_str[ERROR_STRING_SIZE], *c1, *c2;
	int fd, i=0;

	/* 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);
	}

	bzero(buffer, sizeof(buffer));
	read(fd, buffer, sizeof(buffer));
	close(fd);


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

	c1 = buffer;

	for (i=0; i<USABLE_PARTITIONS; i++) {
		char *token, *stopstring;

		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, "");

		token = strtok(c1, ",");
		if (strstr(token, "FIRST") != NULL)
			anc->confdata[i].start=FIRST_USABLE_TRK;
		else
			anc->confdata[i].start=strtol(token, &stopstring, 10);

		token = strtok(NULL, ",");
		if (strstr(token, "LAST") != NULL)
			anc->confdata[i].stop = geo.cylinders * geo.heads - 1;
		else
			anc->confdata[i].stop = strtol(token, &stopstring, 10);
		c1 = c2 + 1;
	}

	return 0;
}


/*
 * checks input from config file
 */
static void
fdasd_check_conffile_input (fdasd_anchor_t *anc,
			    struct fdasd_options *options)
{
	partition_info_t *part_info = anc->first;
	int i;

	if (anc->verbose) printf("checking config file data...\n");
	for (i=0; i<USABLE_PARTITIONS; i++) {
		unsigned long start, stop, first_trk, last_trk;
		char err_str[ERROR_STRING_SIZE];
	   
		start = anc->confdata[i].start;
		stop = anc->confdata[i].stop;

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

		first_trk = FIRST_USABLE_TRK;
		last_trk = geo.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;

		/* 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;
	}
	return;
}


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

	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 (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 (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 paramters 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  INV  inv         */
	/* keep_volser            -    REQ  INV  inv         */
	/* auto                        -    inv  inv         */
	/* config                           -    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) {
			fdasd_error(anc, parser_failed, 
				    "Option 'auto' required when specifying"
				    " 'label'\n");
		}
		if ((anc->keep_volser || options.conffile || 
		     anc->print_table)) {
			fdasd_error(anc, parser_failed, 
				    "Option 'label' cannot be used with "
				    "'keep_volser', 'config' and 'table'.\n");
		}
	}

	if (anc->keep_volser) {
		if (!anc->auto_partition) {
			fdasd_error(anc, parser_failed, 
				    "Option 'auto' required when specifying"
				    " 'keep_volser'\n");
		}
		if (options.conffile || anc->print_table) {
			fdasd_error(anc, parser_failed, 
				    "Option 'keep_volser' cannot be used"
				    " with 'config' and '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[30], dsname[45], *strp;
        partition_info_t *part_info;
	int i=0, j=0;

        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);

        strcpy(dev, options.device);
        if (((strp = strstr(dev,DISC)) != NULL) ||
	    ((strp = strstr(dev,DEVICE)) != 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");
}


/*
 * 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) 
{
        partition_info_t *part_info;
        char str[20], dev[30], *strp, *ch;
        int i=0, dev_len = strlen(options.device);

	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, geo.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");
	}

        strcpy(dev, options.device);
        if (((strp = strstr(dev,DISC)) != NULL) ||
	    ((strp = strstr(dev,DEVICE)) != 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);
        }
}

/*
 * get volser from vtoc
 */
static int
fdasd_get_volser(fdasd_anchor_t *anc, char *devname, char *volser)
{
	dasd_information_t dasd_info;
	int blksize;
	int fd;
	volume_label_t vlabel;
	char err_str[ERROR_STRING_SIZE];

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

	if (ioctl(fd, BIODASDINFO, &dasd_info) != 0) {
		close(fd);
		fdasd_error(anc, unable_to_ioctl, 
			    "Could not retrieve disk information.");
	}

	if (ioctl(fd, BLKSSZGET, &blksize) != 0) {
		close(fd);
	        fdasd_error(anc, unable_to_ioctl,
			    "Could not retrieve blocksize information.");
	}

	close(fd);

	if ((strncmp(dasd_info.type, "ECKD", 4) == 0) &&
	    (!dasd_info.FBA_layout)) {
		/* OS/390 and zOS compatible disk layout */
		vtoc_read_volume_label(options.device,
				       dasd_info.label_block * blksize,
				       &vlabel);
		vtoc_volume_label_get_volser(&vlabel, volser);
		return 0;
        }
	else {
		return -1;
	}
}
	

/*
 * call IOCTL to re-read the partition table
 */
static void
fdasd_reread_partition_table (fdasd_anchor_t *anc)
{
	char err_str[ERROR_STRING_SIZE];
	int fd;

	if (!anc->silent) printf("rereading partition table...\n");

	if ((fd = open(options.device, O_RDONLY)) < 0) {
		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);
	} 

	if (ioctl(fd, BLKRRPART, NULL) != 0) {
		close(fd);
		fdasd_error(anc, unable_to_ioctl, "Error while rereading "
			    "partition table.\nPlease reboot!");
	}
	close(fd);
} 


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

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

	blk = (cchhb2blk(&anc->vlabel->vtoc) - 1) * anc->blksize;
	if (blk <= 0) 
		fdasd_error(anc, vlabel_corrupted, "");

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

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

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

	/* loop over all FMT1 DSCBs */
	for (part_info = anc->first; part_info != NULL;
	     part_info = part_info->next) {
		blk += anc->blksize; 
		i++;

		if (part_info->used != 0x01) {
			vtoc_write_label(options.device, blk, 
					 part_info->f1, NULL, NULL, NULL);
			continue;
		}

		strncpy(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, ' ');
			strncpy(c1, s2, 31);
		}
		else {
			char *tmp = strstr(ch, "SWAP");

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

			setpos(anc, k, i-1);
			
			strncpy(ch, "LINUX.V               "
				"                      ", 44);

			strncpy(volser, anc->vlabel->volid, VOLSER_LENGTH);
			vtoc_ebcdic_dec(volser, volser, VOLSER_LENGTH);
			strncpy(c1, volser, VOLSER_LENGTH);
				
			c1 = strchr(ch, ' ');
			strncpy(c1, ".PART", 5);
			c1 += 5;

			sprintf(dsno,"%04d", k+1);
			strncpy(c1, dsno, 4);

			c1 += 4;
			if (tmp)
				strncpy(c1, ".SWAP", 5);
			else
				strncpy(c1, ".NATIVE", 7);
		}
		vtoc_ebcdic_enc(ch, ch, 44);
		if (anc->verbose) printf("f1 ");

		vtoc_write_label(options.device, blk, part_info->f1, NULL,
				 NULL, NULL);
	}
	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(fdasd_anchor_t *anc)
{
	partition_info_t *part_info = anc->first;
	char str[INPUT_BUF_SIZE];
	int i;

	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... ");
	}

	vtoc_init_format4_label(anc->f4, USABLE_PARTITIONS,
				geo.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, geo.cylinders * geo.heads - 1,
			   geo.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 = geo.cylinders * geo.heads - FIRST_USABLE_TRK;

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

	anc->vtoc_changed++;
	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;
	format1_label_t f1;
	format4_label_t f4;
	format5_label_t f5;
	format7_label_t f7;
	char str[INPUT_BUF_SIZE];

	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, USABLE_PARTITIONS,
				geo.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,
				   geo.cylinders, geo.heads);

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

		vtoc_init_format1_label(anc->vlabel->volid, anc->blksize, 
					&part_info->f1->DS1EXT1, &f1);

		strncpy(f1.DS1DSNAM, part_info->f1->DS1DSNAM, 44);
		strncpy(f1.DS1DSSN,  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,
					   geo.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++;

	return;
}


/*
 * 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)
{
        int i;
        unsigned int part_id, part_type;
	char str[20], *ch;
        partition_info_t *part_info;

	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 < 0) || (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));
	printf("   1  Linux native\n" \
	       "   2  Linux swap\n\n");
	part_type = 0;
	while ((part_type < 1) || (part_type > 2)) {
        	while (!isdigit(part_type = 
				read_char("new partition type: ")));
        	part_type -= 48;
	}

        switch (part_type) {
        case 1:
		strncpy(str, "NATIVE", 6);
                break;
        case 2:
		strncpy(str, "SWAP  ", 6);
                break;
        default:
                printf("'%d' is not supported!\n", part_type);
        }

	ch = strstr(part_info->f1->DS1DSNAM, "PART") + 9;
	if (ch != NULL)	
		strncpy(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 old_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, options.device, old_volser) == 0)
			vtoc_volume_label_set_volser(vlabel, old_volser);
		else {
			fdasd_error(anc, volser_not_found, options.device);
		}
	}
	else {
		if (options.volser == NULL) {
	        	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);
		} 
		else {
			fdasd_check_volser(options.volser, anc->devno);
			vtoc_volume_label_set_volser(vlabel, options.volser);
		}

	}

	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 int head = geo.heads;
	unsigned long max = geo.cylinders * head - 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) {
		        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 = part_info->f1->DS1EXT1.llimit.cc * 
			head + part_info->f1->DS1EXT1.llimit.hh;
		part_info->end_trk = part_info->f1->DS1EXT1.ulimit.cc * head +
			part_info->f1->DS1EXT1.ulimit.hh;
		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) 
{
	int i, j;
	format1_label_t *f1_label;
	partition_info_t *part_info;

	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");
	vtoc_init_format4_label(anc->f4, USABLE_PARTITIONS,
				geo.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, geo.cylinders * geo.heads - 1,
			   geo.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;
	format1_label_t f1_label;
	char part_no_str[5], *part_pos;

	if (!anc->silent) printf(" ok\n");
	blk += anc->blksize;

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

	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:
			if (anc->verbose) printf("f1 ");
			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;
		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)
{
	char str[INPUT_BUF_SIZE];

	/* 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);

	/* change volume serial? */
	if (options.volser && !anc->print_table) {
		char volser[VOLSER_LENGTH + 1];

		vtoc_volume_label_get_volser(anc->vlabel, volser);
		volser[VOLSER_LENGTH] = 0x00;

		fdasd_check_volser(options.volser, anc->devno);

		snprintf(str, INPUT_BUF_SIZE,
			"Overwrite existing volume serial '%-6s'\n"
			"with new volume serial '%-6s'?", 
			volser, options.volser);

		if (yes_no(str) != 0)
			return 0;
		
		vtoc_volume_label_set_volser(anc->vlabel, options.volser);
		anc->vlabel_changed++;
		anc->vtoc_changed++;
	}
	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);
	}

	vtoc_init_format4_label(anc->f4, USABLE_PARTITIONS, 
				geo.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, geo.cylinders * geo.heads - 1,
			   geo.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;
        unsigned long blk = -1;
	char str[LINE_LENGTH];
	char inp_buf[INPUT_BUF_SIZE];

	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) - 1) * anc->blksize;
		if (blk > 0) {
			int rc;
			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) {
			printf("\nCould not find VOL1 volume label.\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");
			strcpy(inp_buf,"Should I create a new one?");
		}
                if ((!anc->print_volser) && (!anc->print_table) && 
		    (yes_no(inp_buf) == 1)) {
		        printf("You need a VOL1 volume label for " \
		               "partitioning\nexiting...\n");
			fdasd_exit(anc, -1);
                }
		fdasd_init_volume_label(anc);

		vtoc_init_format4_label(anc->f4, USABLE_PARTITIONS,
					geo.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, 
				   geo.cylinders * geo.heads - 1,
				   geo.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, ro;

        if ((fd = open(options.device, O_RDWR)) == -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);
        }
	
	if (ioctl(fd, BLKROGET, &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);

        close(fd);
}     


/*
 * reads dasd geometry data
 */
static void
fdasd_get_geometry (fdasd_anchor_t *anc) 
{
        int fd, blksize = 0;
	dasd_information_t dasd_info;
	char err_str[ERROR_STRING_SIZE];

	if ((fd = open(options.device,O_RDONLY)) < 0) {
		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);
	}

	if (ioctl(fd, HDIO_GETGEO, &geo) != 0) {
	        close(fd);
		fdasd_error(anc, unable_to_ioctl, 
			    "Could not retrieve disk geometry information.");
	}

	if (ioctl(fd, BLKSSZGET, &blksize) != 0) {
	        close(fd);
	        fdasd_error(anc, unable_to_ioctl,
			    "Could not retrieve blocksize information.");
	}

	/* get disk type */
	if (ioctl(fd, BIODASDINFO, &dasd_info) != 0) {
	        close(fd);
		fdasd_error(anc, unable_to_ioctl, 
			    "Could not retrieve disk information.");
	}

	close(fd);

	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;
	anc->fspace_trk = geo.cylinders * geo.heads - FIRST_USABLE_TRK;
}


/*
 * 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 long long trk = 0;
	unsigned int use_default = 1;
	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 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;
	unsigned int cc, hh;
	cchh_t llimit,ulimit;
	partition_info_t *part_tmp;
        char mesg[48];
	u_int8_t b1, b2;
	u_int16_t cyl, head;

	start = FIRST_USABLE_TRK;
	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;

	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]");
		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, 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++) {
		int 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 partitons 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;

	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++) {
		int 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) 
{
	cchhb_t hf1;
	partition_info_t *part_info;
	extent_t ext;
	unsigned long start, stop;
	int i;

	if ((part_info = fdasd_get_empty_f1_label(anc)) == 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;
	vtoc_init_format1_label(anc->vlabel->volid, anc->blksize, &ext,
				part_info->f1);
	fdasd_enqueue_new_partition(anc);
	anc->used_partitions += 1;

	i = anc->used_partitions + 2;
	if (anc->big_disk) i++;

        vtoc_set_cchhb(&hf1, VTOC_START_CC, VTOC_START_HH, i);
	vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC - 1);

	start = ext.llimit.cc * geo.heads + ext.llimit.hh;
	stop  = ext.ulimit.cc * geo.heads + ext.ulimit.hh;

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

	anc->vtoc_changed++;
}


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

	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 < 0) || (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 = part_info->f1->DS1EXT1.llimit.cc * geo.heads + 
		part_info->f1->DS1EXT1.llimit.hh;
	stop  = part_info->f1->DS1EXT1.ulimit.cc * geo.heads + 
		part_info->f1->DS1EXT1.ulimit.hh;

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

	if (anc->used_partitions != 0) {
		i = anc->used_partitions + 2;
		if (anc->big_disk) i++;

        	vtoc_set_cchhb(&hf1, VTOC_START_CC, VTOC_START_HH, i);
	}
	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, geo.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 partiton in non-interactive mode,
 * which can be used within shell scripts
 */
static void
fdasd_auto_partition(fdasd_anchor_t *anc)
{
	volume_label_t *vlabel = anc->vlabel;
	partition_info_t *part_info = anc->first;
	cchh_t llimit,ulimit;
	cchhb_t hf1;
	extent_t ext;
	char volser[VOLSER_LENGTH + 1];
	u_int16_t cyl, head;
	int i;
	char old_volser[VOLSER_LENGTH + 1];

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

	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, options.device, old_volser) == 0)
			vtoc_volume_label_set_volser(vlabel, old_volser);
		else {
			fdasd_error(anc, volser_not_found, options.device);
		}
	}
	else {
		if (options.volser == NULL) 
			sprintf(volser, "0X%04x", anc->devno);
		else {
			strcpy(volser, options.volser);
			fdasd_check_volser(volser, anc->devno);
		}
		vtoc_volume_label_set_volser(vlabel, volser);

	}

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

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

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

	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;

	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);

	vtoc_init_format1_label(anc->vlabel->volid, anc->blksize, &ext,
				part_info->f1);
	i = 3;
	if (anc->big_disk) i++;

        vtoc_set_cchhb(&hf1, VTOC_START_CC, VTOC_START_HH, i);
	vtoc_update_format4_label(anc->f4, &hf1, anc->f4->DS4DSREC - 1);

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

	anc->vlabel_changed++;
	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)
{
	volume_label_t *vlabel = anc->vlabel;
	partition_info_t *part_info = anc->first;
	char volser[VOLSER_LENGTH + 1];
	int i;
	cchh_t llimit,ulimit;
	unsigned long start, stop;
	extent_t ext;
	cchhb_t hf1;
	char old_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, options.device, old_volser) == 0)
			vtoc_volume_label_set_volser(vlabel, old_volser);
		else {
			fdasd_error(anc, volser_not_found, options.device);
		}
	}
	else {
		if (options.volser == NULL) 
			sprintf(volser, "0X%04x", anc->devno);
		else {
			strcpy(volser, options.volser);
			fdasd_check_volser(volser, anc->devno);
		}
		vtoc_volume_label_set_volser(vlabel, volser);

	}

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

	if (anc->verbose) printf("initializing labels...\n");
	vtoc_init_format4_label(anc->f4, USABLE_PARTITIONS,
				geo.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,
				   geo.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, (llimit.hh == 0 ? 0x81 : 0x01), 
				0x00, &llimit, &ulimit);

		vtoc_init_format1_label(vlabel->volid,
					anc->blksize, &ext, part_info->f1);

		anc->used_partitions += 1;

		i = anc->used_partitions + 2;
		if (anc->big_disk) i++;

		vtoc_set_cchhb(&hf1, VTOC_START_CC, VTOC_START_HH, i);

		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,
					   geo.cylinders, geo.heads);
		}
	} while ((part_info = part_info->next) != NULL);

	anc->vlabel_changed++;
	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);

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

	if (anchor.auto_partition)
		fdasd_auto_partition(&anchor);

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

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

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

	if (anchor.print_volser) {
		fdasd_print_volser(&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 '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 -1;
}










