//
// pdisk - an editor for Apple format partition tables
//
// Written by Eryk Vershen
//
// Still under development (as of 15 January 1998)
//

/*
 * Copyright 1996,1997,1998 by Apple Computer, Inc.
 *              All Rights Reserved 
 *  
 * Permission to use, copy, modify, and distribute this software and 
 * its documentation for any purpose and without fee is hereby granted, 
 * provided that the above copyright notice appears in all copies and 
 * that both the copyright notice and this permission notice appear in 
 * supporting documentation. 
 *  
 * APPLE COMPUTER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE 
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE. 
 *  
 * IN NO EVENT SHALL APPLE COMPUTER BE LIABLE FOR ANY SPECIAL, INDIRECT, OR 
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT, 
 * NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 */

// for printf()
#include <stdio.h>

#if defined(__linux__) || defined(__unix__)
#include <getopt.h>
#endif
#ifdef __linux__
#include <malloc.h>
#else
// for malloc() & free()
#include <stdlib.h>
#if !defined(__unix__)
// for SIOUXsettings
#include <SIOUX.h>
#endif
#endif

#ifdef __unix__
#include <unistd.h>
#endif

// for strncpy() & strlen()
#include <string.h>
// for O_RDONLY
#include <fcntl.h>
// for errno
#include <errno.h>

#ifdef __linux__
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <linux/hdreg.h>
#endif

#include "pdisk.h"
#include "io.h"
#include "partition_map.h"
#include "pathname.h"
#include "hfs_misc.h"
#include "errors.h"
#include "dump.h"
#include "validate.h"
#include "version.h"
#include "util.h"
#include "cmdline.h"


//
// Defines
//
#define ARGV_CHUNK 5
#define CFLAG_DEFAULT	0
#define DFLAG_DEFAULT	0
#define HFLAG_DEFAULT	0
#define INTERACT_DEFAULT	0
#define LFLAG_DEFAULT	0
#define RFLAG_DEFAULT	0
#define VFLAG_DEFAULT	0


//
// Types
//


//
// Global Constants
//
enum getopt_values {
    kLongOption = 0,
    kBadOption = '?',
    kOptionArg = 1000,
    kListOption = 1001,
    kLogicalOption = 1002
};


//
// Global Variables
//
int lflag = LFLAG_DEFAULT;	/* list the device */
char *lfile;	/* list */
int vflag = VFLAG_DEFAULT;	/* show version */
int hflag = HFLAG_DEFAULT;	/* show help */
int dflag = DFLAG_DEFAULT;	/* turn on debugging commands and printout */
int rflag = RFLAG_DEFAULT;	/* open device read Only */
int interactive = INTERACT_DEFAULT;
int cflag = CFLAG_DEFAULT;	/* compute device size */

static int first_get = 1;


//
// Forward declarations
//
void do_change_map_size(partition_map_header *map);
void do_update_dpme(partition_map *entry);
void do_create_partition(partition_map_header *map, int get_type);
void do_delete_partition(partition_map_header *map);
void do_display_block(partition_map_header *map, char *alt_name);
void do_display_entry(partition_map_header *map);
void do_examine_patch_partition(partition_map_header *map);
int do_expert(partition_map_header *map, char *name);
void do_rename_partition(partition_map_header *map);
void do_change_type(partition_map_header *map);
void do_reorder(partition_map_header *map);
void do_write_partition_map(partition_map_header *map);
void edit(char *name, int ask_logical_size);
int get_base_argument(long *number, partition_map_header *map);
int get_command_line(int *argc, char ***argv);
int get_size_argument(long *number, partition_map_header *map);
int get_options(int argc, char **argv);
void interact();
void print_edit_notes();
void print_expert_notes();
void print_top_notes();


