/*
 * File...........: s390-tools/fdasd/fdasd.c
 * Author(s)......: Volker Sameske <sameske@de.ibm.com>
 *                  Horst Hummel   <horst.hummel@de.ibm.com>
 * Bugreports.to..: <Linux390@de.ibm.com>
 * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2001-2002
 *
 * History of changes (starts March 2001)
 * 2001-04-11 possibility to change volume serial added
 *            possibility to change partition type added
 *            some changes to DS4HPCHR and DS4DSREC
 * 2001-05-03 check for invalid partition numbers added
 *            wrong free_space calculation bug fixed
 * 2001-06-26 '-a' option added, it is now possible to add a single
 *            partition in non-interactive mode
 * 2001-06-26 long parameter support added
 * 2001-10-01 show mapping (partition number - data set name)
 * 2001-10-22 data set name starts counting now from PART0001
 * 2002-02-26 volsers like 'AA AAA' will be changed to 'AAAAA '
 * 2002-03-12 removed dependency to kernel headers          
 * 2002-04-17 added -p command option
 * 2002-05-10 changed API policy
 * 2002-06-26 added -i option to display volser
 */


#include <getopt.h>

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

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 void fdasd_check_volser(char *s, int devno)
{
	int i,j;

	for (i=0; i<6; i++) {
		if ((s[i] < 0x20) || (s[i] > 0x7a) ||
		    ((s[i] >= 0x21)&&(s[i] <= 0x22)) ||  /* !"         */
		    ((s[i] >= 0x26)&&(s[i] <= 0x2f)) ||  /* &'()*+,-./ */
		    ((s[i] >= 0x3a)&&(s[i] <= 0x3f)) ||  /* :;<=>?     */
		    ((s[i] >= 0x5b)&&(s[i] <= 0x60)))    /* \]^_´      */
			s[i] = ' ';
		s[i] = toupper(s[i]);
	}
	s[6] = 0x00;		

	for (i=0; i<6; i++) {
		if (s[i] == ' ')
			for (j=i; j<6; j++)
				if (s[j] != ' ') {
					s[i] = s[j];
					s[j] = ' ';
					break;
				}
	}

	if (s[0] == ' ') {
		printf("Usage error, switching to default.\n");
		sprintf(s, "0X%04x", devno);
		for (i=0; i<6; i++)
			s[i] = toupper(s[i]);
	}
}


/*
 *
 */
static void fdasd_cleanup (fdasd_anchor_t *anchor) 
{
        partition_info_t *p, *q;
        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);

	p = anchor->first;
	if (p == NULL) return;

        for (i=1; i<=USABLE_PARTITIONS; i++) {
	        q = p->next;
		if (p != NULL) free(p);
		p = q;
	}

	if(anchor->devname_specified)
		free(options.device);

	if(anchor->volid_specified)
		free(options.volid);
}


/*
 *
 */
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    error[2*LINE_LENGTH], *message = error;

	switch (why) {
        case unable_to_open_disk:
	        sprintf(error, "%s open error\n%s\n", FDASD_ERROR, str);
		break;
        case unable_to_seek_disk:
	        sprintf(error, "%s seek error\n%s\n", FDASD_ERROR, str);
		break;
        case unable_to_read_disk:
	        sprintf(error, "%s read error\n%s\n", FDASD_ERROR, str);
		break;
        case read_only_disk:
	        sprintf(error, "%s write error\n%s\n", FDASD_ERROR, str);
		break;
        case unable_to_ioctl:
	        sprintf(error, "%s IOCTL error\n%s\n", FDASD_ERROR, str);
		break;
        case api_version_mismatch:
                sprintf(error, "%s API version mismatch\n%s\n",
                        FDASD_ERROR, str);
                break;     
        case wrong_disk_type:
                sprintf(error, "%s Unsupported disk type\n%s\n",
                        FDASD_ERROR, str);
                break;           
        case wrong_disk_format:
                sprintf(error, "%s Unsupported disk format\n%s\n",
                        FDASD_ERROR, str);
                break;   
        case disk_in_use:
                sprintf(error, "%s Disk in use\n%s\n", FDASD_ERROR, str);
                break;      
        case config_syntax_error:
                sprintf(error, "%s Config file syntax error\n%s\n",
                        FDASD_ERROR, str);
                break;       
        case vlabel_corrupted:
	        sprintf(error, "%s Volume label is corrupted.\n%s\n", 
			FDASD_ERROR, str);
		break;
        case dsname_corrupted:
	        sprintf(error, "%s a data set name is corrupted.\n%s\n", 
			FDASD_ERROR, str);
		break;
        case malloc_failed:
	        sprintf(error, "%s space allocation\n%s\n", FDASD_ERROR, str);
		break;
        case device_verification_failed:
	        sprintf(error, "%s device verification failed\n" \
			"The specified device is not a valid DASD device\n",
			FDASD_ERROR);
		break;
	default: 
	        sprintf(error,"%s Fatal error\n%s\n",
			FDASD_ERROR, str);
	}

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

	fdasd_exit(anc, -1);
}


