root/sbin/pdisk/pdisk.c
/*      $OpenBSD: pdisk.c,v 1.87 2016/05/28 22:26:13 tb Exp $   */

/*
 * 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.
 */

#include <sys/param.h>          /* DEV_BSIZE */
#include <sys/dkio.h>
#include <sys/disklabel.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/stat.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>

#include "partition_map.h"
#include "io.h"
#include "dump.h"

int     lflag;  /* list the device */
int     rflag;  /* open device read Only */

static int      first_get = 1;

void    do_dump_map(struct partition_map *, int);
void    do_change_map_size(struct partition_map *);
void    do_create_partition(struct partition_map *, int);
void    do_delete_partition(struct partition_map *);
void    do_display_entry(struct partition_map *);
void    do_rename_partition(struct partition_map *);
void    do_change_type(struct partition_map *);
void    do_reorder(struct partition_map *);
void    do_write_partition_map(struct partition_map *);
void    edit(struct partition_map **);
int     get_base_argument(long *, struct partition_map *);
int     get_size_argument(long *, struct partition_map *);

__dead static void usage(void);

int
main(int argc, char **argv)
{
        struct disklabel dl;
        struct stat st;
        struct partition_map *map;
        int c, fd, oflags;

        oflags = O_RDWR;
        while ((c = getopt(argc, argv, "lr")) != -1) {
                switch (c) {
                case 'l':
                        lflag = 1;
                        oflags = O_RDONLY;
                        break;
                case 'r':
                        rflag = 1;
                        oflags = O_RDONLY;
                        break;
                default:
                        usage();
                        break;
                }
        }

        argc -= optind;
        argv += optind;

        if (argc != 1)
                usage();

        fd = opendev(*argv, oflags, OPENDEV_PART, NULL);
        if (fd == -1)
                err(1, "can't open file '%s'", *argv);

        if (fstat(fd, &st) == -1)
                err(1, "can't fstat %s", *argv);
        if (!S_ISCHR(st.st_mode))
                errx(1, "%s is not a character device", *argv);

        if (ioctl(fd, DIOCGPDINFO, &dl) == -1)
                err(1, "can't get disklabel for %s", *argv);
        if (dl.d_secsize != DEV_BSIZE)
                errx(1, "disk sector size (%d) != 512\n", dl.d_secsize);

        if (pledge("stdio", NULL) == -1)
                err(1, "pledge");

        map = open_partition_map(fd, *argv, DL_GETDSIZE(&dl), dl.d_secsize);
        if (map != NULL) {
                if (lflag)
                        dump_partition_map(map);
                else
                        edit(&map);
        }

        free_partition_map(map);
        close(fd);

        return 0;
}

/*
 * Edit the file
 */
void
edit(struct partition_map **mapp)
{
        struct partition_map *map = *mapp;
        struct partition_map *oldmap;
        int command;

        printf("Edit %s -\n", map->name);

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

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

                        /* fall through */
                case 'h':
                        printf("Commands are:\n"
                            "  ?    verbose command help\n"
                            "  C    create a partition of a specified type\n"
                            "  c    create an OpenBSD partition\n"
                            "  d    delete a partition\n"
                            "  f    full display of a partition\n"
                            "  h    command help\n"
                            "  i    (re)initialize the partition map\n"
                            "  n    (re)name a partition\n"
                            "  P    show the partition map's data structures\n"
                            "  p    print the partition map\n"
                            "  q    quit editing\n"
                            "  r    reorder (swap) disk positions of two "
                                "entries in the partition map\n"
                            "  s    change the size of the partition map\n"
                            "  t    change the type of a partition\n"
                            "  w    write the partition map to disk\n");
                        break;
                case 'P':
                        do_dump_map(map, 1);
                        break;
                case 'p':
                        do_dump_map(map, 0);
                        break;
                case 'q':
                        if (map->changed) {
                                if (get_okay("Discard changes? [n/y]: ", 0) !=
                                    1) {
                                        break;
                                }
                        }
                        flush_to_newline(1);
                        return;
                case 'i':
                        if (get_okay("Discard current map? [n/y]: ", 0) == 1) {
                                oldmap = map;
                                map = create_partition_map(oldmap->fd,
                                    oldmap->name, oldmap->media_size,
                                    oldmap->sbBlkSize);
                                if (map == NULL)
                                        break;
                                *mapp = map;
                                free_partition_map(oldmap);
                        }
                        break;
                case 'C':
                        do_create_partition(map, 1);
                        break;
                case 'c':
                        do_create_partition(map, 0);
                        break;
                case 'n':
                        do_rename_partition(map);
                        break;
                case 'd':
                        do_delete_partition(map);
                        break;
                case 'r':
                        do_reorder(map);
                        break;
                case 's':
                        do_change_map_size(map);
                        break;
                case 't':
                        do_change_type(map);
                        break;
                case 'w':
                        do_write_partition_map(map);
                        break;
                case 'f':
                        do_display_entry(map);
                        break;
                default:
                        bad_input("No such command (%c)", command);
                        break;
                }
        }
}

