/*
    parted - a frontend to libparted
    Copyright (C) 1999-2003, 2005-2014, 2019-2021 Free Software Foundation,
    Inc.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <config.h>
#include <stdbool.h>

#include "argmatch.h"
#include "closeout.h"
#include "configmake.h"
#include "version-etc.h"
#include "command.h"
#include "ui.h"
#include "progname.h"
#include "table.h"
#include "version.h"

#define AUTHORS \
  "<http://git.debian.org/?p=parted/parted.git;a=blob_plain;f=AUTHORS>"

/* The official name of this program (e.g., no `g' prefix).  */
#define PROGRAM_NAME "parted"

#define N_(String) String
#if ENABLE_NLS
#  include <libintl.h>
#  include <locale.h>
#  define _(String) dgettext (PACKAGE, String)
#else
#  define _(String) (String)
#endif /* ENABLE_NLS */

#include <parted/parted.h>
#include <parted/debug.h>

#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include "c-ctype.h"
#include "c-strcase.h"
#include "xalloc.h"

#ifdef ENABLE_MTRACE
#include <mcheck.h>
#endif

#include <getopt.h>

/* minimum amount of free space to leave, or maximum amount to gobble up */
#define MIN_FREESPACE           (1000 * 2)      /* 1000k */

static int MEGABYTE_SECTORS (PedDevice* dev)
{
        return PED_MEGABYTE_SIZE / dev->sector_size;
}

/* For long options that have no equivalent short option, use a
   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
enum
{
  PRETEND_INPUT_TTY = CHAR_MAX + 1,
};

enum
{
        ALIGNMENT_NONE = 2,
        ALIGNMENT_CYLINDER,
        ALIGNMENT_MINIMAL,
        ALIGNMENT_OPTIMAL
};

static char const *const align_args[] =
{
  "none",
  "cylinder",
  "minimal",
  "optimal",
  NULL
};

static int const align_types[] =
{
  ALIGNMENT_NONE,
  ALIGNMENT_CYLINDER,
  ALIGNMENT_MINIMAL,
  ALIGNMENT_OPTIMAL
};
ARGMATCH_VERIFY (align_args, align_types);

typedef struct {
        time_t  last_update;
        time_t  predicted_time_left;
} TimerContext;

static struct option const options[] = {
        /* name, has-arg, string-return-val, char-return-val */
        {"help",        0, NULL, 'h'},
        {"list",        0, NULL, 'l'},
        {"machine",     0, NULL, 'm'},
        {"script",      0, NULL, 's'},
        {"version",     0, NULL, 'v'},
        {"align",       required_argument, NULL, 'a'},
        {"-pretend-input-tty", 0, NULL, PRETEND_INPUT_TTY},
        {NULL,          0, NULL, 0}
};

static const char *const options_help [][2] = {
        {"help",        N_("displays this help message")},
        {"list",        N_("lists partition layout on all block devices")},
        {"machine",     N_("displays machine parseable output")},
        {"script",      N_("never prompts for user intervention")},
        {"version",     N_("displays the version")},
        {"align=[none|cyl|min|opt]", N_("alignment for new partitions")},
        {NULL,          NULL}
};

int     opt_script_mode = 0;
int     pretend_input_tty = 0;
int     opt_machine_mode = 0;
int     disk_is_modified = 0;
int     is_toggle_mode = 0;
int     alignment = ALIGNMENT_OPTIMAL;

static const char* number_msg = N_(
"NUMBER is the partition number used by Linux.  On MS-DOS disk labels, the "
"primary partitions number from 1 to 4, logical partitions from 5 onwards.\n");

static const char* label_type_msg_start = N_("LABEL-TYPE is one of: ");
static const char* flag_msg_start =   N_("FLAG is one of: ");
static const char* disk_flag_msg_start =   N_("FLAG is one of: ");
static const char* unit_msg_start =   N_("UNIT is one of: ");
static const char* min_or_opt_msg = N_("desired alignment: minimum or optimal");
static const char* part_type_msg =    N_("PART-TYPE is one of: primary, logical, "
                                   "extended\n");
static const char* fs_type_msg_start = N_("FS-TYPE is one of: ");
static const char* start_end_msg =    N_("START and END are disk locations, such as "
                "4GB or 10%.  Negative values count from the end of the disk.  "
                "For example, -1s specifies exactly the last sector.\n");
static const char* end_msg =          N_("END is disk location, such as "
                "4GB or 10%.  Negative value counts from the end of the disk.  "
                "For example, -1s specifies exactly the last sector.\n");
static const char* state_msg =        N_("STATE is one of: on, off\n");
static const char* device_msg =       N_("DEVICE is usually /dev/hda or /dev/sda\n");
static const char* name_msg =         N_("NAME is any word you want\n");

static const char* copyright_msg = N_(
"Copyright (C) 1998 - 2006 Free Software Foundation, Inc.\n"
"This program is free software, covered by the GNU General Public License.\n"
"\n"
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
"GNU General Public License for more details.\n\n");

static char* label_type_msg;
static char* flag_msg;
static char* disk_flag_msg;
static char* unit_msg;

static char* mkpart_fs_type_msg;

static Command* commands [256] = {NULL};
static PedTimer* g_timer;
static TimerContext timer_context;

static int _print_list ();
static void _done (PedDevice* dev, PedDisk *diskp);
static bool partition_align_check (PedDisk const *disk,
                        PedPartition const *part, enum AlignmentType a_type,
                        char **align_err);

static void
_timer_handler (PedTimer* timer, void* context)
{
        TimerContext*   tcontext = (TimerContext*) context;
        int             draw_this_time;

        if (opt_script_mode || !isatty(fileno(stdout)))
                return;

        if (tcontext->last_update != timer->now && timer->now > timer->start) {
                tcontext->predicted_time_left
                        = timer->predicted_end - timer->now;
                tcontext->last_update = timer->now;
                draw_this_time = 1;
        } else {
                draw_this_time = 0;
        }

        if (draw_this_time) {
                wipe_line ();

                if (timer->state_name)
                        printf ("%s... ", timer->state_name);
                printf (_("%0.f%%\t(time left %.2d:%.2d)"),
                        (double) (100.0f * timer->frac),
                        (int) (tcontext->predicted_time_left / 60),
                        (int) (tcontext->predicted_time_left % 60));

                fflush (stdout);
        }
}

static int
_partition_warn_busy (PedPartition* part)
{
        char* path;

        if (ped_partition_is_busy (part)) {
                path = ped_partition_get_path (part);
                if (ped_exception_throw (
                            PED_EXCEPTION_WARNING,
                            PED_EXCEPTION_YES_NO,
                            _("Partition %s is being used. Are you sure you " \
                              "want to continue?"),
                            path) != PED_EXCEPTION_YES)
                {
                        free (path);
                        return 0;
                }
                free (path);
        }
        return 1;
}

static int
_disk_warn_busy (PedDisk* disk)
{
        if (ped_device_is_busy (disk->dev))
                return ped_exception_throw (
                        (opt_script_mode
                         ? PED_EXCEPTION_ERROR
                         : PED_EXCEPTION_WARNING),
                        PED_EXCEPTION_IGNORE_CANCEL,
                        _("Partition(s) on %s are being used."),
                        disk->dev->path) == PED_EXCEPTION_IGNORE;

        return 1;
}

static int
_disk_warn_loss (PedDisk* disk)
{
        return ped_exception_throw (
                PED_EXCEPTION_WARNING,
                PED_EXCEPTION_YES_NO,
                _("The existing disk label on %s will be destroyed "
                  "and all data on this disk will be lost. Do you "
                  "want to continue?"),
                disk->dev->path) == PED_EXCEPTION_YES;
}

/* This function changes "sector" to "new_sector" if the new value lies
 * within the required range.
 */
static int
snap (PedSector* sector, PedSector new_sector, PedGeometry* range)
{
        PED_ASSERT (ped_geometry_test_sector_inside (range, *sector));
        if (!ped_geometry_test_sector_inside (range, new_sector))
                return 0;
        *sector = new_sector;
        return 1;
}

typedef enum {
        MOVE_NO         = 0,
        MOVE_STILL      = 1,
        MOVE_UP         = 2,
        MOVE_DOWN       = 4
} EMoves;

enum { /* Don't change these values */
        SECT_START      =  0,
        SECT_END        = -1
};

/* Find the preferred way to adjust the sector s inside range.
 * If a move isn't allowed or is out of range it can't be selected.
 * what contains SECT_START if the sector to adjust is a start sector
 * or SECT_END if it's an end one.
 * The preferred move is to the nearest allowed boundary of the part
 * partition (if at equal distance: to start if SECT_START or to end
 * if SECT_END).
 * The distance is returned in dist.
 */