//
// Routines
//
int
main(int argc, char **argv)
{
#if defined(__linux__) || defined(__unix__)
    int name_index;
#else
    SIOUXSettings.rows = 100;
    printf("This app uses the SIOUX console library\n");
    printf("Choose 'Quit' from the file menu to quit.\n\n");
    printf("Use fake disk names (/dev/scsi<bus>.<id>; i.e. /dev/scsi0.1, /dev/scsi1.3, etc.).\n\n");
 
    SIOUXSettings.autocloseonquit = 0;	/* Do we close the SIOUX window on program termination ... */
    SIOUXSettings.asktosaveonclose = 0;	/* Do we offer to save on a close ... */
#endif

    init_program_name(argv);

    if (sizeof(DPME) != PBLOCK_SIZE) {
	fatal(-1, "Size of partion map entry (%d) "
		"is not equal to block size (%d)\n",
		sizeof(DPME), PBLOCK_SIZE);
    }
    if (sizeof(Block0) != PBLOCK_SIZE) {
	fatal(-1, "Size of block zero structure (%d) "
		"is not equal to block size (%d)\n",
		sizeof(Block0), PBLOCK_SIZE);
    }
    if (strcmp(VERSION, get_version_string()) != 0) {
	fatal(-1, "Version string static form (%s) does not match dynamic form (%s)\n",
		VERSION, get_version_string());
    }

#if defined(__linux__) || defined(__unix__)
    if (argc > 1) {
	do_command_line(argc - 1, argv + 1);
    }

    name_index = get_options(argc, argv);

    if (vflag) {
	printf("version " VERSION " (" RELEASE_DATE ")\n");
    }
    if (hflag) {
 	do_help();
    } else if (interactive) {
	interact();
    } else if (lflag) {
	if (lfile != NULL) {
	    dump(lfile);
	} else if (name_index < argc) {
	    while (name_index < argc) {
		dump(argv[name_index++]);
	    }
	} else {
#if defined(__linux__) || defined(__APPLE__)
	    list_all_disks();
#else
	    usage("no device argument");
	    do_help();
#endif
	}
    } else if (name_index < argc) {
	while (name_index < argc) {
	    edit(argv[name_index++], 0);
	}
    } else if (!vflag) {
#ifdef __APPLE__
	interactive = 1;
	interact();
#else
	usage("no device argument");
 	do_help();
#endif
    }
    return 0;
#else
    interactive = 1;

    interact();

    SIOUXSettings.autocloseonquit = 1;
    //printf("Processing stopped: Choose 'Quit' from the file menu to quit.\n\n");
    exit(0);
#endif
}


void
print_top_notes()
{
#if !defined(__linux__) && !defined(__unix__)
    printf("Notes:\n");
    printf("  Disks have fake names of the form /dev/scsi<bus>.<id>\n");
    printf("  For example, /dev/scsi0.1, /dev/scsi1.3, and so on.\n");
    printf("  Linux style names are also allowed (i.e /dev/sda or /dev/hda).\n");
    printf("  Due to some technical problems these names may not match\n");
    printf("  the 'real' linux names.\n");
    printf("\n");
#endif
}