/*
 *
 */
static int read_line(void) 
{
	bzero(line_buffer,LINE_LENGTH);
        if (!fgets(line_buffer, LINE_LENGTH, stdin))
		return 0;
	line_ptr = line_buffer;
	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;
}


/*
 *
 */
static int yes_no(char *str) 
{
	char s;
	while (1) {
		s = tolower(read_char(strcat(str," (y/n): ")));
		if (s == 'y') return 0;
		if (s == '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 ("Usage:\n"
		"interactive menu mode:\n"
		"    fdasd [-l volser] device\n"
		"command line mode:\n"
		"    fdasd [-s] {-p|-i|-a|-c config} device\n"
		"help:\n"
		"    fdasd {-v|-h|-?}\n\n"
		"Options\n"
		"-v: print version number\n"
		"-h: print this message\n"
		"-?: print this message\n"
		"-s: suppress messages\n"
		"-l: change or set the volume serial\n"
		"-c: auto-partition the DASD using information specified\n"
		"    in a configuration file (non-interactive)\n"
		"-a: auto-partition the DASD with one single partition,\n"
		"    using the entire available disk space\n"
		"-p: print partition table (short version with additional -s)\n"
		"-i: print volume serial\n\n"
		"device: your DASD device, e.g. /dev/dasda or\n"
		"        /dev/dasd/1234/device for the device file system\n"
		"        (1234 is the DASD device number)\n"
		"volser: 6 char volume serial number (volume identifier)\n"
		"config: configuration file\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 *p, *q;
	volume_label_t   *v;
        int               i;

	anc->devno             = 0;
	anc->dev_type          = 0;
	anc->used_partitions   = 0;

	anc->silent            = 0;
	anc->verbose           = 0;
	anc->big_disk          = 0;
	anc->volid_specified   = 0;
	anc->config_specified  = 0;
	anc->auto_partition    = 0;
	anc->devname_specified = 0;
	anc->print_table       = 0;
	anc->print_volid       = 0;

	anc->option_reuse      = 0;
	anc->option_recreate   = 0;

        anc->vlabel_changed    = 0;
	anc->vtoc_changed      = 0;
	anc->blksize           = 0;
	anc->fspace_trk        = 0;
	anc->label_pos         = 0;

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

	bzero(anc->confdata, sizeof(config_data_t));

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

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

	for (i=1; i<=USABLE_PARTITIONS; i++) {
	        p = malloc(sizeof(partition_info_t));
		if (p == NULL) 
			fdasd_error(anc, malloc_failed,
				   "Partition info memory allocation failed.");
		p->used       = 0x00;
		p->len_trk    = 0;
		p->start_trk  = 0;
		p->fspace_trk = 0;

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

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


/*
 * parses the command line options
 */
static int fdasd_parse_options (fdasd_anchor_t *anc, struct fdasd_options *o, 
				int argc, char *argv[]) 
{
	int rc = 0, k, len, index;
	char *ch;

	while (1) {
		k = getopt_long(argc, argv, "?hl:c:vaspi", 
				fdasd_long_options, &index);
        	if (k == -1) break;

		switch (k) {
		case 'v':
			printf("fdasd version: %s\n", FDASD_VERSION);
			fdasd_exit(anc, 0);
		case 'h':
		case '?':
			fdasd_usage ();
			fdasd_exit(anc, 0);
		case 'l':
			if ((o->volid = (char *) malloc(k)) == NULL)
				fdasd_error(anc, malloc_failed, 
				"Memory allocation for options failed.");

			strncpy(o->volid, optarg, strlen(optarg));
			anc->volid_specified++;
			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_volid++;
			anc->silent++;
			break;
		case 'c':
			if ((o->conffile = (char *) malloc(k)) == NULL)
				fdasd_error(anc, malloc_failed, 
				"Memory allocation for options failed.");

			strncpy(o->conffile, optarg, strlen(optarg));
			anc->config_specified++;
			break;
		default:
			rc = EINVAL;
			break;
		}
	}

	if ((ch = argv[optind]) != NULL) {
		len = strlen(ch);
		if ((o->device = malloc(len)) == NULL)
			fdasd_error(anc, malloc_failed, 
			    "options.device  memory allocation failed.");

		strncpy(o->device,ch,len);
		anc->devname_specified++;
	}

	return rc;
}


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

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

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

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


	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) {
			sprintf(str,"']' missing in config file " \
				"%s\n", o->conffile);
			fdasd_error(anc, config_syntax_error, 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 *o)
{
	partition_info_t *p = anc->first;
	int i;

	if (anc->verbose) printf("checking config file data...\n");
	for (i=0; i<USABLE_PARTITIONS; i++) {
		unsigned long a,b,l,u;
		char str[LINE_LENGTH];
	   
		a = anc->confdata[i].start;
		b = anc->confdata[i].stop;

		if ((a == 0) || (b == 0)) break;

		l = FIRST_USABLE_TRK;
		u = geo.cylinders * geo.heads - 1;

		if ((a < l) || (a > u)) {
			sprintf(str,"One of the lower partition limits "
				"(%ld) is not within the range of \n "
				"available tracks on disk (%ld-%ld)!\n", 
				a, l, u);
			fdasd_error(anc, config_syntax_error, str);
		}

		if ((b < l) || (b > u)) {
			sprintf(str,"One of the upper partition limits "
				"(%ld) is not within the range of \n "
				"available tracks on disk (%ld-%ld)!\n", 
				b, l, u);
			fdasd_error(anc, config_syntax_error, str);
		}

		if (a >= b) {
			sprintf(str,"Lower partition limit (%ld) is not "
				"less than upper partition \nlimit (%ld) "
				"in config file %s!\n", a, b, o->conffile);
			fdasd_error(anc, config_syntax_error, str);
		}

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

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

		p->used      = 0x01;
		p->start_trk = a;
		p->end_trk   = b;
		p->len_trk   = b - a + 1;

		/* update the current free space counter */
		if (i == 0)
			anc->fspace_trk = a - FIRST_USABLE_TRK;
		
		if (i < USABLE_PARTITIONS - 1) {
			if (anc->confdata[i+1].start != 0)
				p->fspace_trk = anc->confdata[i+1].start-b-1; 
			else
				p->fspace_trk = u - b; 
		}
		else if (i == USABLE_PARTITIONS - 1)
			p->fspace_trk = u - b; 

		p = p->next;
	}

	return;
}


/*
 * verifies the specified dasd device
 */
static int fdasd_verify_device (fdasd_anchor_t *anc, char *name) 
{
	int rc = 0;
	struct stat dst;
	if ( (stat(name, &dst)) < 0 )  
		fdasd_error(anc, device_verification_failed, "");

	if (S_ISBLK (dst.st_mode)) {
		if (!(minor (dst.st_rdev) & PARTN_MASK))
			rc = dst.st_rdev;
		else {
			printf("Invalid minor and partition mask\n");
			rc = -1;
			errno = EINVAL;
		}
	}
	else {
		printf("%s is no block device\n", name);
		rc = -1;
		errno = EINVAL;
	}

	return rc;
}


/*
 * verifies the specified fdasd command line options
 */
static int fdasd_verify_options (fdasd_anchor_t *anc, struct fdasd_options *o) 
{
	int dev = 0;

	if (!o->device) {
		fdasd_usage ();
		fdasd_exit (anc, -1);
	}

	if ( (dev = fdasd_verify_device(anc, o->device)) == -1 ) 
		fdasd_error(anc, device_verification_failed, "");

	if (anc->verbose)
		printf("(%d/%d)\n", (unsigned short) major (dev),
		       (unsigned short) minor (dev));

	if (anc->print_table && (anc->auto_partition || anc->print_volid ||
	    anc->config_specified || anc->volid_specified)) {
		printf("It makes no sense to specify the -p option "
		       "together with other options.\n");
		fdasd_exit (anc, -1);
	}

	if (anc->print_volid && (anc->auto_partition ||
	    anc->config_specified || anc->volid_specified)) {
		printf("It makes no sense to specify the -i option "
		       "together with other options.\n");
		fdasd_exit (anc, -1);
	}

	if (anc->auto_partition && anc->config_specified) {
		printf("It makes no sense to use -a together with -c.\n");
		fdasd_exit (anc, -1);
	}

	return 0;
}


/*
 * 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 *p;
	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 identifier: %.6s\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 (p = anc->first ; p != NULL; p = p->next) {
                i++;
                if (p->used != 0x01)
			continue;

		bzero(dsname, sizeof(dsname));
		strncpy(dsname, p->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 identifier
 */
static void fdasd_print_volid (fdasd_anchor_t *anc)
{
	char volid[7];

	vtoc_ebcdic_dec(anc->vlabel->volid, volid, VOLSER_LENGTH);
	volid[6] = '\0';
	printf("%6.6s\n", volid);
}


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

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

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

		vtoc_volume_label_get_volser(anc->vlabel, str);

		printf("volume identifier: %.6s\n", str);
		printf("maximum partition number: %d\n\n", USABLE_PARTITIONS);
	}

        if (w < 20)
                w = 20;

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

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

        strcpy(dev, options.device);
        if (((strp = strstr(dev,DISC)) != NULL) ||
	    ((strp = strstr(dev,DEVICE)) != NULL))
                strcpy(strp, PART);

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

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

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

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

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

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

		if (p->fspace_trk > 0) 
			printf("%*s   %9ld%9ld%9ld       unused\n",w ,"" ,
			       p->end_trk + 1, p->end_trk + p->fspace_trk,
			       p->fspace_trk);
        }
}


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

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

	if ((f = open(options.device, O_RDONLY)) < 0) {
		sprintf(str,"Could not open device '%s' "
			"in read-only mode!\n", options.device);
		fdasd_error(anc, unable_to_open_disk, str);
	} 

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


/*
 * writes all changes to dasd
 */
static void fdasd_write_vtoc_labels (fdasd_anchor_t *anc) 
{
        partition_info_t *p;
	unsigned long b;
	char dsno[6], s1[7], s2[45], *c1, *c2, *ch;
	int i=0, k=0;

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

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

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

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

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

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

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

		strncpy(p->f1->DS1DSSN,	anc->vlabel->volid, 6);

		ch = p->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(s1, anc->vlabel->volid, 6);
			vtoc_ebcdic_dec(s1, s1, 6);
			s1[6] = ' ';
			strncpy(c1, s1, 7);
			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(s1, anc->vlabel->volid, 6);
			vtoc_ebcdic_dec(s1, s1, 6);
			strncpy(c1, s1, 6);
				
			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, b, p->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 *p = anc->first;
	char str[2*LINE_LENGTH];
	int i;

	if (!anc->silent) {
		sprintf(str, "ATTENTION: this will delete all partitions!\n"
			"           Do you know what you're doing?");

		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 (p != NULL) {

		bzero(p->f1, sizeof(format1_label_t));

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

		p = p->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 *p = anc->first;
	format1_label_t f1;
	format4_label_t f4;
	format5_label_t f5;
	format7_label_t f7;
	char str[2*LINE_LENGTH];

	if (!anc->silent) {
		sprintf(str, "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 (p != NULL) {
		if (p->used != 0x01) {
			p = p->next;
			continue;
		}

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

		strncpy(f1.DS1DSNAM, p->f1->DS1DSNAM, 44);
		strncpy(f1.DS1DSSN,  p->f1->DS1DSSN, 6);
		f1.DS1CREDT = p->f1->DS1CREDT;

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

		if (p->fspace_trk > 0)
			vtoc_set_freespace(&f4, &f5, &f7, '+', anc->verbose,
					   p->end_trk + 1, 
					   p->end_trk + p->fspace_trk,
					   geo.cylinders, geo.heads);

		p = p->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
 */
static void fdasd_change_volser (fdasd_anchor_t *anc) 
{
	char str[7];

	vtoc_volume_label_get_volser(anc->vlabel, str);
	printf("Please specify the new volume serial (6 characters).\n" \
	       "(Press ENTER if you do not want to change it)\n\n");
	printf("current: %.6s\nnew:     ", str);

        fgets(line_buffer, LINE_LENGTH, stdin);
        line_ptr = line_buffer;
        while (*line_ptr && !isgraph(*line_ptr))
        	line_ptr++;

	strncpy(str, line_ptr, 6);
	fdasd_check_volser(str, anc->devno);

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

	vtoc_set_cchhb(&anc->vlabel->vtoc, 0x0000, 0x0001, 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 k;
	char str[7], *ch;
        partition_info_t *p = anc->first;

	fdasd_list_partition_table(anc);

	/* ask for partition number */
	printf("\nchange partition type\n");
        while (!isdigit(k = read_char("partition id (use 0 to exit): ")))
		printf("Seriously, do you think '%c' is a partition id!\n", 
		       k);

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

        for (i=1; i<k; i++) p=p->next;

	/* ask for partition type */
	k = 0;
	vtoc_ebcdic_dec(p->f1->DS1DSNAM, p->f1->DS1DSNAM, 44);
	ch = strstr(p->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");
	while ((k < 1) || (k > 2)) {
        	while (!isdigit(k = read_char("new partition type: ")));
        	k -= 48;
	}

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

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



/*
 * initialize the VOL1 volume label
 */
static void fdasd_init_volume_label (fdasd_anchor_t *anc) 
{
	volume_label_t *v = anc->vlabel;

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

	if (options.volid == NULL) {
	        printf("\nPlease insert a volume serial (6 characters) "
		       "or simply hit enter for '0X%04x'\n--> ", anc->devno);
		fgets(line_buffer, LINE_LENGTH, stdin);
		line_ptr = line_buffer;
		while (*line_ptr && !isgraph(*line_ptr))
		        line_ptr++;
		if (strcmp(line_ptr, "") == 0)
			sprintf(line_ptr, "0X%04x", anc->devno);

		fdasd_check_volser(line_ptr, anc->devno);
		vtoc_volume_label_set_volser(v, line_ptr);
	} 
	else {
		fdasd_check_volser(options.volid, anc->devno);
		vtoc_volume_label_set_volser(v, options.volid);
	}

	vtoc_set_cchhb(&v->vtoc, 0x0000, 0x0001, 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 *q = NULL, *p = anc->first;
	unsigned int h = geo.heads;
	unsigned long max = geo.cylinders * h - 1;
        int i;

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

        for (i=1; i<=USABLE_PARTITIONS; i++) {
	        if (p->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 */
			        q->fspace_trk = max - q->end_trk;
			break;
		}
		
		/* this is a valid format 1 label */
		p->used      = 0x01;
		p->start_trk = p->f1->DS1EXT1.llimit.cc * h +
	                       p->f1->DS1EXT1.llimit.hh;
		p->end_trk   = p->f1->DS1EXT1.ulimit.cc * h +
		               p->f1->DS1EXT1.ulimit.hh;
		p->len_trk   = p->end_trk - p->start_trk + 1;

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

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

	        q = p;
	        p = p->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 *ltmp;
	partition_info_t *ptmp;

	for (i=1; i<=USABLE_PARTITIONS - 1; i++) {
		ptmp = anc->first;
		for (j=1; j<=USABLE_PARTITIONS - i; j++) {
			if (ptmp->f1->DS1FMTID < ptmp->next->f1->DS1FMTID) {
				ltmp = ptmp->f1;
				ptmp->f1 = ptmp->next->f1;
				ptmp->next->f1 = ltmp;
			}
			ptmp=ptmp->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 b)
{
	int f5_counter = 0, f7_counter = 0, f1_counter = 0, oldfmt = 0;
	int i, n, f1size = sizeof(format1_label_t);
	partition_info_t *p = anc->first;
	format1_label_t q;
	char s[5], *ch;

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

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

	for (i = 1; i <= geo.sectors; i++) {
		bzero(&q, f1size);
		vtoc_read_label(options.device, b, &q, NULL, NULL, NULL);

		switch (q.DS1FMTID) {
		case 0xf1:
			if (anc->verbose) printf("f1 ");
			if(p == NULL) break;
			memcpy(p->f1, &q, f1size);

			n = -1;
			vtoc_ebcdic_dec(p->f1->DS1DSNAM, p->f1->DS1DSNAM, 44);
			ch = strstr(p->f1->DS1DSNAM, "PART");
			if (ch != NULL) {
				strncpy(s, ch + 4, 4);
				s[4] = '\0';
				n = atoi(s) - 1;
			}

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

			/* this dasd has data set names 0000-0002
                           but we use now 0001-0003 */
			if (n == -1)
				oldfmt++;

			if ((n < 0) || (n>=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",
				       n + 1, USABLE_PARTITIONS);
			else
					setpos(anc, n, f1_counter);

			p = p->next;
			f1_counter++;
			break;
		case 0xf5:
			if (anc->verbose) printf("f5 ");
			memcpy(anc->f5, &q, f1size);
			f5_counter++;
			break;
		case 0xf7:
			if (anc->verbose) printf("f7 ");
			if (f7_counter == 0) memcpy(anc->f7, &q, f1size);
			f7_counter++;
			break;
		default: 
			if (q.DS1FMTID > 0)
				printf("'%d' is not supported!\n", q.DS1FMTID);
		}
		b += anc->blksize;
	}
		
	if ((oldfmt > 0) && !anc->print_table) {
		/* this is the old format PART0000 - PART0002 */
		printf("WARNING: it looks like your DASD has been "
		       "partitioned with an older \nversion of fdasd.\n"
		       "Updating data set names...\n");

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

		anc->vtoc_changed++;
	}

	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 b)
{
	char str[LINE_LENGTH];

	/* VOL1 label contains valid VTOC pointer */
	if (!anc->silent)
		printf("reading vtoc        :");

	vtoc_read_label(options.device, b, 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, b);

	/* change volume identifier? */
	if (anc->volid_specified && !anc->print_table) {
		char v[7];

		vtoc_volume_label_get_volser(anc->vlabel, v);
		v[6] = 0x00;

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

		sprintf(str,"Overwrite existing volume identifier '%-6s'\n" \
			"with new volume identifier '%-6s'?", 
			v, options.volid);

		if (yes_no(str) != 0)
			return 0;
		
		vtoc_volume_label_set_volser(anc->vlabel, options.volid);
		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 */
	printf("There is no VTOC yet, ");

	if (yes_no("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, 0x0000, 0x0001, 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   *v = anc->vlabel;
        unsigned long b = -1;
	char str[LINE_LENGTH];

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

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

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

		b = (cchhb2blk(&v->vtoc) - 1) * anc->blksize;
		if (b > 0) {
			int rc;
			rc = fdasd_valid_vtoc_pointer(anc, b);

			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(v->vollbl, vtoc_ebcdic_enc("LNX1",str,4),4) == 0) {
			if (!anc->silent)
				printf(" LNX1\n");
			strcpy(str,"Overwrite inapplicable label?");
		}
		else {
			if (!anc->silent)
				printf(" no known label\n");
			strcpy(str,"Should I create a new one?");
		}
                if ((!anc->print_volid) && (!anc->print_table) && (yes_no(str) == 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 for read-only disks
 * read the last block in the second track and write it back
 */
static void fdasd_check_ro_disks (fdasd_anchor_t *anc)
{
	char s[LINE_LENGTH];
	format1_label_t f1;
	int f, t, pos;

        if ((f = open(options.device, O_RDWR)) == -1) {
		sprintf(s,"Could not open device '%s' " \
			"in read-only mode!\n", options.device);
		fdasd_error(anc, unable_to_open_disk, s);
	}

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

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

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

	if (write(f, &f1, t) != t) {
		close(f);
		sprintf(s, "device '%s' is a read-only device. Exiting...",
			options.device);
		fdasd_error(anc, read_only_disk, s);
	}

        close(f);
}     


/*
 * checks the current API version with the API version of the dasd driver
 */
static void fdasd_check_api_version (fdasd_anchor_t *anc)
{
        int f, api;
	char s[LINE_LENGTH];
 
        if ((f = open(options.device,O_RDONLY)) < 0) {
		sprintf(s,"Could not open device '%s' in read-only mode!\n", 
			options.device);
		fdasd_error(anc, unable_to_open_disk, s);
	}

        /* get dasd driver API version */
        if (ioctl(f, DASDAPIVER, &api) != 0) {
                close(f);
                fdasd_error(anc, unable_to_ioctl,
                            "Could not retrieve API version.");
        }
 
	close(f);
	
	/* check API versions */
	if (api < DASD_MIN_API_VERSION) {
		
		sprintf(s, "WARNING: This API version '%d' doesn't match "
			"with the API version of the DASD driver '%d'!\n",
			DASD_MIN_API_VERSION, api);
		fdasd_error(anc, api_version_mismatch, s);
	}
	else
		if (anc->verbose) printf("API version check   : ok\n");

	if (api == 1) {
		sprintf(s, "WARNING: The dasd driver API version '1' is not"
			"supported!\n");
		fdasd_error(anc, api_version_mismatch, s);
        }
}                                      


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

	if ((f = open(options.device,O_RDONLY)) < 0) {
		sprintf(s,"Could not open device '%s' "
			"in read-only mode!\n", options.device);
		fdasd_error(anc, unable_to_open_disk, s);
	}

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

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

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

	close(f);

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

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

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

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

	if (dasd_info.open_count > 1) {
		if (anc->auto_partition) {
			sprintf(s, "DASD '%s' is in use. Unmount it first!",
				options.device);
			fdasd_error(anc, disk_in_use, s);
		} 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 int fdasd_read_int (uint low, uint dflt, uint high, 
				    enum offset base, char *mesg, 
				    fdasd_anchor_t *anc) 
{
        unsigned int i=0, use_default = 1;
	char ms[70];

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

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

                        switch (*line_ptr) {
                                case 'c':
                                case 'C': 
					i *= geo.heads;
                                        break;
                                case 'k':
                                case 'K': 
					i *= 1024;
                                        i /= anc->blksize;
					i /= geo.sectors;
                                        break;
                                case 'm':
                                case 'M': 
					i *= (1024*1024);
                                        i /= anc->blksize;
					i /= 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;
                        }

			i += (low - 1);

		}
		else if (*line_ptr == '\0') {
			switch(base) {
			case lower: i = low; break;
			case upper: i = 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;
			}

		        i = 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 %d\n", i = dflt);
		else
		        printf("You have selected track %d\n", i);

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


/*
 * 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 *p) 
{
	unsigned int start = 0, stop = 0, limit, cc, hh;
	cchh_t llimit,ulimit;
	partition_info_t *q;
        char mesg[48];
	u_int8_t b1, b2;
	u_int16_t c, h;

	start = FIRST_USABLE_TRK;
	if (anc->f4->DS4DEVCT.DS4DEVFG & ALTERNATE_CYLINDERS_USED)
		c = anc->f4->DS4DEVCT.DS4DSCYL - (u_int16_t) anc->f4->DS4DEVAC;
	else
		c = anc->f4->DS4DEVCT.DS4DSCYL;

	h = anc->f4->DS4DEVCT.DS4DSTRK;
	limit = (h * c - 1);

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

	/* find default start value */
	for (q = anc->first; q->next != NULL; q = q->next) {
		if ((start >= q->start_trk) && (start <= q->end_trk))
			start = q->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 (q = anc->first; q->next != NULL; q = q->next) {
		if (start >= q->start_trk && start <= q->end_trk) {
			/* start is within another partition */
			start = q->end_trk + 1;
			if (start > limit) {
				start = FIRST_USABLE_TRK;
				q = anc->first;
			}

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

		if (start < q->start_trk) {
			limit = q->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 */
	p->len_trk    = stop - start + 1;
	p->start_trk  = start;
	p->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 *q = anc->first, *p = anc->last;
	int i,k=0;

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

	if (anc->first == q) anc->first = p;
	
	if (p != q) {
	        anc->last->prev->next = NULL;
		anc->last = anc->last->prev;

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

	p->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 == p) {
	        /* partition is the first used partition */
	        if (p->start_trk == FIRST_USABLE_TRK) {
	               /* partition starts right behind VTOC */
	               p->fspace_trk = anc->fspace_trk - p->len_trk;
		       anc->fspace_trk = 0;
		}
		else {
	               /* there is some space between VTOC and partition */

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


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

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

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

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

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

	p->used       = 0x00;
	p->len_trk    = 0x0;
	p->start_trk  = 0x0;
	p->end_trk    = 0x0;
	p->fspace_trk = 0x0;
	bzero(p->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 *p;
	extent_t ext;
	long start, stop;
	int i;

	if ((p = 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, p) != 0)
	        return;
	vtoc_init_format1_label(anc->vlabel->volid, anc->blksize, &ext, p->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 k;
	long start, stop;
	partition_info_t *p = anc->first;

	fdasd_list_partition_table(anc);

	while (!isdigit(k = read_char("\ndelete partition with id "
				      "(use 0 to exit): ")))
		 printf("Seriously, do you think '%c' is a partition id!\n",k);

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

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

	setpos(anc, k-1, -1);
	for (i=1; i<k; i++) p=p->next;

	start = p->f1->DS1EXT1.llimit.cc * geo.heads + 
		p->f1->DS1EXT1.llimit.hh;
	stop  = p->f1->DS1EXT1.ulimit.cc * geo.heads + 
		p->f1->DS1EXT1.ulimit.hh;

	fdasd_dequeue_old_partition (anc, p, k-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   *v = anc->vlabel;
	partition_info_t *p = anc->first;
	cchh_t llimit,ulimit;
	cchhb_t hf1;
	extent_t ext;
	char str[7];
	u_int16_t c, h;
	int i;

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

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

	if (options.volid == NULL) 
		sprintf(str, "0X%04x", anc->devno);
	else {
		strcpy(str, options.volid);
		fdasd_check_volser(str, anc->devno);
	}

	vtoc_volume_label_set_volser(v, str);

	vtoc_set_cchhb(&v->vtoc, 0x0000, 0x0001, 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)
		c = anc->f4->DS4DEVCT.DS4DSCYL - (u_int16_t) anc->f4->DS4DEVAC;
	else
		c = anc->f4->DS4DEVCT.DS4DSCYL;

	h = anc->f4->DS4DEVCT.DS4DSTRK;

	p->used       = 0x01;
        p->fspace_trk = 0;
	p->len_trk    = h * c - FIRST_USABLE_TRK;
	p->start_trk  = FIRST_USABLE_TRK;
	p->end_trk    = h * c - 1;

	vtoc_set_cchh(&llimit, 0, FIRST_USABLE_TRK);
	vtoc_set_cchh(&ulimit, c - 1, h - 1);

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

	vtoc_init_format1_label(anc->vlabel->volid, anc->blksize, &ext, p->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 *v = anc->vlabel;
	partition_info_t *p = anc->first;
	char str[7];
	int i;
	cchh_t llimit,ulimit;
	extent_t ext;
	cchhb_t hf1;

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

	if (options.volid == NULL) 
		sprintf(str, "0X%04x", anc->devno);
	else {
		strcpy(str, options.volid);
		fdasd_check_volser(str, anc->devno);
	}

	vtoc_volume_label_set_volser(v, str);

	vtoc_set_cchhb(&v->vtoc, 0x0000, 0x0001, 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) {
		unsigned long start = FIRST_USABLE_TRK;
		unsigned long 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 (p->used != 0x01)
			continue;

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

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

		vtoc_init_format1_label(anc->vlabel->volid,
					anc->blksize, &ext, p->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 (p->fspace_trk != 0) {
			unsigned long start = p->end_trk + 1;
			unsigned long stop  = start + p->fspace_trk -1;
				
			vtoc_set_freespace(anc->f4, anc->f5, anc->f7, '+', 
					   anc->verbose, start, stop,
					   geo.cylinders, geo.heads);
		}
	} while ((p = p->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[LINE_LENGTH];

	if ((anc->vtoc_changed)||(anc->vlabel_changed)) {
		sprintf(str,"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_options (&anchor, &options);
	fdasd_get_geometry(&anchor);
	fdasd_check_api_version(&anchor);	
	fdasd_check_ro_disks(&anchor);

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

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

	if (anchor.config_specified) {
		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_volid) {
		fdasd_print_volid(&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;
}