static EMoves
prefer_snap (PedSector s, int what, PedGeometry* range, EMoves* allow,
             PedPartition* part, PedSector* dist)
{
        PedSector up_dist = -1, down_dist = -1;
        PedSector new_sect;
        EMoves move;

        PED_ASSERT (what == SECT_START || what == SECT_END);

        if (!(*allow & (MOVE_UP | MOVE_DOWN))) {
                *dist = 0;
                return MOVE_STILL;
        }

        if (*allow & MOVE_UP) {
                new_sect = part->geom.end + 1 + what;
                if (ped_geometry_test_sector_inside (range, new_sect))
                        up_dist = new_sect - s;
                else
                        *allow &= ~MOVE_UP;
        }

        if (*allow & MOVE_DOWN) {
                new_sect = part->geom.start + what;
                if (ped_geometry_test_sector_inside (range, new_sect))
                        down_dist = s - new_sect;
                else
                        *allow &= ~MOVE_DOWN;
        }

        move = MOVE_STILL;
        if ((*allow & MOVE_UP) && (*allow & MOVE_DOWN)) {
                if (down_dist < up_dist || (down_dist == up_dist
                                            && what == SECT_START) )
                        move = MOVE_DOWN;
                else if (up_dist < down_dist || (down_dist == up_dist
                                                 && what == SECT_END) )
                        move = MOVE_UP;
                else
                        PED_ASSERT (0);
        } else if (*allow & MOVE_UP)
                move = MOVE_UP;
        else if (*allow & MOVE_DOWN)
                move = MOVE_DOWN;

        *dist = ( move == MOVE_DOWN ? down_dist :
                ( move == MOVE_UP   ? up_dist   :
                  0 ) );
        return move;
}

/* Snaps a partition to nearby partition boundaries.  This is useful for
 * gobbling up small amounts of free space, and also for reinterpreting small
 * changes to a partition as non-changes (eg: perhaps the user only wanted to
 * resize the end of a partition).
 *      Note that this isn't the end of the story... this function is
 * always called before the constraint solver kicks in.  So you don't need to
 * worry too much about inadvertantly creating overlapping partitions, etc.
 */
static void
snap_to_boundaries (PedGeometry* new_geom, PedGeometry* old_geom,
                    PedDisk* disk,
                    PedGeometry* start_range, PedGeometry* end_range)
{
        PedPartition*   start_part;
        PedPartition*   end_part;
        PedSector       start = new_geom->start;
        PedSector       end = new_geom->end;
        PedSector       start_dist = -1, end_dist = -1;
        EMoves          start_allow, end_allow, start_want, end_want;
        int             adjacent;

        start_allow = end_allow = MOVE_STILL | MOVE_UP | MOVE_DOWN;

        start_part = ped_disk_get_partition_by_sector (disk, start);
        end_part = ped_disk_get_partition_by_sector (disk, end);
        adjacent = (start_part->geom.end + 1 == end_part->geom.start);

        /* If we can snap to old_geom, then we will... */
        /* and this will enforce the snapped positions  */
        if (old_geom) {
                if (snap (&start, old_geom->start, start_range))
                        start_allow = MOVE_STILL;
                if (snap (&end, old_geom->end, end_range))
                        end_allow = MOVE_STILL;
        }

        /* If start and end are on the same partition, we */
        /* don't allow them to cross. */
        if (start_part == end_part) {
                start_allow &= ~MOVE_UP;
                end_allow &= ~MOVE_DOWN;
        }

        /* Let's find our way */
        start_want = prefer_snap (start, SECT_START, start_range, &start_allow,
                                  start_part, &start_dist );
        end_want = prefer_snap (end, SECT_END, end_range, &end_allow,
                                end_part, &end_dist );

        PED_ASSERT (start_dist >= 0 && end_dist >= 0);

        /* If start and end are on adjacent partitions,    */
        /* and if they would prefer crossing, then refrain */
        /* the farthest to do so. */
        if (adjacent && start_want == MOVE_UP && end_want == MOVE_DOWN) {
                if (end_dist < start_dist) {
                        start_allow &= ~MOVE_UP;
                        start_want = prefer_snap (start, SECT_START,
                                                  start_range, &start_allow,
                                                  start_part, &start_dist );
                        PED_ASSERT (start_dist >= 0);
                } else {
                        end_allow &= ~MOVE_DOWN;
                        end_want = prefer_snap (end, SECT_END,
                                                end_range, &end_allow,
                                                end_part, &end_dist );
                        PED_ASSERT (end_dist >= 0);
                }
        }

        /* New positions */
        start = ( start_want == MOVE_DOWN ? start_part->geom.start :
                ( start_want == MOVE_UP ? start_part->geom.end + 1 :
                  start ) );
        end = ( end_want == MOVE_DOWN ? end_part->geom.start - 1 :
              ( end_want == MOVE_UP ? end_part->geom.end :
                end ) );
        PED_ASSERT (ped_geometry_test_sector_inside(start_range,start));
        PED_ASSERT (ped_geometry_test_sector_inside (end_range, end));
        PED_ASSERT (start <= end);
        ped_geometry_set (new_geom, start, end - start + 1);
}

static PedConstraint*
constraint_from_start_end (PedDevice* dev, PedGeometry* range_start,
                           PedGeometry* range_end)
{
        return ped_constraint_new (ped_alignment_any, ped_alignment_any,
                range_start, range_end, 1, dev->length);
}


static PedConstraint*
constraint_from_start_end_fixed_start (PedDevice* dev, PedSector start_sector,
                           PedGeometry* range_end)
{
        PedGeometry range_start;
        range_start.dev = dev;
        range_start.start = start_sector;
        range_start.end = start_sector;
        range_start.length = 1;

        return ped_constraint_new (ped_alignment_any, ped_alignment_any,
                &range_start, range_end, 1, dev->length);
}

void
help_on (char* topic)
{
        Command*        cmd;

        cmd = command_get (commands, topic);
        if (!cmd) return;

        command_print_help (cmd);
}

void
print_commands_help ()
{
        int             i;

        for (i=0; commands [i]; i++)
                command_print_summary (commands [i]);
}

void
print_options_help ()
{
        int             i;

        for (i=0; options_help [i][0]; i++) {
                printf ("  -%c, --%-25.25s %s\n",
                        options_help [i][0][0],
                        options_help [i][0],
                        _(options_help [i][1]));
        }
}

int
do_help (PedDevice** dev, PedDisk** diskp)
{
        if (command_line_get_word_count ()) {
                char*   word = command_line_pop_word ();
                if (word) {
                        help_on (word);
                        free (word);
                }
        } else {
                print_commands_help();
        }
        return 1;
}

static int
do_mklabel (PedDevice** dev, PedDisk** diskp)
{
        PedDisk*                disk;
        const PedDiskType*      type = NULL;

        if (*diskp)
                disk = *diskp;
        else {
                ped_exception_fetch_all ();
                disk = ped_disk_new (*dev);
                if (!disk) ped_exception_catch ();
                ped_exception_leave_all ();
        }

        if (!command_line_get_disk_type (_("New disk label type?"), &type))
                goto error;

        if (disk) {
                if (!_disk_warn_busy (disk))
                        goto error;
                if (!opt_script_mode && !_disk_warn_loss (disk))
                        goto error;
        }

        disk = ped_disk_new_fresh (*dev, type);
        if (!disk)
                goto error;

        if (!ped_disk_commit (disk))
                goto error_destroy_disk;

        if ((*dev)->type != PED_DEVICE_FILE)
                disk_is_modified = 1;
        if (*diskp)
                ped_disk_destroy (*diskp);
        *diskp = disk;
        return 1;

error_destroy_disk:
        ped_disk_destroy (disk);
        *diskp = 0;
error:
        return 0;
}

/* Strip blanks from the end of string STR, in place.  */
void _strip_trailing_spaces(char *str)
{
        if (!str)
                return;
        size_t i = strlen(str);
        while (i && c_isblank(str[--i]))
                str[i]='\0';
}

/* Return true, if str ends with [kMGTPEZY]iB, i.e. IEC units.  */
static bool
_string_ends_with_iec_unit(const char *str)
{
        /* 3 characters for the IEC unit and at least 1 digit */
        if (!str || strlen(str) < 4)
                return false;

        char const *p = str + strlen(str) - 3;
        return strchr ("kMGTPEZY", *p) && c_strcasecmp (p+1, "iB") == 0;
}

/* Return true if str ends with explicit unit identifier.
 * Do not parse the unit, just check if the unit is specified.
 * This function expects trailing spaces to be already stripped.
 */