void
interact()
{
    char *name;
    int command;
    int ask_logical_size;

    while (get_command("Top level command (? for help): ", first_get, &command)) {
	first_get = 0;
	ask_logical_size = 0;

	switch (command) {
	case '?':
	    print_top_notes();
	    // fall through
	case 'H':
	case 'h':
	    printf("Commands are:\n");
	    printf("  h    print help\n");
	    printf("  v    print the version number and release date\n");
	    printf("  l    list device's map\n");
#if defined(__linux__) || defined(__APPLE__)
	    printf("  L    list all devices' maps\n");
#endif
	    printf("  e    edit device's map\n");
	    printf("  E    (edit map with specified block size)\n");
	    printf("  r    toggle readonly flag\n");
	    printf("  f    toggle show filesystem name flag\n");
	    if (dflag) {
		printf("  a    toggle abbreviate flag\n");
		printf("  p    toggle physical flag\n");
		printf("  c    toggle compute size flag\n");
		printf("  d    toggle debug flag\n");
		printf("  x    examine block n of device\n");
	    }
	    printf("  q    quit the program\n");
	    break;
	case 'Q':
	case 'q':
	    return;
	    break;
	case 'V':
	case 'v':
	    printf("version " VERSION " (" RELEASE_DATE ")\n");
	    break;
#if defined(__linux__) || defined(__APPLE__)
	case 'L':
	    list_all_disks();
	    break;
#endif
	case 'l':
	    if (get_string_argument("Name of device: ", &name, 1) == 0) {
		bad_input("Bad name");
		break;
	    }
	    dump(name);
	    free(name);
	    break;
	case 'E':
	    ask_logical_size = 1;
	case 'e':
	    if (get_string_argument("Name of device: ", &name, 1) == 0) {
		bad_input("Bad name");
		break;
	    }
	    edit(name, ask_logical_size);
	    free(name);
	    break;
	case 'R':
	case 'r':
	    if (rflag) {
		rflag = 0;
	    } else {
		rflag = 1;
	    }
	    printf("Now in %s mode.\n", (rflag)?"readonly":"read/write");
	    break;
	case 'F':
	case 'f':
	    if (fflag) {
		fflag = 0;
	    } else {
		fflag = 1;
	    }
	    printf("Now in show %s name mode.\n", (fflag)?"filesystem":"partition");
	    break;
	case 'A':
	case 'a':
	    if (dflag) {
		if (aflag) {
		    aflag = 0;
		} else {
		    aflag = 1;
		}
		printf("Now in %s mode.\n", (aflag)?"abbreviate":"full type");
	    } else {
	    	goto do_error;
	    }
	    break;
	case 'P':
	case 'p':
	    if (dflag) {
		if (pflag) {
		    pflag = 0;
		} else {
		    pflag = 1;
		}
		printf("Now in %s mode.\n", (pflag)?"physical":"logical");
	    } else {
	    	goto do_error;
	    }
	    break;
	case 'D':
	case 'd':
	    if (dflag) {
		dflag = 0;
	    } else {
		dflag = 1;
	    }
	    printf("Now in %s mode.\n", (dflag)?"debug":"normal");
	    break;
	case 'C':
	case 'c':
	    if (dflag) {
		if (cflag) {
		    cflag = 0;
		} else {
		    cflag = 1;
		}
		printf("Now in %s device size mode.\n", (cflag)?"always compute":"use existing");
	    } else {
	    	goto do_error;
	    }
	    break;
	case 'X':
	case 'x':
	    if (dflag) {
		do_display_block(0, 0);
	    } else {
	    	goto do_error;
	    }
	    break;
	default:
	do_error:
	    bad_input("No such command (%c)", command);
	    break;
	}
    }
}


#if defined(__linux__) || defined(__unix__)
int
get_options(int argc, char **argv)
{
    int c;
#if defined(__linux__) || defined(__unix__)
    static struct option long_options[] =
    {
	// name		has_arg			&flag	val
	{"help",	no_argument,		0,	'h'},
	{"list",	optional_argument,	0,	kListOption},
	{"version",	no_argument,		0,	'v'},
	{"debug",	no_argument,		0,	'd'},
	{"readonly",	no_argument,		0,	'r'},
	{"abbr",	no_argument,		0,	'a'},
	{"fname",	no_argument,		0,	'f'},
	{"logical",	no_argument,		0,	kLogicalOption},
	{"interactive",	no_argument,		0,	'i'},
	{"compute_size", no_argument,		0,	'c'},
	{0, 0, 0, 0}
    };
    int option_index = 0;
#else
    extern int opterr;		/* who does error messages */
    extern int optopt;		/* char that caused the error */
    int getopt_error;		/* getopt choked */
#endif
    extern int optind;		/* next argv index */
    extern char *optarg;	/* pointer to argument */
    int flag = 0;

    lflag = LFLAG_DEFAULT;
    lfile = NULL;
    vflag = VFLAG_DEFAULT;
    hflag = HFLAG_DEFAULT;
    dflag = DFLAG_DEFAULT;
    rflag = RFLAG_DEFAULT;
    aflag = AFLAG_DEFAULT;
    pflag = PFLAG_DEFAULT;
    interactive = INTERACT_DEFAULT;
    cflag = CFLAG_DEFAULT;
    fflag = FFLAG_DEFAULT;

#if defined(__linux__) || defined(__unix__)
    optind = 0;	// reset option scanner logic
    while ((c = getopt_long(argc, argv, "hlvdraLicf", long_options,
	    &option_index)) >= 0)
#else
    opterr = 0;			/* tell getopt to be quiet */
    while ((c = getopt(argc, argv, "hlvdraLicf")) != EOF)
#endif
	{
#if !defined(__linux__) && !defined(__unix__)
	if (c == '?') {
	    getopt_error = 1;
	    c = optopt;
	} else {
	    getopt_error = 0;
	}
#endif
	switch (c) {
	case kLongOption:
	    // option_index would be used here
	    break;
	case 'h':
	    hflag = (HFLAG_DEFAULT)?0:1;
	    break;
	case kListOption:
	    if (optarg != NULL) {
		lfile = optarg;
	    }
	    // fall through
	case 'l':
	    lflag = (LFLAG_DEFAULT)?0:1;
	    break;
	case 'v':
	    vflag = (VFLAG_DEFAULT)?0:1;
	    break;
	case 'd':
	    dflag = (DFLAG_DEFAULT)?0:1;
	    break;
	case 'c':
	    cflag = (CFLAG_DEFAULT)?0:1;
	    break;
	case 'r':
	    rflag = (RFLAG_DEFAULT)?0:1;
	    break;
	case 'f':
	    fflag = (FFLAG_DEFAULT)?0:1;
	    break;
	case 'i':
	    interactive = (INTERACT_DEFAULT)?0:1;
	    break;
	case 'a':
	    aflag = (AFLAG_DEFAULT)?0:1;
	    break;
	case 'L':
	case kLogicalOption:
	    pflag = (PFLAG_DEFAULT)?0:1;
	    break;
	case kBadOption:
	default:
	    flag = 1;
	    break;
	}
    }
    if (flag) {
	usage("bad arguments");
    }
    return optind;
}
#endif