void
do_create_partition(struct partition_map *map, int get_type)
{
        long base, length;
        char *name = NULL;
        char *type = NULL;

        if (get_base_argument(&base, map) == 0)
                return;
        if (get_size_argument(&length, map) == 0)
                return;

        name = get_dpistr_argument("Name of partition: ");
        if (name == NULL) {
                bad_input("Bad name");
                goto out;
        }

        if (get_type == 0)
                type = strdup(kUnixType);
        else
                type = get_dpistr_argument("Type of partition: ");
        if (type == NULL) {
                bad_input("Bad type");
                goto out;
        }

        if (strncasecmp(type, kFreeType, DPISTRLEN) == 0) {
                bad_input("Can't create a partition with the Free type");
                goto out;
        }
        if (strncasecmp(type, kMapType, DPISTRLEN) == 0) {
                bad_input("Can't create a partition with the Map type");
                goto out;
        }

        add_partition_to_map(name, type, base, length, map);

out:
        free(type);
        free(name);

        return;
}

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

        if (get_number_argument("First block: ", number) == 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->dpme_pblock_start;
                        }
                }
        }
        return result;
}


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

        if (get_number_argument("Length in blocks: ", number) == 0) {
                bad_input("Bad length");
        } else {
                multiple = get_multiplier(map->sbBlkSize);
                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->dpme_pblocks;
                                result = 1;
                        }
                } else {
                        result = 1;
                }
        }
        return result;
}


void
do_rename_partition(struct partition_map *map)
{
        struct entry *entry;
        char *name;
        long ix;

        if (get_number_argument("Partition number: ", &ix) == 0) {
                bad_input("Bad partition number");
                return;
        }
        entry = find_entry_by_disk_address(ix, map);
        if (entry == NULL) {
                printf("No such partition\n");
                return;
        }

        printf("Existing partition name ``%s''.\n", entry->dpme_name);
        name = get_dpistr_argument("New name of partition: ");
        if (name == NULL) {
                bad_input("Bad name");
                return;
        }

        /*
         * Since dpme_name is supposed to be NUL-filled, make sure
         * current contents are zapped before copying in new name!
         */
        memset(entry->dpme_name, 0, sizeof(entry->dpme_name));
        strlcpy(entry->dpme_name, name, sizeof(entry->dpme_name));
        map->changed = 1;

        free(name);
        return;
}

void
do_change_type(struct partition_map *map)
{
        struct entry *entry;
        char *type;
        long ix;

        if (get_number_argument("Partition number: ", &ix) == 0) {
                bad_input("Bad partition number");
                return;
        }
        entry = find_entry_by_disk_address(ix, map);
        if (entry == NULL) {
                printf("No such partition\n");
                return;
        }

        printf("Existing partition type ``%s''.\n", entry->dpme_type);
        type = get_dpistr_argument("New type of partition: ");
        if (type == NULL) {
                bad_input("Bad type");
                return;
        }

        /*
         * Since dpme_type is supposed to be NUL-filled, make sure
         * current contents are zapped before copying in new type!
         */
        memset(entry->dpme_type, 0, sizeof(entry->dpme_type));
        strncpy(entry->dpme_type, type, sizeof(entry->dpme_type));
        map->changed = 1;

        free(type);
        return;
}


void
do_delete_partition(struct partition_map *map)
{
        struct entry *cur;
        long ix;

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

        cur = find_entry_by_disk_address(ix, map);
        if (cur == NULL)
                printf("No such partition\n");
        else
                delete_partition_from_map(cur);
}


void
do_reorder(struct partition_map *map)
{
        long ix, old_index;

        if (get_number_argument("Partition number: ", &old_index) == 0) {
                bad_input("Bad partition number");
                return;
        }
        if (get_number_argument("New number: ", &ix) == 0) {
                bad_input("Bad partition number");
                return;
        }
        move_entry_in_map(old_index, ix, map);
}


void
do_write_partition_map(struct partition_map *map)
{
        if (map->changed == 0) {
                bad_input("The map has not been changed.");
                return;
        }
        if (rflag) {
                bad_input("The map is not writable.");
                return;
        }
        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;
}


void
do_change_map_size(struct partition_map *map)
{
        long size;

        if (get_number_argument("New size: ", &size) == 0) {
                bad_input("Bad size");
                return;
        }
        resize_map(size, map);
}


void
do_display_entry(struct partition_map *map)
{
        long number;

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

void
do_dump_map(struct partition_map *map, int verbose)
{
        if (verbose)
                show_data_structures(map);
        else
                dump_partition_map(map);
}

__dead static void
usage(void)
{
        extern char *__progname;

        fprintf(stderr, "usage: %s [-lr] disk\n", __progname);

        exit(1);
}