static bool
_string_has_unit_suffix(const char *str)
{
        /* At least 1 digit and 1 character to meet the condition */
        if (!str || strlen(str) < 2)
                return false;

        if (!isdigit(str[strlen(str) - 1]))
                return true;

        return false;
}

/* If the selected unit is one of kiB, MiB, GiB or TiB and the partition is not
 * only 1 sector long, then adjust the end so that it is one sector before the
 * given position. Also adjust range_end accordingly. Thus next partition can
 * start immediately after this one.
 *
 * To be called after end sector is read from the user.
 *
 * https://lists.gnu.org/archive/html/bug-parted/2011-10/msg00009.html
 */
static void
_adjust_end_if_iec (PedSector* start, PedSector* end,
                    PedGeometry* range_end, char* end_input)
{
        PED_ASSERT(start);
        PED_ASSERT(end);
        PED_ASSERT(range_end);

        /* 1s partition - do not move the end */
        if (*start == *end)
                return;

        _strip_trailing_spaces(end_input);
        PedUnit unit = ped_unit_get_default();
        if (_string_ends_with_iec_unit(end_input) ||
            (!_string_has_unit_suffix(end_input) &&
             ((unit == PED_UNIT_KIBIBYTE) || (unit == PED_UNIT_MEBIBYTE) ||
              (unit == PED_UNIT_GIBIBYTE) || (unit == PED_UNIT_TEBIBYTE)))) {
                *end -= 1;
                range_end->start -= 1;
                range_end->end -= 1;
        }
}

static int
do_mkpart (PedDevice** dev, PedDisk** diskp)
{
        PedDisk*                 disk;
        PedPartition*            part;
        PedPartitionType         part_type;
        const PedFileSystemType* fs_type = ped_file_system_type_get ("ext2");
        PedSector                start = 0, end = 0;
        PedGeometry              *range_start = NULL, *range_end = NULL;
        PedConstraint*           user_constraint;
        PedConstraint*           dev_constraint;
        PedConstraint*           final_constraint;
        char*                    peek_word;
        char*                    part_name = NULL;
        char                     *start_usr = NULL, *end_usr = NULL;
        char                     *start_sol = NULL, *end_sol = NULL;

        if (*diskp)
                disk = *diskp;
        else {
                disk = ped_disk_new (*dev);
                *diskp = disk;
        }
        if (!disk)
                goto error;

        if (ped_disk_is_flag_available(disk, PED_DISK_CYLINDER_ALIGNMENT))
                if (!ped_disk_set_flag(disk, PED_DISK_CYLINDER_ALIGNMENT,
                                       alignment == ALIGNMENT_CYLINDER))
                        goto error;

        if (!ped_disk_type_check_feature (disk->type, PED_DISK_TYPE_EXTENDED)) {
                part_type = PED_PARTITION_NORMAL;
        } else {
                if (!command_line_get_part_type (_("Partition type?"),
                                                disk, &part_type))
                        goto error;
        }

        /* The undocumented feature that mkpart sometimes takes a
           partition name is next to useless, at least with a dvh
           partition table, since it makes the "mkpart" command
           fail unconditionally for a primary partition.  E.g.,
           mkpart primary any-name xfs 4096s 5000s
           requires the name, yet always fails, saying that only
           logical partitions may have names.
           If you want a name, use parted's separate "name" command.  */

        if (ped_disk_type_check_feature (disk->type,
                                         PED_DISK_TYPE_PARTITION_NAME)
            && ! (strcmp (disk->type->name, "dvh") == 0
                  && part_type != PED_PARTITION_LOGICAL))
                part_name = command_line_get_word (_("Partition name?"),
                                                   "", NULL, 1);

        peek_word = command_line_peek_word ();
        if (part_type == PED_PARTITION_EXTENDED
            || (peek_word && (isdigit (peek_word[0]) || peek_word[0] == '-'))) {
                fs_type = NULL;
                free (peek_word);
        } else {
                free (peek_word);
                if (!command_line_get_fs_type (_("File system type?"),
                                               &fs_type))
                        goto error;
        }

        if (!command_line_get_sector (_("Start?"), *dev, &start, &range_start, NULL))
                goto error;
        char *end_input;
        if (!command_line_get_sector (_("End?"), *dev, &end, &range_end, &end_input))
                goto error;

        _adjust_end_if_iec(&start, &end, range_end, end_input);
        free(end_input);

        /* processing starts here */
        part = ped_partition_new (disk, part_type, fs_type, start, end);
        if (!part)
                goto error;

        snap_to_boundaries (&part->geom, NULL, disk, range_start, range_end);

        /* create constraints */
        user_constraint = constraint_from_start_end (*dev, range_start,
                        range_end);
        PED_ASSERT (user_constraint != NULL);

        if (alignment == ALIGNMENT_OPTIMAL)
                dev_constraint =
                        ped_device_get_optimal_aligned_constraint(*dev);
        else if (alignment == ALIGNMENT_MINIMAL)
                dev_constraint =
                        ped_device_get_minimal_aligned_constraint(*dev);
        else
                dev_constraint = ped_device_get_constraint(*dev);
        PED_ASSERT (dev_constraint != NULL);

        final_constraint = ped_constraint_intersect (user_constraint,
                        dev_constraint);
        ped_constraint_destroy (user_constraint);
        ped_constraint_destroy (dev_constraint);
        if (!final_constraint)
                goto error_destroy_simple_constraints;

        /* subject to partition constraint */
        ped_exception_fetch_all();
        bool added_ok = ped_disk_add_partition (disk, part, final_constraint);
        ped_constraint_destroy (final_constraint);
        if (!added_ok) {
                ped_exception_leave_all();

                PedConstraint *constraint_any = ped_constraint_any (*dev);
                added_ok = ped_disk_add_partition (disk, part,
                                                        constraint_any);
                ped_constraint_destroy (constraint_any);

                if (!added_ok)
                        goto error_destroy_simple_constraints;

                if (!ped_geometry_test_sector_inside(range_start, part->geom.start) ||
                    !ped_geometry_test_sector_inside(range_end, part->geom.end)) {
                        start_usr = ped_unit_format (*dev, start);
                        end_usr   = ped_unit_format (*dev, end);
                        start_sol = ped_unit_format (*dev, part->geom.start);
                        end_sol   = ped_unit_format (*dev, part->geom.end);

                        /* In script mode failure to use specified values is fatal.
                         * However, in interactive mode, it merely elicits a warning
                         * and a prompt for whether to proceed.
                         */
                        switch (ped_exception_throw (
                                (opt_script_mode
                                 ? PED_EXCEPTION_ERROR
                                 : PED_EXCEPTION_WARNING),
                                (opt_script_mode
                                 ? PED_EXCEPTION_CANCEL
                                 : PED_EXCEPTION_YES_NO),
                                _("You requested a partition from %s to %s "
                                  "(sectors %llu..%llu).\n"
                                  "The closest location we can manage is "
                                  "%s to %s (sectors %llu..%llu).%s"),
                                start_usr, end_usr,
                                start, end,
                                start_sol, end_sol,
                                part->geom.start, part->geom.end,
                                (opt_script_mode ? ""
                                 : _("\nIs this still acceptable to you?"))))
                        {
                                case PED_EXCEPTION_YES:
                                        /* all is well in this state */
                                        break;
                                case PED_EXCEPTION_NO:
                                case PED_EXCEPTION_UNHANDLED:
                                default:
                                        /* undo partition addition */
                                        goto error_remove_part;
                        }
                }

                char *align_err = NULL;
                if ((alignment == ALIGNMENT_OPTIMAL &&
                     !partition_align_check(disk, part, PA_OPTIMUM, &align_err)) ||
                    (alignment == ALIGNMENT_MINIMAL &&
                     !partition_align_check(disk, part, PA_MINIMUM, &align_err))) {
                        if (ped_exception_throw(
                                PED_EXCEPTION_WARNING,
                                (opt_script_mode
                                 ? PED_EXCEPTION_OK
                                 : PED_EXCEPTION_IGNORE_CANCEL),
                                _("The resulting partition is not properly "
                                  "aligned for best performance: %s"),
                                align_err ? align_err : _("unknown (malloc failed)")) ==
                            PED_EXCEPTION_CANCEL) {
                                free(align_err);
                                /* undo partition addition */
                                goto error_remove_part;
                        }
                        free(align_err);
                }
        } else {
                ped_exception_leave_all();
        }
        ped_exception_catch();

        /* set minor attributes */
        if (part_name)
            if (!ped_partition_set_name (part, part_name))
                goto error_remove_part;
        free (part_name);  /* avoid double-free upon failure */
        part_name = NULL;

        if (!ped_partition_set_system (part, fs_type))
                goto error_remove_part;
        if (ped_partition_is_flag_available (part, PED_PARTITION_LBA))
                ped_partition_set_flag (part, PED_PARTITION_LBA, 1);

        if (!ped_disk_commit (disk))
                goto error_remove_part;

        /* clean up */
        if (range_start != NULL)
                ped_geometry_destroy (range_start);
        if (range_end != NULL)
                ped_geometry_destroy (range_end);

        free (start_usr);
        free (end_usr);
        free (start_sol);
        free (end_sol);

        if ((*dev)->type != PED_DEVICE_FILE)
                disk_is_modified = 1;

        return 1;

error_remove_part:
        ped_disk_remove_partition (disk, part);
error_destroy_simple_constraints:
        ped_partition_destroy (part);
error:
        free (part_name);
        if (range_start != NULL)
                ped_geometry_destroy (range_start);
        if (range_end != NULL)
                ped_geometry_destroy (range_end);

        free (start_usr);
        free (end_usr);
        free (start_sol);
        free (end_sol);

        return 0;
}