void
print_edit_notes()
{
    printf("Notes:\n");
    printf("  Base and length fields are blocks, which vary in size between media.\n");
    printf("  The base field can be <nth>p; i.e. use the base of the nth partition.\n");
    printf("  The length field can be a length followed by k, m, g or t to indicate\n");
    printf("  kilo, mega, giga, or tera bytes; also the length can be <nth>p; i.e. use\n");
    printf("  the length of the nth partition.\n");
    printf("  The name of a partition is descriptive text.\n");
    printf("\n");
}


//
// Edit the file
//
void
edit(char *name, int ask_logical_size)
{
    partition_map_header *map;
    int command;
    int order;
    int get_type;
    int valid_file;

    map = open_partition_map(name, &valid_file, ask_logical_size, (rflag)?O_RDONLY:O_RDWR);
    if (!valid_file) {
    	return;
    }

    printf("Edit %s -\n", name);
    
#if 0 /* this check is not found in linux fdisk-0.1 */
    if (map != NULL && map->blocks_in_map > MAX_LINUX_MAP) {
	error(-1, "Map contains more than %d blocks - Linux may have trouble", MAX_LINUX_MAP);
    }
#endif

    while (get_command("Command (? for help): ", first_get, &command)) {
	first_get = 0;
	order = 1;
	get_type = 0;

	switch (command) {
	case '?':
	    print_edit_notes();
	    // fall through
	case 'H':
	case 'h':
	    printf("Commands are:\n");
	    printf("  C    (create with type also specified)\n");
	    printf("  c    create new partition (standard type)\n");
	    printf("  d    delete a partition\n");
	    printf("  h    help\n");
	    printf("  i    initialize partition map\n");
	    printf("  n    (re)name a partition\n");
	    printf("  P    (print ordered by base address)\n");
	    printf("  p    print the partition table\n");
	    printf("  q    quit editing\n");
	    printf("  r    reorder partition entry in map\n");
	    printf("  s    change size of partition map\n");
	    printf("  t    change a partition's type\n");
	    if (!rflag) {
		printf("  w    write the partition table\n");
	    }
	    if (dflag) {
		printf("  x    extra extensions for experts\n");
	    }
	    break;
	case 'P':
	    order = 0;
	    // fall through
	case 'p':
	    dump_partition_map(map, order);
	    break;
	case 'Q':
	case 'q':
	    if (map && map->changed) {
		if (get_okay("Discard changes? [n/y]: ", 0) != 1) {
		    break;
		}
	    }
	    flush_to_newline(1);
	    goto finis;
	    break;
	case 'I':
	case 'i':
	    map = init_partition_map(name, map, (rflag)?O_RDONLY:O_RDWR);
	    break;
	case 'C':
	    get_type = 1;
	    // fall through
	case 'c':
	    do_create_partition(map, get_type);
	    break;
	case 'N':
	case 'n':
	    do_rename_partition(map);
	    break;
	case 'D':
	case 'd':
	    do_delete_partition(map);
	    break;
	case 'R':
	case 'r':
	    do_reorder(map);
	    break;
	case 'S':
	case 's':
	    do_change_map_size(map);
	    break;
	case 'T':
	case 't':
	    do_change_type(map);
	    break;
	case 'X':
	case 'x':
	    if (!dflag) {
		goto do_error;
	    } else if (do_expert(map, name)) {
		flush_to_newline(1);
		goto finis;
	    }
	    break;
	case 'W':
	case 'w':
	    if (!rflag) {
		do_write_partition_map(map);
	    } else {
	    	goto do_error;
	    }
	    break;
	default:
	do_error:
	    bad_input("No such command (%c)", command);
	    break;
	}
    }
finis:

    close_partition_map(map);
}

void
do_update_dpme(partition_map *entry)
{
    int slice = 0;
    if (!entry) return;
    dpme_init_flags(entry->data);
    entry->HFS_name = get_HFS_name(entry, &entry->HFS_kind);
    if (istrncmp(entry->data->dpme_type, kUnixType, DPISTRLEN) == 0) {
	printf("Available partition slices for %s:\n",entry->data->dpme_type);
	printf("  a   root partition\n");
	printf("  b   swap partition\n");
	printf("  c   do not set any bzb bits\n");
	printf("  g   user partition\n");
	printf("Other lettered values will create user partitions\n");
	get_command("Select a slice for default bzb values: ",0,&slice);
    }
    bzb_init_slice((BZB *)entry->data->dpme_bzb,slice);
    entry->the_map->changed = 1;
}

void
do_create_partition(partition_map_header *map, int get_type)
{
    long base;
    long length;
    char *name = 0;
    char *type_name = 0;

    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }
    if (!rflag && map->writable == 0) {
	printf("The map is not writable.\n");
    }
// XXX add help feature (i.e. '?' in any argument routine prints help string)
    if (get_base_argument(&base, map) == 0) {
	return;
    }
    if (get_size_argument(&length, map) == 0) {
	return;
    }

    if (get_string_argument("Name of partition: ", &name, 1) == 0) {
	bad_input("Bad name");
	return;
    }
    if (get_type == 0) {
#ifdef __APPLE__
	add_partition_to_map(name, kHFSType, base, length, map);
#else
	add_partition_to_map(name, kUnixType, base, length, map);
#endif
#if 0 /* this check is not found in linux fdisk-0.1 */
	if (map->blocks_in_map > MAX_LINUX_MAP) {
	    error(-1, "Map contains more than %d blocks - Linux may have trouble", MAX_LINUX_MAP);
	}
	goto xit1;
#endif
    } else if (get_string_argument("Type of partition: ", &type_name, 1) == 0) {
	bad_input("Bad type");
	goto xit1;
    } else {
	if (istrncmp(type_name, kFreeType, DPISTRLEN) == 0) {
	    bad_input("Can't create a partition with the Free type");
	    goto xit2;
	}
	if (istrncmp(type_name, kMapType, DPISTRLEN) == 0) {
	    bad_input("Can't create a partition with the Map type");
	    goto xit2;
	}
	add_partition_to_map(name, type_name, base, length, map);
#if 0 /* this check is not found in linux fdisk-0.1 */
	if (map->blocks_in_map > MAX_LINUX_MAP) {
	    error(-1, "Map contains more than %d blocks - Linux may have trouble", MAX_LINUX_MAP);
	}
#endif
    }
    do_update_dpme(find_entry_by_base(base,map));
xit2:
    if (type_name)
        free(type_name);
xit1:
    if (name)
        free(name);
    return;
}


int
get_base_argument(long *number, partition_map_header *map)
{
    partition_map * entry;
    int result = 0;

    if (get_number_argument("First block: ", number, kDefault) == 0) {
	bad_input("Bad block number");
    } else {
	result = 1;
	if (get_partition_modifier()) {
	    entry = find_entry_by_disk_address(*number, map);
	    if (entry == NULL) {
		bad_input("Bad partition number");
		result = 0;
	    } else {
		*number = entry->data->dpme_pblock_start;
	    }
	}
    }
    return result;
}