static int
do_name (PedDevice** dev, PedDisk** diskp)
{
        PedPartition*   part = NULL;
        char*           name;

        if (!*diskp)
                *diskp = ped_disk_new (*dev);
        if (!*diskp)
                goto error;

        if (!ped_disk_type_check_feature((*diskp)->type, PED_DISK_TYPE_PARTITION_NAME)) {
                ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
                                     _("%s disk labels do not support partition name."),
				     (*diskp)->type->name);
                goto error;
        }

        if (!command_line_get_partition (_("Partition number?"), *diskp, &part))
                goto error;

        name = command_line_get_word (_("Partition name?"),
                        ped_partition_get_name (part), NULL, 0);
        if (!name)
                goto error;
        if (!ped_partition_set_name (part, name))
                goto error_free_name;
        free (name);

        if (!ped_disk_commit (*diskp))
                goto error;
        return 1;

error_free_name:
        free (name);
error:
        return 0;
}

static char*
partition_print_flags (PedPartition const *part)
{
  char *res = xstrdup ("");
  if (!part)
    return res;

  PedPartitionFlag flag;
  size_t res_buf_len = 1;
  char const *sep = "";
  for (flag = ped_partition_flag_next (0); flag;
       flag = ped_partition_flag_next (flag))
    {
      if (ped_partition_get_flag (part, flag))
        {
          const char *name = _(ped_partition_flag_get_name (flag));
          size_t new_len = res_buf_len + strlen (sep) + strlen (name);
          res = xrealloc (res, new_len);
          stpcpy (stpcpy (res + res_buf_len - 1, sep), name);
          res_buf_len = new_len;
          sep = ", ";
        }
    }

  return res;
}

static int
partition_print (PedPartition* part)
{
        return 1;
}

static char*
disk_print_flags (PedDisk const *disk)
{
  char *res = xstrdup ("");
  if (!disk)
    return res;

  PedDiskFlag flag;
  size_t res_buf_len = 1;
  char const *sep = "";
  for (flag = ped_disk_flag_next (0); flag;
       flag = ped_disk_flag_next (flag))
    {
      if (ped_disk_get_flag (disk, flag))
        {
          const char *name = _(ped_disk_flag_get_name (flag));
          size_t new_len = res_buf_len + strlen (sep) + strlen (name);
          res = xrealloc (res, new_len);
          stpcpy (stpcpy (res + res_buf_len - 1, sep), name);
          res_buf_len = new_len;
          sep = ", ";
        }
    }

  return res;
}

static void
_print_disk_geometry (const PedDevice *dev)
{
        PED_ASSERT (dev != NULL);
        const PedCHSGeometry* chs = &dev->bios_geom;
        char* cyl_size = ped_unit_format_custom (dev,
                                chs->heads * chs->sectors,
                                PED_UNIT_KILOBYTE);

        if (opt_machine_mode) {
            printf ("%d:%d:%d:%s;\n",
                    chs->cylinders, chs->heads, chs->sectors, cyl_size);
        } else {
            printf (_("BIOS cylinder,head,sector geometry: %d,%d,%d.  "
                      "Each cylinder is %s.\n"),
                    chs->cylinders, chs->heads, chs->sectors, cyl_size);
        }

        free (cyl_size);
}

static void
_print_disk_info (const PedDevice *dev, const PedDisk *diskp)
{
        char const *const transport[] = {"unknown", "scsi", "ide", "dac960",
                                         "cpqarray", "file", "ataraid", "i2o",
                                         "ubd", "dasd", "viodasd", "sx8", "dm",
                                         "xvd", "sd/mmc", "virtblk", "aoe",
                                         "md", "loopback", "nvme", "brd",
                                         "pmem"};

        char* start = ped_unit_format (dev, 0);
        PedUnit default_unit = ped_unit_get_default ();
        char* end = ped_unit_format_byte (dev, dev->length * dev->sector_size
                                    - (default_unit == PED_UNIT_CHS ||
                                       default_unit == PED_UNIT_CYLINDER));

        const char* pt_name = diskp ? diskp->type->name : "unknown";
        char *disk_flags = disk_print_flags (diskp);

        if (opt_machine_mode) {
            switch (default_unit) {
                case PED_UNIT_CHS:      puts ("CHS;");
                                        break;
                case PED_UNIT_CYLINDER: puts ("CYL;");
                                        break;
                default:                puts ("BYT;");
                                        break;

            }
            printf ("%s:%s:%s:%lld:%lld:%s:%s:%s;\n",
                    dev->path, end, transport[dev->type],
                    dev->sector_size, dev->phys_sector_size,
                    pt_name, dev->model, disk_flags);
        } else {
            printf (_("Model: %s (%s)\n"),
                    dev->model, transport[dev->type]);
            printf (_("Disk %s: %s\n"), dev->path, end);
            printf (_("Sector size (logical/physical): %lldB/%lldB\n"),
                    dev->sector_size, dev->phys_sector_size);
        }

        free (start);
        free (end);

        if (ped_unit_get_default () == PED_UNIT_CHS
            || ped_unit_get_default () == PED_UNIT_CYLINDER)
                _print_disk_geometry (dev);

        if (!opt_machine_mode) {
            printf (_("Partition Table: %s\n"), pt_name);
            printf (_("Disk Flags: %s\n"), disk_flags);
        }
        free (disk_flags);
}