int
get_size_argument(long *number, partition_map_header *map)
{
    partition_map * entry;
    int result = 0;
    unsigned long multiple;

    if (get_number_argument("Length in blocks: ", number, kDefault) == 0) {
	bad_input("Bad length");
    } else {
	multiple = get_multiplier(map->logical_block);
	if (multiple == 0) {
	    bad_input("Bad multiplier");
	} else if (multiple != 1) {
	    *number *= multiple;
	    result = 1;
	} else if (get_partition_modifier()) {
	    entry = find_entry_by_disk_address(*number, map);
	    if (entry == NULL) {
		bad_input("Bad partition number");
	    } else {
		*number = entry->data->dpme_pblocks;
		result = 1;
	    }
	} else {
	    result = 1;
	}
    }
    return result;
}


void
do_rename_partition(partition_map_header *map)
{
    partition_map * entry;
    long index;
    char *name;

    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }
    if (!rflag && map->writable == 0) {
	printf("The map is not writable.\n");
    }
    if (get_number_argument("Partition number: ", &index, kDefault) == 0) {
	bad_input("Bad partition number");
	return;
    }
    if (get_string_argument("New name of partition: ", &name, 1) == 0) {
	bad_input("Bad name");
	return;
    }

	// find partition and change it
    entry = find_entry_by_disk_address(index, map);
    if (entry == NULL) {
	printf("No such partition\n");
    } else {
	// stuff name into partition map entry data
	strncpy(entry->data->dpme_name, name, DPISTRLEN);
	map->changed = 1;
    }
    free(name);
    return;
}

void
do_change_type(partition_map_header *map)
{
    partition_map * entry;
    long index;
    char *type = NULL;

    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }

    if (!rflag && map->writable == 0) {
	printf("The map is not writable.\n");
    }

    if (get_number_argument("Partition number: ", &index, kDefault) == 0) {
	bad_input("Bad partition number");
	return;
    }

    entry = find_entry_by_disk_address(index, map);

    if (entry == NULL ) {
        printf("No such partition\n");
	goto out;
    }

    printf("Existing partition type ``%s''.\n", entry->data->dpme_type);
    if (get_string_argument("New type of partition: ", &type, 1) == 0) {
	bad_input("Bad type");
	goto out;
    }

    strncpy(entry->data->dpme_type, type, DPISTRLEN);
    do_update_dpme(entry);
    map->changed = 1;

out:
    if (type)
        free(type);
    return;
}


void
do_delete_partition(partition_map_header *map)
{
    partition_map * cur;
    long index;

    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }
    if (!rflag && map->writable == 0) {
	printf("The map is not writable.\n");
    }
    if (get_number_argument("Partition number: ", &index, kDefault) == 0) {
	bad_input("Bad partition number");
	return;
    }

	// find partition and delete it
    cur = find_entry_by_disk_address(index, map);
    if (cur == NULL) {
	printf("No such partition\n");
    } else {
	delete_partition_from_map(cur);
    }
}


void
do_reorder(partition_map_header *map)
{
    long old_index;
    long index;

    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }
    if (!rflag && map->writable == 0) {
	printf("The map is not writable.\n");
    }
    if (get_number_argument("Partition number: ", &old_index, kDefault) == 0) {
	bad_input("Bad partition number");
	return;
    }
    if (get_number_argument("New number: ", &index, kDefault) == 0) {
	bad_input("Bad partition number");
	return;
    }

    move_entry_in_map(old_index, index, map);
}


void
do_write_partition_map(partition_map_header *map)
{
    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }
    if (map->changed == 0 && map->written == 0) {
	bad_input("The map has not been changed.");
	return;
    }
    if (map->writable == 0) {
	bad_input("The map is not writable.");
	return;
    }
#if 0 /* this check is not found in linux fdisk-0.1 */
    if (map->blocks_in_map > MAX_LINUX_MAP) {
	error(-1, "Map contains more than %d blocks - Linux may have trouble", MAX_LINUX_MAP);
    }
#endif
    printf("Writing the map destroys what was there before. ");
    if (get_okay("Is that okay? [n/y]: ", 0) != 1) {
	return;
    }

    write_partition_map(map);
    
    map->changed = 0;
    map->written = 1;

    // exit(0);
}


void
print_expert_notes()
{
    printf("Notes:\n");
    printf("  The expert commands are for low level and experimental features.\n");
    printf("  These commands are available only when debug mode is on.\n");
    printf("\n");
}