static int
do_print (PedDevice** dev, PedDisk** diskp)
{
        Table*          table;
        int             has_extended;
        int             has_name;
        int             has_devices_arg = 0;
        int             has_free_arg = 0;
        int             has_list_arg = 0;
        int             has_num_arg = 0;
        char*           peek_word;
        char*           start;
        char*           end;
        char*           size;
        const char*     name;
        char*           tmp;
        wchar_t*        table_rendered;
        int ok = 1; /* default to success */

        peek_word = command_line_peek_word ();
        if (peek_word) {
                if (strncmp (peek_word, "devices", 7) == 0) {
                        char *w = command_line_pop_word();
                        free (w);
                        has_devices_arg = 1;
                }
                else if (strncmp (peek_word, "free", 4) == 0) {
                        char *w = command_line_pop_word ();
                        free (w);
                        has_free_arg = 1;
                }
                else if (strncmp (peek_word, "list", 4) == 0 ||
                         strncmp (peek_word, "all", 3) == 0) {
                        char *w = command_line_pop_word();
                        free (w);
                        has_list_arg = 1;
                }
                else
                        has_num_arg = isdigit(peek_word[0]);

                free (peek_word);
        }

        if (!has_devices_arg && !has_list_arg) {
                if (!*diskp)
                        *diskp = ped_disk_new (*dev);
                /* Returning NULL here is an indication of failure, when in
                   script mode.  Otherwise (interactive mode) it may indicate
                   a real error, but it may also indicate that the user
                   declined when asked to perform some operation.  FIXME:
                   what this really needs is an API change, but a reliable
                   exit code is less important in interactive mode.  */
                if (*diskp == NULL && opt_script_mode)
                        ok = 0;
        }

        if (*diskp &&
            ped_disk_is_flag_available(*diskp, PED_DISK_CYLINDER_ALIGNMENT))
                if (!ped_disk_set_flag(*diskp, PED_DISK_CYLINDER_ALIGNMENT,
                                       alignment == ALIGNMENT_CYLINDER))
                        return 0;

        if (has_devices_arg) {
                char*           dev_name;
                PedDevice*      current_dev = NULL;
                int             status = 0;

                ped_device_probe_all();

                while ((current_dev = ped_device_get_next(current_dev))) {
                        end = ped_unit_format_byte (current_dev,
                                             current_dev->length
                                             * current_dev->sector_size);
                        printf ("%s (%s)\n", current_dev->path, end);
                        free (end);
                }

                dev_name = xstrdup ((*dev)->path);
                ped_device_free_all ();

                *dev = ped_device_get (dev_name);
                if (*dev && ped_device_open (*dev))
                    status = 1;
                free (dev_name);

                return status;
        }

        else if (has_list_arg)
                return _print_list ();

        else if (*diskp && has_num_arg) {
                PedPartition*   part = NULL;
                int             status = 0;
                if (command_line_get_partition ("", *diskp, &part))
                        status = partition_print (part);
                return status;
        }

        _print_disk_info (*dev, *diskp);
        if (!*diskp)
                goto nopt;
        if (!opt_machine_mode)
                putchar ('\n');

        has_extended = ped_disk_type_check_feature ((*diskp)->type,
                                         PED_DISK_TYPE_EXTENDED);
        has_name = ped_disk_type_check_feature ((*diskp)->type,
                                         PED_DISK_TYPE_PARTITION_NAME);

        PedPartition* part;
        if (!opt_machine_mode) {
            StrList *row1;

            if (ped_unit_get_default() == PED_UNIT_CHS) {
                    row1 = str_list_create (_("Number"), _("Start"),
                                               _("End"), NULL);
            } else {
                    row1 = str_list_create (_("Number"), _("Start"),
                                               _("End"), _("Size"), NULL);
            }

            if (has_extended)
                    str_list_append (row1, _("Type"));

            str_list_append (row1, _("File system"));

            if (has_name)
                    str_list_append (row1, _("Name"));

            str_list_append (row1, _("Flags"));


            table = table_new (str_list_length(row1));

            table_add_row_from_strlist (table, row1);

            for (part = ped_disk_next_partition (*diskp, NULL); part;
                 part = ped_disk_next_partition (*diskp, part)) {

                    if ((!has_free_arg && !ped_partition_is_active(part)) ||
                        part->type & PED_PARTITION_METADATA)
                            continue;

                    tmp = ped_malloc (4);

                    if (part->num >= 0)
                            sprintf (tmp, "%2d ", part->num);
                    else
                            sprintf (tmp, "%2s ", "");

                    StrList *row = str_list_create (tmp, NULL);

                    start = ped_unit_format (*dev, part->geom.start);
                    end = ped_unit_format_byte (
                            *dev,
                            (part->geom.end + 1) * (*dev)->sector_size - 1);
                    size = ped_unit_format (*dev, part->geom.length);
                    if (ped_unit_get_default() == PED_UNIT_CHS) {
                            str_list_append (row, start);
                            str_list_append (row, end);
                    } else {
                            str_list_append (row, start);
                            str_list_append (row, end);
                            str_list_append (row, size);
                    }

                    if (!(part->type & PED_PARTITION_FREESPACE)) {
                            if (has_extended) {
                                name = ped_partition_type_get_name (part->type);
                                str_list_append (row, name);
                            }

                            str_list_append (row, part->fs_type ?
                                             part->fs_type->name : "");

                            if (has_name) {
                                    name = ped_partition_get_name (part);
                                    str_list_append (row, name);
                            }

                            char *flags = partition_print_flags (part);
                            str_list_append (row, flags);
                            free (flags);
                    } else {
                            if (has_extended)
                                    str_list_append (row, "");
                            str_list_append (row, _("Free Space"));
                            if (has_name)
                                    str_list_append (row, "");
                            str_list_append (row, "");
                    }

                    //PED_ASSERT (row.cols == caption.cols)
                    table_add_row_from_strlist (table, row);
                    str_list_destroy (row);
                    free (start);
                    free (end);
                    free (size);
            }

            table_rendered = table_render (table);
#ifdef ENABLE_NLS
            printf("%ls\n", table_rendered);
#else
            printf("%s\n", table_rendered);
#endif
            free (table_rendered);
            table_destroy (table);
            str_list_destroy (row1);

        } else {

            for (part = ped_disk_next_partition (*diskp, NULL); part;
                 part = ped_disk_next_partition (*diskp, part)) {

                if ((!has_free_arg && !ped_partition_is_active(part)) ||
                        part->type & PED_PARTITION_METADATA)
                            continue;

                if (part->num >= 0)
                    printf ("%d:", part->num);
                else
                    fputs ("1:", stdout);

                char *s = ped_unit_format (*dev, part->geom.start);
                printf ("%s:", s);
                free (s);
                s = ped_unit_format_byte (*dev,
                                          (part->geom.end + 1) *
                                          (*dev)->sector_size - 1);
                printf ("%s:", s);
                free (s);

                if (ped_unit_get_default() != PED_UNIT_CHS) {
                    s = ped_unit_format (*dev, part->geom.length);
                    printf ("%s:", s);
                    free (s);
                }

                if (!(part->type & PED_PARTITION_FREESPACE)) {

                    if (part->fs_type)
                        printf ("%s:", part->fs_type->name);
                    else
                        putchar (':');

                    if (has_name)
                        printf ("%s:", ped_partition_get_name (part));
                    else
                        putchar (':');

                    char *flags = partition_print_flags (part);
                    printf ("%s;\n", flags);
                    free (flags);

                } else {
                    puts ("free;");
                }
            }
        }

        return ok;

nopt:
        return ok;
}

static int
_print_list ()
{
        PedDevice *current_dev = NULL;
        PedDisk *diskp = NULL;

        ped_device_probe_all();

        while ((current_dev = ped_device_get_next(current_dev))) {
                do_print (&current_dev, &diskp);
                if (diskp)
                        ped_disk_destroy (diskp);
                diskp = 0;
                putchar ('\n');
        }

        return 1;
}

static int
do_quit (PedDevice** dev, PedDisk **diskp)
{
        _done (*dev, *diskp);
        exit (EXIT_SUCCESS);
}

static PedPartitionType
_disk_get_part_type_for_sector (PedDisk* disk, PedSector sector)
{
        PedPartition*   extended;

        extended = ped_disk_extended_partition (disk);
        if (!extended
            || !ped_geometry_test_sector_inside (&extended->geom, sector))
                return 0;

        return PED_PARTITION_LOGICAL;
}

/* This function checks if "part" contains a file system, and returs
 *      0 if either no file system was found, or the user declined to add it.
 *      1 if a file system was found, and the user chose to add it.
 *      -1 if the user chose to cancel the entire search.
 */
static int
_rescue_add_partition (PedPartition* part)
{
        const PedFileSystemType*        fs_type;
        PedGeometry*                    probed;
        PedExceptionOption              ex_opt;
        PedConstraint*                  constraint;
        char*                           found_start;
        char*                           found_end;

        fs_type = ped_file_system_probe (&part->geom);
        if (!fs_type)
                return 0;
        probed = ped_file_system_probe_specific (fs_type, &part->geom);
        if (!probed)
                return 0;

        if (!ped_geometry_test_inside (&part->geom, probed)) {
                ped_geometry_destroy (probed);
                return 0;
        }

        constraint = ped_constraint_exact (probed);
        if (!ped_disk_set_partition_geom (part->disk, part, constraint,
                                          probed->start, probed->end)) {
                ped_constraint_destroy (constraint);
                return 0;
        }
        ped_constraint_destroy (constraint);

        found_start = ped_unit_format (probed->dev, probed->start);
        found_end = ped_unit_format (probed->dev, probed->end);
        ex_opt = ped_exception_throw (
                PED_EXCEPTION_INFORMATION,
                PED_EXCEPTION_YES_NO_CANCEL,
                _("A %s %s partition was found at %s -> %s.  "
                  "Do you want to add it to the partition table?"),
                fs_type->name, ped_partition_type_get_name (part->type),
                found_start, found_end);
        ped_geometry_destroy (probed);
        free (found_start);
        free (found_end);

        switch (ex_opt) {
                case PED_EXCEPTION_CANCEL: return -1;
                case PED_EXCEPTION_NO: return 0;
                default: break;
        }

        if (!ped_partition_set_system (part, fs_type))
                return 0;

        if (!ped_disk_commit (part->disk))
                return 0;

        return 1;
}

/* hack: we only iterate through the start, since most (all) fs's have their
 * superblocks at the start.  We'll need to change this if we generalize
 * for RAID, or something...
 */
static int
_rescue_pass (PedDisk* disk, PedGeometry* start_range, PedGeometry* end_range)
{
        PedSector               start;
        PedGeometry             start_geom_exact;
        PedGeometry             entire_dev;
        PedConstraint           constraint;
        PedPartition*           part;
        PedPartitionType        part_type;

        part_type = _disk_get_part_type_for_sector (
                        disk, (start_range->start + end_range->end) / 2);

        ped_geometry_init (&entire_dev, disk->dev, 0, disk->dev->length);

        ped_timer_reset (g_timer);
        ped_timer_set_state_name (g_timer, _("searching for file systems"));
        for (start = start_range->start; start <= start_range->end; start++) {
                ped_timer_update (g_timer, 1.0 * (start - start_range->start)
                                         / start_range->length);

                ped_geometry_init (&start_geom_exact, disk->dev, start, 1);
                ped_constraint_init (
                        &constraint, ped_alignment_any, ped_alignment_any,
                        &start_geom_exact, &entire_dev,
                        1, disk->dev->length);
                part = ped_partition_new (disk, part_type, NULL, start,
                                end_range->end);
                if (!part) {
                        ped_constraint_done (&constraint);
                        continue;
                }

                ped_exception_fetch_all ();
                if (ped_disk_add_partition (disk, part, &constraint)) {
                        ped_exception_leave_all ();
                        switch (_rescue_add_partition (part)) {
                        case 1:
                                ped_constraint_done (&constraint);
                                return 1;

                        case 0:
                                ped_disk_remove_partition (disk, part);
                                break;

                        case -1:
                                goto error_remove_partition;
                        }
                } else {
                        ped_exception_leave_all ();
                }
                ped_partition_destroy (part);
                ped_constraint_done (&constraint);
        }
        ped_timer_update (g_timer, 1.0);

        return 1;

error_remove_partition:
        ped_disk_remove_partition (disk, part);
        ped_partition_destroy (part);
        ped_constraint_done (&constraint);
        return 0;
}

static int
do_rescue (PedDevice** dev, PedDisk** diskp)
{
        PedDisk*                disk;
        PedSector               start = 0, end = 0;
        PedSector               fuzz;
        PedGeometry             probe_start_region;
        PedGeometry             probe_end_region;

        if (*diskp) {
                ped_disk_destroy (*diskp);
                *diskp = 0;
        }
        disk = ped_disk_new (*dev);
        if (!disk)
                goto error;
        if (ped_disk_is_flag_available(disk, PED_DISK_CYLINDER_ALIGNMENT))
                if (!ped_disk_set_flag(disk, PED_DISK_CYLINDER_ALIGNMENT,
                                       0))
                        goto error;

        if (!command_line_get_sector (_("Start?"), *dev, &start, NULL, NULL))
                goto error_destroy_disk;
        if (!command_line_get_sector (_("End?"), *dev, &end, NULL, NULL))
                goto error_destroy_disk;

        fuzz = PED_MAX (PED_MIN ((end - start) / 10, MEGABYTE_SECTORS(*dev)),
                        MEGABYTE_SECTORS(*dev) * 16);

        ped_geometry_init (&probe_start_region, *dev,
                           PED_MAX(start - fuzz, 0),
                           PED_MIN(2 * fuzz, (*dev)->length - (start - fuzz)));
        ped_geometry_init (&probe_end_region, *dev,
                           PED_MAX(end - fuzz, 0),
                           PED_MIN(2 * fuzz, (*dev)->length - (end - fuzz)));

        if (!_rescue_pass (disk, &probe_start_region, &probe_end_region))
                goto error_destroy_disk;

        ped_disk_destroy (disk);

        if ((*dev)->type != PED_DEVICE_FILE)
                disk_is_modified = 1;

        return 1;

error_destroy_disk:
        ped_disk_destroy (disk);
error:
        return 0;
}

static int
do_resize (PedDevice **dev, PedDisk** diskp)
{
        ped_exception_throw (
                PED_EXCEPTION_ERROR,
                PED_EXCEPTION_CANCEL,
                _("The resize command has been removed in parted 3.0"));
        return 0;
}

static int
do_resizepart (PedDevice** dev, PedDisk** diskp)
{
        PedDisk                 *disk = *diskp;
        PedPartition            *part = NULL;
        PedSector               start, end, oldend;
        PedGeometry             *range_end = NULL;
        PedConstraint*          constraint;
        int rc = 0;
        char*                   end_input = NULL;
        char*                   end_size = NULL;

        if (!disk) {
                disk = ped_disk_new (*dev);
                *diskp = disk;
        }
        if (!disk)
                goto error;

        if (ped_disk_is_flag_available(disk, PED_DISK_CYLINDER_ALIGNMENT))
                if (!ped_disk_set_flag(disk, PED_DISK_CYLINDER_ALIGNMENT,
                                       alignment == ALIGNMENT_CYLINDER))
                        goto error;

        if (!command_line_get_partition (_("Partition number?"), disk, &part))
                goto error;

        /* Save the optional End value if the partition is busy. */
        if (ped_partition_is_busy(part)) {
            if (command_line_get_word_count())
                end_size = command_line_pop_word();
        }

        /* If the partition is busy this may clear the command_line and prompt the user */
        if (!_partition_warn_busy (part))
                goto error;

        /* Push the End value back onto the command_line, if it exists */
        if (end_size) {
            command_line_push_word(end_size);
            free(end_size);
        }

        start = part->geom.start;
        end = oldend = part->geom.end;
        if (!command_line_get_sector (_("End?"), *dev, &end, &range_end, &end_input))
                goto error;
        _adjust_end_if_iec(&start, &end, range_end, end_input);
        free(end_input);
        /* Do not move start of the partition */
        constraint = constraint_from_start_end_fixed_start (*dev, start, range_end);
        if (!ped_disk_set_partition_geom (disk, part, constraint,
                                          start, end))
                goto error_destroy_constraint;
        /* warn when shrinking partition - might lose data */
        if (part->geom.end < oldend)
                if (ped_exception_throw (
                            PED_EXCEPTION_WARNING,
                            PED_EXCEPTION_YES_NO,
                            _("Shrinking a partition can cause data loss, " \
                              "are you sure you want to continue?")) != PED_EXCEPTION_YES)
                        goto error_destroy_constraint;
        ped_disk_commit (disk);

        if ((*dev)->type != PED_DEVICE_FILE)
                disk_is_modified = 1;

        rc = 1;

error_destroy_constraint:
        ped_constraint_destroy (constraint);
error:
        if (range_end != NULL)
                ped_geometry_destroy (range_end);
        return rc;
}


static int
do_rm (PedDevice** dev, PedDisk** diskp)
{
        PedPartition*           part = NULL;

        if (!*diskp)
                *diskp = ped_disk_new (*dev);
        if (!*diskp)
                goto error;

        if (!command_line_get_partition (_("Partition number?"), *diskp, &part))
                goto error;
        if (!_partition_warn_busy (part))
                goto error;

        if (!ped_disk_delete_partition (*diskp, part))
                goto error;
        if (!ped_disk_commit (*diskp))
                goto error;

        if ((*dev)->type != PED_DEVICE_FILE)
                disk_is_modified = 1;

        return 1;

error:
        return 0;
}

static int
do_select (PedDevice** dev, PedDisk** diskp)
{
        PedDevice*      new_dev = *dev;

        if (!command_line_get_device (_("New device?"), &new_dev))
                return 0;
        if (!ped_device_open (new_dev))
                return 0;

        ped_device_close (*dev);
        if (*diskp) {
                ped_disk_destroy (*diskp);
                *diskp = 0;
        }
        *dev = new_dev;
        print_using_dev (*dev);
        return 1;
}

/* Return true if partition PART is consistent with DISK's selected
   offset and alignment requirements.  Also return true if there is
   insufficient kernel support to determine DISK's alignment requirements.
   Otherwise, return false.  A_TYPE selects whether to check for minimal
   or optimal alignment requirements.

   If align_err is not NULL a string describing why the check failed
   will be allocated and returned. It is up to the caller to free this.
   Pass NULL if no error description is needed.

   If allocating the error string fails *align_err will be set to NULL, the
   caller should always check for this.
*/
static bool
partition_align_check (PedDisk const *disk, PedPartition const *part,
		       enum AlignmentType a_type, char **align_err)
{
  PED_ASSERT (part->disk == disk);
  PedDevice const *dev = disk->dev;

  PedAlignment *pa = (a_type == PA_MINIMUM
		      ? ped_device_get_minimum_alignment (dev)
		      : ped_device_get_optimum_alignment (dev));
  if (pa == NULL)
      return true;

  PED_ASSERT (pa->grain_size != 0);
  bool ok = (part->geom.start % pa->grain_size == pa->offset);