int
do_expert(partition_map_header *map, char *name)
{
    int command;
    int quit = 0;

    while (get_command("Expert command (? for help): ", first_get, &command)) {
	first_get = 0;

	switch (command) {
	case '?':
	    print_expert_notes();
	    // fall through
	case 'H':
	case 'h':
	    printf("Commands are:\n");
	    printf("  h    print help\n");
	    printf("  d    dump block n\n");
	    printf("  p    print the partition table\n");
	    if (dflag) {
		printf("  P    (show data structures  - debugging)\n");
	    }
	    printf("  f    full display of nth entry\n");
	    printf("  v    validate map\n");
	    printf("  e    examine patch partition\n");
	    printf("  q    return to main edit menu\n");
	    printf("  Q    quit editing\n");
	    break;
	case 'q':
	    flush_to_newline(1);
	    goto finis;
	    break;
	case 'Q':
	    if (map->changed) {
		if (get_okay("Discard changes? [n/y]: ", 0) != 1) {
		    break;
		}
	    }
	    quit = 1;
	    goto finis;
	    break;
	case 'P':
	    if (dflag) {
		show_data_structures(map);
		break;
	    }
	    // fall through
	case 'p':
	    dump_partition_map(map, 1);
	    break;
	case 'D':
	case 'd':
	    do_display_block(map, name);
	    break;
	case 'F':
	case 'f':
	    do_display_entry(map);
	    break;
	case 'V':
	case 'v':
	    validate_map(map);
	    break;
	case 'E':
	case 'e':
	    do_examine_patch_partition(map);
	    break;
	default:
	    bad_input("No such command (%c)", command);
	    break;
	}
    }
finis:
    return quit;
}

void
do_change_map_size(partition_map_header *map)
{
    long size;

    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }
    if (!rflag && map->writable == 0) {
	printf("The map is not writable.\n");
    }
    if (get_number_argument("New size: ", &size, kDefault) == 0) {
	bad_input("Bad size");
	return;
    }
    resize_map(size, map);
}


void
do_display_block(partition_map_header *map, char *alt_name)
{
    MEDIA m;
    long number;
    char *name;
    static unsigned char *display_block;
    static int display_g;
    int g;
    static long next_number = -1;

    if (map != NULL) {
    	name = 0;
	m = map->m;
	g = map->logical_block;
    } else {
	if (alt_name == 0) {
	    if (get_string_argument("Name of device: ", &name, 1) == 0) {
		bad_input("Bad name");
		return;
	    }
	} else {
	    name = strdup(alt_name);
	}
	m = open_pathname_as_media(name, O_RDONLY);
	if (m == 0) {
	    error(errno, "can't open file '%s'", name);
	    free(name);
	    return;
	}
	g = media_granularity(m);
	if (g < PBLOCK_SIZE) {
	    g = PBLOCK_SIZE;
	}
    }
    if (get_number_argument("Block number: ", &number, next_number) == 0) {
	bad_input("Bad block number");
	goto xit;
    }
    if (display_block == NULL || display_g < g) {
    	if (display_block != NULL) {
    	    free(display_block);
    	    display_g = 0;
	}
	display_block = (unsigned char *) malloc(g);
	if (display_block == NULL) {
	    error(errno, "can't allocate memory for display block buffer");
	    goto xit;
	}
	display_g = g;
    }
    if (read_media(m, ((long long)number) * g, g, (char *)display_block) != 0) {
	printf("block %ld -", number);
	dump_block((unsigned char*) display_block, g);
	next_number = number + 1;
    }

xit:
    if (name) {
	close_media(m);
	free(name);
    }
    return;
}


void
do_display_entry(partition_map_header *map)
{
    long number;

    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }
    if (get_number_argument("Partition number: ", &number, kDefault) == 0) {
	bad_input("Bad partition number");
	return;
    }
    if (number == 0) {
    	full_dump_block_zero(map);
    } else {
	full_dump_partition_entry(map, number);
    }
}


void
do_examine_patch_partition(partition_map_header *map)
{
    partition_map * entry;

    if (map == NULL) {
	bad_input("No partition map exists");
	return;
    }
    entry = find_entry_by_type(kPatchType, map);
    if (entry == NULL) {
	printf("No patch partition\n");
    } else {
	display_patches(entry);
    }
}