  /* If it isn't aligned and the caller wants an explanation,
     show them the math.  */
  if (!ok && align_err) {
      if (asprintf(align_err,
                   "%llds %% %llds != %llds",
                   part->geom.start, pa->grain_size, pa->offset) < 0) {
          *align_err = NULL;
      }
  }
  free (pa);
  return ok;
}

static int
do_align_check (PedDevice **dev, PedDisk** diskp)
{
  if (!*diskp)
    *diskp = ped_disk_new (*dev);
  if (!*diskp)
    goto error;

  enum AlignmentType align_type = PA_OPTIMUM;
  PedPartition *part = NULL;

  if (!command_line_get_align_type (_("alignment type(min/opt)"), &align_type))
    goto error;
  if (!command_line_get_partition (_("Partition number?"), *diskp, &part))
    goto error;

  char *align_err = NULL;
  bool aligned = partition_align_check (*diskp, part, align_type, &align_err);

  /* Don't print the error in script mode */
  if (opt_script_mode) {
      free(align_err);
      return aligned ? 1 : 0;
  }

  if (aligned)
      printf(_("%d aligned\n"), part->num);
  else
      printf(_("%d not aligned: %s\n"),
             part->num,
             align_err ? align_err : _("unknown (malloc failed)"));

  free(align_err);

  /* Always return 1 in interactive mode, to be consistent
     with the other modes.  */
  return 1;

error:
  return 0;
}

static int
do_disk_set (PedDevice** dev, PedDisk** diskp)
{
    PedDiskFlag flag;
    int         state;

    if (!*diskp)
            *diskp = ped_disk_new (*dev);
    if (!*diskp)
        goto error;

    if (!command_line_get_disk_flag (_("Flag to Invert?"), *diskp, &flag))
        goto error;
    state = (ped_disk_get_flag (*diskp, flag) == 0 ? 1 : 0);

    if (!is_toggle_mode) {
        if (!command_line_get_state (_("New state?"), &state))
            goto error;
    }

    if (!ped_disk_set_flag (*diskp, flag, state))
        goto error;
    if (!ped_disk_commit (*diskp))
        goto error;

    if ((*dev)->type != PED_DEVICE_FILE)
        disk_is_modified = 1;

    return 1;

error:
    return 0;
}

static int
do_set (PedDevice** dev, PedDisk **diskp)
{
        PedPartition*           part = NULL;
        PedPartitionFlag        flag;
        int                     state;

        if (*diskp == 0)
                *diskp = ped_disk_new (*dev);
        if (!*diskp)
                goto error;

        if (!command_line_get_partition (_("Partition number?"), *diskp, &part))
                goto error;
        if (!command_line_get_part_flag (_("Flag to Invert?"), part, &flag))
                goto error;
        state = (ped_partition_get_flag (part, flag) == 0 ? 1 : 0);

        if (!is_toggle_mode) {
                if (!command_line_get_state (_("New state?"), &state))
                        goto error;
        }

        if (!ped_partition_set_flag (part, flag, state))
                goto error;
        if (!ped_disk_commit (*diskp))
                goto error;

        if ((*dev)->type != PED_DEVICE_FILE)
                disk_is_modified = 1;

        return 1;

error:
        return 0;
}

static int
do_disk_toggle (PedDevice **dev, PedDisk** diskp)
{
    int result;

    is_toggle_mode = 1;
    result = do_disk_set (dev, diskp);
    is_toggle_mode = 0;

    return result;
}

static int
do_toggle (PedDevice **dev, PedDisk** diskp)
{
        int result;

        is_toggle_mode = 1;
        result = do_set (dev, diskp);
        is_toggle_mode = 0;

        return result;
}

static int
do_unit (PedDevice** dev, PedDisk** diskp)
{
        PedUnit unit = ped_unit_get_default ();
        if (!command_line_get_unit (_("Unit?"), &unit))
                return 0;
        ped_unit_set_default (unit);
        return 1;
}

static int
do_version ()
{
    printf ("\n%s\n%s",
            prog_name,
            _(copyright_msg));
    return 1;
}

static void
_init_messages ()
{
        StrList*                list;
        int                     first;
        PedFileSystemType*      fs_type;
        PedFileSystemAlias*     fs_alias;
        PedDiskType*            disk_type;
        PedPartitionFlag        part_flag;
        PedDiskFlag             disk_flag;
        PedUnit                 unit;

/* flags */
        first = 1;
        list = str_list_create (_(flag_msg_start), NULL);
        for (part_flag = ped_partition_flag_next (0); part_flag;
                        part_flag = ped_partition_flag_next (part_flag)) {
                if (first)
                        first = 0;
                else
                        str_list_append (list, ", ");
                str_list_append (list,
                                 _(ped_partition_flag_get_name (part_flag)));
        }
        str_list_append (list, "\n");

        flag_msg = str_list_convert (list);
        str_list_destroy (list);
/* disk flags */
        first = 1;
        list = str_list_create (_(disk_flag_msg_start), NULL);
        for (disk_flag = ped_disk_flag_next (0); disk_flag;
                        disk_flag = ped_disk_flag_next (disk_flag)) {
                if (first)
                        first = 0;
                else
                        str_list_append (list, ", ");
                str_list_append (list,
                                 _(ped_disk_flag_get_name (disk_flag)));
        }
        str_list_append (list, "\n");

        disk_flag_msg = str_list_convert (list);
        str_list_destroy (list);

/* units */
        first = 1;
        list = str_list_create (_(unit_msg_start), NULL);
        for (unit = PED_UNIT_FIRST; unit <= PED_UNIT_LAST; unit++) {
                if (first)
                        first = 0;
                else
                        str_list_append (list, ", ");
                str_list_append (list, ped_unit_get_name (unit));
        }
        str_list_append (list, "\n");

        unit_msg = str_list_convert (list);
        str_list_destroy (list);

/* disk type */
        list = str_list_create (_(label_type_msg_start), NULL);

        first = 1;
        for (disk_type = ped_disk_type_get_next (NULL);
             disk_type; disk_type = ped_disk_type_get_next (disk_type)) {
                if (disk_type->ops->write == NULL)
                        continue;

                if (first)
                        first = 0;
                else
                        str_list_append (list, ", ");
                str_list_append (list, disk_type->name);
        }
        str_list_append (list, "\n");

        label_type_msg = str_list_convert (list);
        str_list_destroy (list);

/* mkpart - file system types and aliases */
        list = str_list_create (_(fs_type_msg_start), NULL);

        first = 1;
        for (fs_type = ped_file_system_type_get_next (NULL);
             fs_type; fs_type = ped_file_system_type_get_next (fs_type)) {
                if (first)
                        first = 0;
                else
                        str_list_append (list, ", ");
                str_list_append (list, fs_type->name);
        }
        for (fs_alias = ped_file_system_alias_get_next (NULL);
             fs_alias; fs_alias = ped_file_system_alias_get_next (fs_alias)) {
                if (first)
                        first = 0;
                else
                        str_list_append (list, ", ");
                str_list_append (list, fs_alias->alias);
        }
        str_list_append (list, "\n");

        mkpart_fs_type_msg = str_list_convert (list);
        str_list_destroy (list);
}

static void
_done_messages ()
{
        free (flag_msg);
        free (unit_msg);
        free (mkpart_fs_type_msg);
        free (label_type_msg);
}

static void
_init_commands ()
{
  command_register (commands,
    command_create ( str_list_create_unique ("align-check",
					     _("align-check"), NULL),
		     do_align_check,
		     str_list_create (
				      _("align-check TYPE N"
					"                       "
					"check partition N for"
					" TYPE(min|opt) alignment"), NULL),

		     str_list_create (_(number_msg), _(min_or_opt_msg),
				      NULL), 1));
        command_register (commands, command_create (
                str_list_create_unique ("help", _("help"), NULL),
                do_help,
                str_list_create (
_("help [COMMAND]                           print general help, or help "
  "on COMMAND"),
NULL),
                NULL, 1));

        command_register (commands, command_create (
                str_list_create_unique ("mklabel", _("mklabel"), "mktable", _("mktable"), NULL),
                do_mklabel,
                str_list_create (
_("mklabel,mktable LABEL-TYPE               create a new disklabel "
  "(partition table)"),
NULL),
                str_list_create (label_type_msg, NULL), 1));

        command_register (commands, command_create (
                str_list_create_unique ("mkpart", _("mkpart"), NULL),
                do_mkpart,
                str_list_create (
_("mkpart PART-TYPE [FS-TYPE] START END     make a partition"),
NULL),
                str_list_create (_(part_type_msg),
                                 _(mkpart_fs_type_msg),
                                 _(start_end_msg),
                                 "\n",
_("'mkpart' makes a partition without creating a new file system on the "
  "partition.  FS-TYPE may be specified to set an appropriate partition ID.\n"),
NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("name", _("name"), NULL),
        do_name,
        str_list_create (
_("name NUMBER NAME                         name partition NUMBER as NAME"),
NULL),
        str_list_create (_(number_msg), _(name_msg), NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("print", _("print"), NULL),
        do_print,
        str_list_create (
_("print [devices|free|list,all|NUMBER]     display the partition table, "
  "available devices, free space, all found partitions, or a particular "
  "partition"),
NULL),
        str_list_create (
_("Without arguments, 'print' displays the entire partition table. However "
  "with the following arguments it performs various other actions.\n"),
_("  devices   : display all active block devices\n"),
_("  free      : display information about free unpartitioned space on the "
  "current block device\n"),
_("  list, all : display the partition tables of all active block devices\n"),
_("  NUMBER    : display more detailed information about this particular "
  "partition\n"),
NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("quit", _("quit"), NULL),
        do_quit,
        str_list_create (
_("quit                                     exit program"),
NULL),
        NULL, 1));

command_register (commands, command_create (
        str_list_create_unique ("rescue", _("rescue"), NULL),
        do_rescue,
        str_list_create (
_("rescue START END                         rescue a lost partition near "
"START and END"),
NULL),
        str_list_create (_(start_end_msg), NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("resize", _("resize"), NULL),
        do_resize,
        NULL,
        str_list_create (_(N_("The resize command was removed in parted 3.0\n")), NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("resizepart", _("resizepart"), NULL),
        do_resizepart,
        str_list_create (
_("resizepart NUMBER END                    resize partition NUMBER"),
NULL),
        str_list_create (_(number_msg), _(end_msg), NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("rm", _("rm"), NULL),
        do_rm,
        str_list_create (
_("rm NUMBER                                delete partition NUMBER"),
NULL),
        str_list_create (_(number_msg), NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("select", _("select"), NULL),
        do_select,
        str_list_create (
_("select DEVICE                            choose the device to edit"),
NULL),
        str_list_create (_(device_msg), NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("disk_set", _("disk_set"), NULL),
        do_disk_set,
        str_list_create (
_("disk_set FLAG STATE                      change the FLAG on selected device"),
NULL),
        str_list_create (disk_flag_msg, _(state_msg), NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("disk_toggle", _("disk_toggle"), NULL),
        do_disk_toggle,
        str_list_create (
_("disk_toggle [FLAG]                       toggle the state of FLAG on "
"selected device"),
NULL),
        str_list_create (disk_flag_msg, NULL), 1));

command_register (commands, command_create (
		str_list_create_unique ("set", _("set"), NULL),
		do_set,
		str_list_create (
_("set NUMBER FLAG STATE                    change the FLAG on partition "
  "NUMBER"),
NULL),
        str_list_create (_(number_msg), flag_msg, _(state_msg), NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("toggle", _("toggle"), NULL),
        do_toggle,
        str_list_create (
_("toggle [NUMBER [FLAG]]                   toggle the state of FLAG on "
  "partition NUMBER"),
NULL),
        str_list_create (_(number_msg), flag_msg, NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("unit", _("unit"), NULL),
        do_unit,
        str_list_create (
_("unit UNIT                                set the default unit to UNIT"),
NULL),
        str_list_create (unit_msg, NULL), 1));

command_register (commands, command_create (
        str_list_create_unique ("version", _("version"), NULL),
        do_version,
        str_list_create (
_("version                                  display the version number "
"and copyright information of GNU Parted"),
NULL),
        str_list_create (
_("'version' displays copyright and version information corresponding to this "
"copy of GNU Parted\n"),
NULL), 1));

}

static void
_done_commands ()
{
Command**       walk;

for (walk = commands; *walk; walk++) {
        command_destroy (*walk);
        *walk = NULL;
}
}

static void
_init_i18n ()
{
/* intialize i18n */
#ifdef ENABLE_NLS
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
#endif /* ENABLE_NLS */
}

void
_version ()
{
  version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
               (char *) NULL);
}

static int
_parse_options (int* argc_ptr, char*** argv_ptr)
{
int     opt, help = 0, list = 0, version = 0, wrong = 0;

while (1)
{
        opt = getopt_long (*argc_ptr, *argv_ptr, "hlmsva:",
                           options, NULL);
        if (opt == -1)
                break;

        switch (opt) {
                case 'h': help = 1; break;
                case 'l': list = 1; break;
                case 'm': opt_machine_mode = 1; break;
                case 's': opt_script_mode = 1; break;
                case 'v': version = 1; break;
                case 'a':
                  alignment = XARGMATCH ("--align", optarg,
                                         align_args, align_types);
                  break;
                case PRETEND_INPUT_TTY:
                  pretend_input_tty = 1;
                  break;
                default:
                  wrong = 1;
                  break;
        }
}

if (wrong == 1) {
        fprintf (stderr,
                 _("Usage: %s [-hlmsv] [-a<align>] [DEVICE [COMMAND [PARAMETERS]]...]\n"),
                 program_name);
        return 0;
}

if (version == 1) {
        _version ();
        exit (EXIT_SUCCESS);
}

if (help == 1) {
        help_msg ();
        exit (EXIT_SUCCESS);
}

if (list == 1) {
        _print_list ();
        exit (EXIT_SUCCESS);
}

*argc_ptr -= optind;
*argv_ptr += optind;
return 1;
}

static PedDevice*
_choose_device (int* argc_ptr, char*** argv_ptr)
{
PedDevice*      dev;

/* specified on comand line? */
if (*argc_ptr) {
        dev = ped_device_get ((*argv_ptr) [0]);
        if (!dev)
                return NULL;
        (*argc_ptr)--;
        (*argv_ptr)++;
} else {
retry:
        ped_device_probe_all ();
        dev = ped_device_get_next (NULL);
        if (!dev) {
                if (ped_exception_throw (PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_RETRY_CANCEL,
                        _("No device found"))
                                == PED_EXCEPTION_RETRY)
                        goto retry;
                else
                        return NULL;
        }
}

if (!ped_device_open (dev))
        return NULL;
return dev;
}

static PedDevice*
_init (int* argc_ptr, char*** argv_ptr)
{
PedDevice*      dev;

#ifdef ENABLE_MTRACE
mtrace();
#endif

_init_i18n ();
if (!init_ui ())
        goto error;
_init_messages ();
_init_commands ();

if (!_parse_options (argc_ptr, argv_ptr))
        goto error_done_commands;

if (!opt_script_mode)
        if (init_readline ())
                goto error_done_commands;

#ifdef HAVE_GETUID
        if (getuid() != 0 && !opt_script_mode) {
            fputs (_("WARNING: You are not superuser.  Watch out for "
                     "permissions.\n"), stderr);
        }
#endif

dev = _choose_device (argc_ptr, argv_ptr);
if (!dev)
        goto error_done_commands;

g_timer = ped_timer_new (_timer_handler, &timer_context);
if (!g_timer)
        goto error_done_commands;
timer_context.last_update = 0;

return dev;

error_done_commands:
_done_commands ();
_done_messages ();
done_ui ();
error:
return NULL;
}

static void
_done (PedDevice* dev, PedDisk* diskp)
{
        if (diskp)
                ped_disk_destroy (diskp);
        if (dev->boot_dirty && dev->type != PED_DEVICE_FILE) {
                ped_exception_throw (
                        PED_EXCEPTION_WARNING,
                        PED_EXCEPTION_OK,
                        _("You should reinstall your boot loader before "
                          "rebooting.  Read section 4 of the Parted User "
                          "documentation for more information."));
        }
        if (!opt_script_mode && !opt_machine_mode && disk_is_modified) {
                ped_exception_throw (
                        PED_EXCEPTION_INFORMATION, PED_EXCEPTION_OK,
                        _("You may need to update /etc/fstab.\n"));
}

ped_device_close (dev);

ped_timer_destroy (g_timer);
_done_commands ();
_done_messages ();
done_ui();
}

int
main (int argc, char** argv)
{
        PedDevice*      dev;
	PedDisk*        diskp = 0;
        int             status;

        set_program_name (argv[0]);
        atexit (close_stdout);

        dev = _init (&argc, &argv);
        if (!dev)
                return 1;

        if (argc || opt_script_mode)
                status = non_interactive_mode (&dev, &diskp, commands, argc, argv);
        else
                status = interactive_mode (&dev, &diskp, commands);

        _done (dev, diskp);

        return !status;
}
