summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsrs5694 <srs5694@users.sourceforge.net>2011-09-10 20:29:53 -0400
committersrs5694 <srs5694@users.sourceforge.net>2011-09-10 20:29:53 -0400
commit3860cbe1cafb88d70097bdfb8d84cc0029f1738e (patch)
tree337b424babd62ef8fb5d124111f62bb436c92186
parent00b6d7a4604e759eb3c92b3ecea608d6fe024b81 (diff)
downloadsgdisk-3860cbe1cafb88d70097bdfb8d84cc0029f1738e.tar.gz
New files in support of version 0.8.0
-rw-r--r--cgdisk.8385
-rw-r--r--cgdisk.cc65
-rwxr-xr-xgdisk_test.sh377
-rw-r--r--gptcl.cc534
-rw-r--r--gptcl.h53
-rw-r--r--gptcurses.cc808
-rw-r--r--gptcurses.h129
7 files changed, 2351 insertions, 0 deletions
diff --git a/cgdisk.8 b/cgdisk.8
new file mode 100644
index 0000000..6d09bb2
--- /dev/null
+++ b/cgdisk.8
@@ -0,0 +1,385 @@
+.\" Copyright 2011 Roderick W. Smith (rodsmith@rodsbooks.com)
+.\" May be distributed under the GNU General Public License
+.TH "CGDISK" "8" "0.8.0" "Roderick W. Smith" "GPT fdisk Manual"
+.SH "NAME"
+cgdisk \- Curses-based GUID partition table (GPT) manipulator
+.SH "SYNOPSIS"
+.BI "cgdisk "
+.I device
+
+.SH "DESCRIPTION"
+
+GPT fdisk is a text\-mode family of programs for creation and manipulation
+of partition tables. The \fBcgdisk\fR member of this family employs a
+curses-based user interface for interaction using a text\-mode menuing
+system. It will automatically convert an old\-style Master Boot Record
+(MBR) partition table or BSD disklabel stored without an MBR carrier
+partition to the newer Globally Unique Identifier (GUID) Partition Table
+(GPT) format, or will load a GUID partition table. Other members of this
+program family are \fBgdisk\fR (the most feature-rich program of the group,
+with a non-curses-based interactive user interface) and \fBsgdisk\fR (which
+is driven via command-line options for use by experts or in scripts).
+FixParts is a related program for fixing a limited set of problems with MBR
+disks.
+
+For information on MBR vs. GPT, as well as GPT terminology and structure,
+see the extended GPT fdisk documentation at
+\fIhttp://www.rodsbooks.com/gdisk/\fR or consult Wikipedia.
+
+The \fBcgdisk\fR program employs a user interface similar to that of Linux's
+\fBcfdisk\fR, but \fBcgdisk\fR modifies GPT partitions. It also has the
+capability of transforming MBR partitions or BSD disklabels into GPT
+partitions. Like the original \fBcfdisk\fR program, \fBcgdisk\fR does not
+modify disk structures until you explicitly write them to disk, so if you
+make a mistake, you can exit from the program with the Quit option to leave
+your partitions unmodified.
+
+Ordinarily, \fBcgdisk\fR operates on disk device files, such as
+\fI/dev/sda\fR or \fI/dev/hda\fR under Linux, \fI/dev/disk0\fR under
+Mac OS X, or \fI/dev/ad0\fR or \fI/dev/da0\fR under FreeBSD. The program
+can also operate on disk image files, which can be either copies of whole
+disks (made with \fBdd\fR, for instance) or raw disk images used by
+emulators such as QEMU or VMWare. Note that only \fIraw\fR disk images
+are supported; \fBcgdisk\fR cannot work on compressed or other advanced
+disk image formats.
+
+Upon start, \fBcgdisk\fR attempts to identify the partition type in use on
+the disk. If it finds valid GPT data, \fBcgdisk\fR will use it. If
+\fBcgdisk\fR finds a valid MBR or BSD disklabel but no GPT data, it will
+attempt to convert the MBR or disklabel into GPT form. (BSD disklabels are
+likely to have unusable first and/or final partitions because they overlap
+with the GPT data structures, though.) Upon exiting with the 'w' option,
+\fBcgdisk\fR replaces the MBR or disklabel with a GPT. \fIThis action is
+potentially dangerous!\fR Your system may become unbootable, and partition
+type codes may become corrupted if the disk uses unrecognized type codes.
+Boot problems are particularly likely if you're multi\-booting with any
+GPT\-unaware OS. If you mistakenly launch \fBcgdisk\fR on an MBR disk, you
+can safely exit the program without making any changes by using the Quit
+option.
+
+When creating a fresh partition table, certain considerations may be in
+order:
+
+.TP
+.B *
+For data (non\-boot) disks, and for boot disks used on BIOS\-based computers
+with GRUB as the boot loader, partitions may be created in whatever order
+and in whatever sizes are desired.
+
+.TP
+.B *
+Boot disks for EFI\-based systems require an \fIEFI System
+Partition\fR (GPT fdisk internal code 0xEF00) formatted as FAT\-32.
+The recommended size of this partition is between 100 and 300 MiB.
+Boot\-related files are stored here. (Note that GNU Parted identifies
+such partitions as having the "boot flag" set.)
+
+.TP
+.B *
+The GRUB 2 boot loader for BIOS\-based systems makes use of a \fIBIOS Boot
+Partition\fR (GPT fdisk internal code 0xEF02), in which the secondary
+boot loader is stored, without the benefit of a filesystem. This partition
+can typically be quite small (roughly 32 KiB to 1 MiB), but you should
+consult your boot loader documentation for details.
+
+.TP
+.B *
+If Windows is to boot from a GPT disk, a partition of type \fIMicrosoft
+Reserved\fR (GPT fdisk
+internal code 0x0C01) is recommended. This partition should be about 128 MiB
+in size. It ordinarily follows the EFI System Partition and immediately
+precedes the Windows data partitions. (Note that old versions of GNU Parted
+create all FAT partitions as this type, which actually makes the partition
+unusable for normal file storage in both Windows and Mac OS X.)
+
+.TP
+.B *
+Some OSes' GPT utilities create some blank space (typically 128 MiB) after
+each partition. The intent is to enable future disk utilities to use this
+space. Such free space is not required of GPT disks, but creating it may
+help in future disk maintenance. You can use GPT fdisk's relative partition
+positioning option (specifying the starting sector as '+128M', for
+instance) to simplify creating such gaps.
+
+.SH "OPTIONS"
+
+.PP
+
+Interactions with \fBcgdisk\fR occur with its interactive text\-mode menus.
+The display is broken into two interactive parts:
+
+.TP
+.B *
+The partition display area, in which partitions and gaps between them
+(marked as "free space") are summarized.
+
+.TP
+.B *
+The option selection area, in which buttons for the main options appear.
+
+.PP
+
+In addition, the top of the display shows the program's name and version
+number, the device filename associated with the disk, and the disk's size
+in both sectors and IEEE-1541 units (GiB, TiB, and so on).
+
+You can use the following keys to move among the various options and to
+select among them:
+
+.TP
+.B up arrow
+This key moves the partition selection up by one partition.
+
+.TP
+.B down arrow
+This key moves the partition selection down by one partition.
+
+.TP
+.B Page Up
+This key moves the partition selection up by one screen.
+
+.TP
+.B Page Down
+This key moves the partition selection down by one screen.
+
+.TP
+.B right arrow
+This key moves the option selection to the right by one item.
+
+.TP
+.B left arrow
+This key moves the option selection to the left by one item.
+
+.TP
+.B Enter
+This key activates the currently selected option. You can also activate an
+option by typing the capitalized letter in the option's name on the
+keyboard, such as \fBa\fR to activate the Align option.
+
+.PP
+
+If more partitions exist than can be displayed in one screen, you can
+scroll between screens using the partition selection keys, much as in a
+text editor.
+
+Available options are as described below. (Note that \fBcgdisk\fR provides
+a much more limited set of options than its sibling \fBgdisk\fR. If you
+need to perform partition table recovery, hybrid MBR modifcation, or other
+advanced operations, you should consult the \fBgdisk\fR documentation.)
+
+.TP
+.B Align
+Change the sector alignment value. Disks with more logical sectors than
+physical sectors (such as modern Advanced Format drives), some RAID
+configurations, and many SSD devices, can suffer performance problems if
+partitions are not aligned properly for their internal data structures. On
+new disks, GPT fdisk attempts to align partitions on 2048\-sector (1MiB)
+boundaries by default, which optimizes performance for all of these disk
+types. On pre\-partitioned disks, GPT fdisk attempts to identify the
+alignment value used on that disk, but will set 8-sector alignment on disks
+larger than 300 GB even if lesser alignment values are detected. In either
+case, it can be changed by using this option.
+
+.TP
+.B Backup
+Save partition data to a backup file. You can back up your current
+in\-memory partition table to a disk file using this option. The resulting
+file is a binary file consisting of the protective MBR, the main GPT
+header, the backup GPT header, and one copy of the partition table, in that
+order. Note that the backup is of the current in\-memory data structures, so
+if you launch the program, make changes, and then use this option, the
+backup will reflect your changes.
+
+.TP
+.B Delete
+Delete a partition. This action deletes the entry from the partition table
+but does not disturb the data within the sectors originally allocated to
+the partition on the disk. If a corresponding hybrid MBR partition exists,
+\fBgdisk\fR deletes it, as well, and expands any adjacent 0xEE (EFI GPT)
+MBR protective partition to fill the new free space.
+
+.TP
+.B Help
+Print brief descriptions of all the options.
+
+.TP
+.B Info
+Show detailed partition information. The summary information shown in the
+partition display area necessarily omits many details, such as the
+partitions' unique GUIDs and the partitions' sector-exact start and end
+points. The Info option displays this information for a single partition.
+
+.TP
+.B Load
+Load partition data from a backup file. This option is the reverse of the
+Backup option. Note that restoring partition data from anything but the
+original disk is not recommended.
+
+.TP
+.B naMe
+Change the GPT name of a partition. This name is encoded as a UTF\-16
+string, but proper entry and display of anything beyond basic ASCII values
+requires suitable locale and font support. For the most part, Linux ignores
+the partition name, but it may be important in some OSes. GPT fdisk sets a
+default name based on the partition type code. Note that the GPT partition
+name is different from the filesystem name, which is encoded in the
+filesystem's data structures. Note also that to activate this item by
+typing its alphabetic equivalent, you must use \fBM\fR, not the more
+obvious \fBN\fR, because the latter is used by the next option....
+
+.TP
+.B New
+Create a new partition. You enter a starting sector, a size, a type code,
+and a name. The start sector can be specified in absolute terms as a sector
+number or as a position measured in kibibytes (K), mebibytes (M), gibibytes
+(G), tebibytes (T), or pebibytes (P); for instance, \fI\fB40M\fR\fR
+specifies a position 40MiB from the start of the disk. You can specify
+locations relative to the start or end of the specified default range by
+preceding the number by a '+' symbol, as in \fI\fB+2G\fR\fR to specify a
+point 2GiB after the default start sector. The size value can use the K, M,
+G, T, and P suffixes, too. Pressing the Enter key with no input specifies
+the default value, which is the start of the largest available block for
+the start sector and the full available size for the size.
+
+.TP
+.B Quit
+Quit from the program \fIwithout saving your changes\fR.
+Use this option if you just wanted to view information or if you make a
+mistake and want to back out of all your changes.
+
+.TP
+.B Type
+Change a single partition's type code. You enter the type code using a
+two\-byte hexadecimal number. You may also enter a GUID directly, if you
+have one and \fBcgdisk\fR doesn't know it. If you don't know the type code
+for your partition, you can type \fBL\fR to see a list of known type codes.
+
+.TP
+.B Verify
+Verify disk. This option checks for a variety of problems, such as
+incorrect CRCs and mismatched main and backup data. This option does not
+automatically correct most problems, though; for that, you must use
+\fBgdisk\fR. If no problems are found, this command displays a summary of
+unallocated disk space.
+
+.TP
+.B Write
+Write data. Use this command to save your changes.
+
+.SH "BUGS"
+
+As of September 2011 (version 0.8.0), \fBcgdisk\fR should be considered
+beta software. Although the underlying partition manipulation code is much
+older, the \fBcgdisk\fR ncurses user interface is brand new with GPT fdisk
+version 0.8.0. Known bugs and limitations include:
+
+.TP
+.B *
+The program compiles correctly only on Linux, FreeBSD, and Mac OS X. In
+theory, it should compile under Windows if the Ncurses library for Windows
+is installed, but I have not tested this capability. Linux versions for
+x86\-64 (64\-bit), x86 (32\-bit), and PowerPC (32\-bit) have been tested,
+with the x86\-64 version having seen the most testing. Under FreeBSD,
+32\-bit (x86) and 64\-bit (x86\-64) versions have been tested. Only 32\-bit
+versions for Mac OS X has been tested by the author.
+
+.TP
+.B *
+The FreeBSD version of the program can't write changes to the partition
+table to a disk when existing partitions on that disk are mounted. (The
+same problem exists with many other FreeBSD utilities, such as
+\fBgpt\fR, \fBfdisk\fR, and \fBdd\fR.) This limitation can be overcome
+by typing \fBsysctl kern.geom.debugflags=16\fR at a shell prompt.
+
+.TP
+.B *
+The program can load only up to 128 partitions (4 primary partitions and
+124 logical partitions) when converting from MBR format. This limit can
+be raised by changing the \fI#define MAX_MBR_PARTS\fR line in the
+\fIbasicmbr.h\fR source code file and recompiling; however, such a change
+will require using a larger\-than\-normal partition table. (The limit
+of 128 partitions was chosen because that number equals the 128 partitions
+supported by the most common partition table size.)
+
+.TP
+.B *
+Converting from MBR format sometimes fails because of insufficient space at
+the start or (more commonly) the end of the disk. Resizing the partition
+table (using the 's' option in the experts' menu in \fBgdisk\fR) can
+sometimes overcome this problem; however, in extreme cases it may be
+necessary to resize a partition using GNU Parted or a similar tool prior to
+conversion with GPT fdisk.
+
+.TP
+.B *
+MBR conversions work only if the disk has correct LBA partition
+descriptors. These descriptors should be present on any disk over 8 GiB in
+size or on smaller disks partitioned with any but very ancient software.
+
+.TP
+.B *
+BSD disklabel support can create first and/or last partitions that overlap
+with the GPT data structures. This can sometimes be compensated by
+adjusting the partition table size, but in extreme cases the affected
+partition(s) may need to be deleted.
+
+.TP
+.B *
+Because of the highly variable nature of BSD disklabel structures,
+conversions from this form may be unreliable \-\- partitions may be dropped,
+converted in a way that creates overlaps with other partitions, or
+converted with incorrect start or end values. Use this feature with
+caution!
+
+.TP
+.B *
+Booting after converting an MBR or BSD disklabel disk is likely to be
+disrupted. Sometimes re\-installing a boot loader will fix the problem, but
+other times you may need to switch boot loaders. Except on EFI\-based
+platforms, Windows through at least Windows 7 doesn't support booting from
+GPT disks. Creating a hybrid MBR (using the 'h' option on the recovery &
+transformation menu in \fBgdisk\fR) or abandoning GPT in favor of MBR may
+be your only options in this case.
+
+.TP
+.B *
+The \fBcgdisk\fR Verify function and the partition type listing obtainable
+by typing \fIL\fR in the Type function (or when specifying a partition type
+while creating a new partition) both currently exit ncurses mode. This
+limitation is a minor cosmetic blemish that does not affect functionality.
+
+.SH "AUTHORS"
+Primary author: Roderick W. Smith (rodsmith@rodsbooks.com)
+
+Contributors:
+
+* Yves Blusseau (1otnwmz02@sneakemail.com)
+
+* David Hubbard (david.c.hubbard@gmail.com)
+
+* Justin Maggard (justin.maggard@netgear.com)
+
+* Dwight Schauer (dschauer@ti.com)
+
+* Florian Zumbiehl (florz@florz.de)
+
+
+.SH "SEE ALSO"
+\fBcfdisk (8)\fR,
+\fBfdisk (8)\fR,
+\fBgdisk (8)\fR,
+\fBmkfs (8)\fR,
+\fBparted (8)\fR,
+\fBsfdisk (8)\fR
+\fBsgdisk (8)\fR
+\fBfixparts (8)\fR
+
+\fIhttp://en.wikipedia.org/wiki/GUID_Partition_Table\fR
+
+\fIhttp://developer.apple.com/technotes/tn2006/tn2166.html\fR
+
+\fIhttp://www.rodsbooks.com/gdisk/\fR
+
+.SH "AVAILABILITY"
+The \fBcgdisk\fR command is part of the \fIGPT fdisk\fR package and is
+available from Rod Smith.
diff --git a/cgdisk.cc b/cgdisk.cc
new file mode 100644
index 0000000..1d52ee2
--- /dev/null
+++ b/cgdisk.cc
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) 2011 <Roderick W. Smith>
+
+ 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 2 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+*/
+
+/* This class implements an interactive curses-based interface atop the
+ GPTData class */
+
+#include <string>
+#include "gptcurses.h"
+
+using namespace std;
+
+#define MAX_OPTIONS 50
+
+int main(int argc, char *argv[]) {
+ string device = "";
+
+ if (!SizesOK())
+ exit(1);
+
+ switch (argc) {
+ case 1:
+ cout << "Type device filename, or press <Enter> to exit: ";
+ device = ReadString();
+ if (device.length() == 0)
+ exit(0);
+ break;
+ case 2: // basic usage
+ device = argv[1];
+ break;
+ default:
+ cerr << "Usage: " << argv[0] << " device_file\n";
+ exit(1);
+ break;
+ } // switch
+
+ GPTDataCurses theGPT;
+
+ if (theGPT.LoadPartitions(device)) {
+ if (theGPT.GetState() != use_gpt) {
+ Report("Warning! Non-GPT or damaged disk detected! This program will attempt to\n"
+ "convert to GPT form or repair damage to GPT data structures, but may not\n"
+ "succeed. Use gdisk or another disk repair tool if you have a damaged GPT\n"
+ "disk.");
+ } // if
+ theGPT.MainMenu();
+ } else {
+ Report("Could not load partitions from '" + device + "'! Aborting!");
+ } // if/else
+} // main
diff --git a/gdisk_test.sh b/gdisk_test.sh
new file mode 100755
index 0000000..216b310
--- /dev/null
+++ b/gdisk_test.sh
@@ -0,0 +1,377 @@
+#!/bin/bash
+# test gdisk and sgdisk by creating a dd file
+# Copyright (C) 2011 Guillaume Delacour <gui@iroqwa.org>
+#
+# 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 2 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#
+# Requires: coreutils (mktemp, dd) and 64M of disk space in /tmp (temp dd disk)
+#
+# This script test gdisk commands through the following scenario:
+# - Initialize a new GPT table
+# - Create a single Linux partition
+# - Change name of partition
+# - Change type of partition
+# - Backup to file the GPT table
+# - Delete the single partition
+# - Restore from backup file the GPT table
+# - Wipe the GPT table
+
+# TODO
+# Try to generate a wrong GPT table to detect problems (test --verify)
+# Create MBR partition table with fdisk and migrate it with gdisk
+
+GDISK_BIN=./gdisk
+SGDISK_BIN=./sgdisk
+
+OPT_CLEAR="o"
+OPT_NEW="n"
+OPT_CHANGE_NAME="c"
+OPT_CHANGE_TYPE="t"
+OPT_BACKUP="b"
+OPT_DELETE="d"
+OPT_ZAP="z"
+
+# temp disk for testing gdisk
+TEMP_DISK=$(mktemp)
+# 64 MiB
+TEMP_DISK_SIZE=65536
+
+# the test partition to create
+TEST_PART_TYPE="8300"
+TEST_PART_DEFAULT_NAME="Linux filesystem"
+
+# newname for the partition
+TEST_PART_NEWNAME=$(tr -dc "[:alpha:]" < /dev/urandom | head -c 8)
+# and new type (swap for example)
+TEST_PART_NEWTYPE="8200"
+
+# GPT data backup to filename
+GPT_BACKUP_FILENAME=$(mktemp)
+
+# Pretty print string (Red if FAILED or green if SUCCESS)
+# $1: string to pretty print
+pretty_print() {
+ if [ "$1" = "SUCCESS" ]
+ then
+ # green
+ color="32"
+ else
+ # red
+ color="31"
+ fi
+
+ printf "\033[0;${color}m**$1**\033[m $2\n"
+}
+
+# Verify that the partition exist and has the given type/name
+# $1: Partition type to verify (ex.: 8300)
+# $2: Partition name to verify (ex.: Linux filesystem)
+# $3: Text to print
+verify_part() {
+ partition=$($GDISK_BIN -l $TEMP_DISK | tail -n 1)
+ echo $partition | grep -q "$1[[:space:]]$2$"
+
+ if [ $? -eq 0 ]
+ then
+ pretty_print "SUCCESS" "$3"
+ else
+ pretty_print "FAILED" "$3"
+ exit 1
+ fi
+}
+
+
+#####################################
+# Get GUID of disk
+#####################################
+get_diskguid() {
+ DISK_GUID=$($GDISK_BIN -l $TEMP_DISK | grep "^Disk identifier (GUID):" | awk '{print $4}')
+ return $DISK_GUID
+}
+
+
+#####################################
+# Create a new empty table
+#####################################
+create_table() {
+ case $1 in
+ gdisk)
+ $GDISK_BIN $TEMP_DISK << EOF
+$OPT_CLEAR
+Y
+w
+Y
+EOF
+ ;;
+ sgdisk)
+ $SGDISK_BIN $TEMP_DISK -${OPT_CLEAR}
+ ;;
+ esac
+
+ # verify that the table is empty
+ # only the columns should appear in the table
+ verify_part "Code" "Name" "Create new empty GPT table"
+ echo ""
+}
+
+
+
+#####################################
+# First create a new partition
+#####################################
+create_partition() {
+ case $1 in
+ gdisk)
+ $GDISK_BIN $TEMP_DISK << EOF
+$OPT_NEW
+1
+
+
+$TEST_PART_TYPE
+w
+Y
+EOF
+ ;;
+
+ sgdisk)
+ $SGDISK_BIN $TEMP_DISK -${OPT_NEW}=1 -${OPT_CHANGE_NAME}=1:"${TEST_PART_DEFAULT_NAME}"
+ ;;
+ esac
+
+ verify_part "$TEST_PART_TYPE" "$TEST_PART_DEFAULT_NAME" "Create new partition"
+ echo ""
+}
+
+
+#####################################
+# Change name of partition
+#####################################
+change_partition_name() {
+ case $1 in
+ gdisk)
+ $GDISK_BIN $TEMP_DISK << EOF
+$OPT_CHANGE_NAME
+$TEST_PART_NEWNAME
+w
+Y
+EOF
+ ;;
+
+ sgdisk)
+ $SGDISK_BIN $TEMP_DISK -${OPT_CHANGE_NAME}=1:${TEST_PART_NEWNAME}
+ ;;
+ esac
+
+ verify_part "$TEST_PART_TYPE" "$TEST_PART_NEWNAME" "Change partition 1 name ($TEST_PART_DEFAULT_NAME -> $TEST_PART_NEWNAME)"
+ echo ""
+}
+
+
+change_partition_type() {
+#####################################
+# Change type of partition
+#####################################
+ case $1 in
+ gdisk)
+ $GDISK_BIN $TEMP_DISK << EOF
+$OPT_CHANGE_TYPE
+$TEST_PART_NEWTYPE
+w
+Y
+EOF
+ ;;
+
+ sgdisk)
+ $SGDISK_BIN $TEMP_DISK -${OPT_CHANGE_TYPE}=1:${TEST_PART_NEWTYPE}
+ ;;
+ esac
+
+ verify_part "$TEST_PART_NEWTYPE" "$TEST_PART_NEWNAME" "Change partition 1 type ($TEST_PART_TYPE -> $TEST_PART_NEWTYPE)"
+ echo ""
+}
+
+
+#####################################
+# Backup GPT data to file
+#####################################
+backup_table() {
+ case $1 in
+ gdisk)
+ $GDISK_BIN $TEMP_DISK << EOF
+$OPT_BACKUP
+$GPT_BACKUP_FILENAME
+q
+EOF
+echo ""
+ ;;
+
+ sgdisk)
+ $SGDISK_BIN $TEMP_DISK -${OPT_BACKUP}=${GPT_BACKUP_FILENAME}
+ ;;
+ esac
+
+ # if exist and not empty; we will test it after
+ if [ -s $GPT_BACKUP_FILENAME ]
+ then
+ pretty_print "SUCCESS" "GPT data backuped sucessfully"
+ else
+ pretty_print "FAILED" "Unable to create GPT backup file !"
+ exit 1
+ fi
+}
+
+
+#####################################
+# Now, we can delete the partition
+#####################################
+delete_partition() {
+ case $1 in
+ gdisk)
+ $GDISK_BIN $TEMP_DISK << EOF
+$OPT_DELETE
+w
+Y
+EOF
+ ;;
+
+ sgdisk)
+ $SGDISK_BIN $TEMP_DISK -${OPT_DELETE}=1
+ ;;
+ esac
+
+ # verify that the table is empty (just one partition):
+ # only the columns should appear in the table
+ verify_part "Code" "Name" "Delete partition 1"
+ echo ""
+}
+
+
+#####################################
+# Restore GPT table
+#####################################
+restore_table() {
+ $GDISK_BIN $TEMP_DISK << EOF
+r
+r
+l
+$GPT_BACKUP_FILENAME
+w
+Y
+EOF
+
+ verify_part "$TEST_PART_NEWTYPE" "$TEST_PART_NEWNAME" "Restore the GPT backup"
+ echo ""
+}
+
+
+#####################################
+# Change UID of disk
+#####################################
+change_disk_uid() {
+
+ # get UID of disk before changing it
+ GUID=get_diskguid
+
+
+ case $1 in
+ gdisk)
+ $GDISK_BIN $TEMP_DISK << EOF
+x
+g
+R
+w
+Y
+EOF
+ ;;
+
+ sgdisk)
+ $SGDISK_BIN $TEMP_DISK -U=R
+ ;;
+ esac
+
+ # get GUID after change
+ NEW_DISK_GUID=get_diskguid
+
+ # compare them
+ if [ "$DISK_GUID" != "$NEW_DISK_GUID" ]
+ then
+ pretty_print "SUCCESS" "GUID of disk has been sucessfully changed"
+ else
+ pretty_print "FAILED" "GUID of disk is the same as the previous one"
+ fi
+}
+
+#####################################
+# Wipe GPT table
+#####################################
+wipe_table() {
+ case $1 in
+ gdisk)
+ $GDISK_BIN $TEMP_DISK << EOF
+x
+$OPT_ZAP
+Y
+Y
+EOF
+ ;;
+
+ sgdisk)
+ $SGDISK_BIN $TEMP_DISK -${OPT_ZAP}
+ esac
+
+ # verify that the table is empty (just one partition):
+ # only the columns should appear in the table
+ verify_part "Code" "Name" "Wipe GPT table"
+ echo ""
+}
+
+
+###################################
+# Main
+###################################
+
+# create a file to simulate a real device
+dd if=/dev/zero of=$TEMP_DISK bs=1024 count=$TEMP_DISK_SIZE
+
+if [ -s $TEMP_DISK ]
+then
+ pretty_print "SUCCESS" "Temp disk sucessfully created"
+else
+ pretty_print "FAILED" "Unable to create temp disk !"
+ exit 1
+fi
+
+# test gdisk and sgdisk
+for binary in gdisk sgdisk
+do
+ echo ""
+ printf "\033[0;34m**Testing $binary binary**\033[m\n"
+ echo ""
+ create_table "$binary"
+ create_partition "$binary"
+ change_partition_name "$binary"
+ change_partition_type "$binary"
+ backup_table "$binary"
+ delete_partition "$binary"
+ restore_table # only with gdisk
+ change_disk_uid "$binary"
+ wipe_table "$binary"
+done
+
+# remove temp files
+rm -f $TEMP_DISK $GPT_BACKUP_FILENAME
+
+exit 0
diff --git a/gptcl.cc b/gptcl.cc
new file mode 100644
index 0000000..92aa601
--- /dev/null
+++ b/gptcl.cc
@@ -0,0 +1,534 @@
+/*
+ Implementation of GPTData class derivative with popt-based command
+ line processing
+ Copyright (C) 2010-2011 Roderick W. Smith
+
+ 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 2 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <string.h>
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <errno.h>
+#include <popt.h>
+#include "gptcl.h"
+
+GPTDataCL::GPTDataCL(void) {
+ attributeOperation = backupFile = partName = hybrids = newPartInfo = NULL;
+ mbrParts = twoParts = outDevice = typeCode = partGUID = diskGUID = NULL;
+ alignment = DEFAULT_ALIGNMENT;
+ deletePartNum = infoPartNum = largestPartNum = bsdPartNum = 0;
+ tableSize = GPT_SIZE;
+} // GPTDataCL constructor
+
+GPTDataCL::GPTDataCL(string filename) {
+} // GPTDataCL constructor with filename
+
+GPTDataCL::~GPTDataCL(void) {
+} // GPTDataCL destructor
+
+void GPTDataCL::LoadBackupFile(string backupFile, int &saveData, int &neverSaveData) {
+ if (LoadGPTBackup(backupFile) == 1) {
+ JustLooking(0);
+ saveData = 1;
+ } else {
+ saveData = 0;
+ neverSaveData = 1;
+ cerr << "Error loading backup file!\n";
+ } // else
+} //
+
+int GPTDataCL::DoOptions(int argc, char* argv[]) {
+ GPTData secondDevice;
+ int opt, numOptions = 0, saveData = 0, neverSaveData = 0;
+ int partNum = 0, saveNonGPT = 1, retval = 0, pretend = 0;
+ uint32_t gptPartNum = 0, low, high;
+ uint64_t startSector, endSector, sSize;
+ uint64_t temp; // temporary variable; free to use in any case
+ char *device;
+ string cmd, typeGUID, name;
+ PartType typeHelper;
+
+ struct poptOption theOptions[] =
+ {
+ {"attributes", 'A', POPT_ARG_STRING, &attributeOperation, 'A', "operate on partition attributes", "list|[partnum:show|or|nand|xor|=|set|clear|toggle|get[:bitnum|hexbitmask]]"},
+ {"set-alignment", 'a', POPT_ARG_INT, &alignment, 'a', "set sector alignment", "value"},
+ {"backup", 'b', POPT_ARG_STRING, &backupFile, 'b', "backup GPT to file", "file"},
+ {"change-name", 'c', POPT_ARG_STRING, &partName, 'c', "change partition's name", "partnum:name"},
+ {"recompute-chs", 'C', POPT_ARG_NONE, NULL, 'C', "recompute CHS values in protective/hybrid MBR", ""},
+ {"delete", 'd', POPT_ARG_INT, &deletePartNum, 'd', "delete a partition", "partnum"},
+ {"display-alignment", 'D', POPT_ARG_NONE, NULL, 'D', "show number of sectors per allocation block", ""},
+ {"move-second-header", 'e', POPT_ARG_NONE, NULL, 'e', "move second header to end of disk", ""},
+ {"end-of-largest", 'E', POPT_ARG_NONE, NULL, 'E', "show end of largest free block", ""},
+ {"first-in-largest", 'f', POPT_ARG_NONE, NULL, 'f', "show start of the largest free block", ""},
+ {"first-aligned-in-largest", 'F', POPT_ARG_NONE, NULL, 'F', "show start of the largest free block, aligned", ""},
+ {"mbrtogpt", 'g', POPT_ARG_NONE, NULL, 'g', "convert MBR to GPT", ""},
+ {"randomize-guids", 'G', POPT_ARG_NONE, NULL, 'G', "randomize disk and partition GUIDs", ""},
+ {"hybrid", 'h', POPT_ARG_STRING, &hybrids, 'h', "create hybrid MBR", "partnum[:partnum...]"},
+ {"info", 'i', POPT_ARG_INT, &infoPartNum, 'i', "show detailed information on partition", "partnum"},
+ {"load-backup", 'l', POPT_ARG_STRING, &backupFile, 'l', "load GPT backup from file", "file"},
+ {"list-types", 'L', POPT_ARG_NONE, NULL, 'L', "list known partition types", ""},
+ {"gpttombr", 'm', POPT_ARG_STRING, &mbrParts, 'm', "convert GPT to MBR", "partnum[:partnum...]"},
+ {"new", 'n', POPT_ARG_STRING, &newPartInfo, 'n', "create new partition", "partnum:start:end"},
+ {"largest-new", 'N', POPT_ARG_INT, &largestPartNum, 'N', "create largest possible new partition", "partnum"},
+ {"clear", 'o', POPT_ARG_NONE, NULL, 'o', "clear partition table", ""},
+ {"print", 'p', POPT_ARG_NONE, NULL, 'p', "print partition table", ""},
+ {"pretend", 'P', POPT_ARG_NONE, NULL, 'P', "make changes in memory, but don't write them", ""},
+ {"transpose", 'r', POPT_ARG_STRING, &twoParts, 'r', "transpose two partitions", "partnum:partnum"},
+ {"replicate", 'R', POPT_ARG_STRING, &outDevice, 'R', "replicate partition table", "device_filename"},
+ {"sort", 's', POPT_ARG_NONE, NULL, 's', "sort partition table entries", ""},
+ {"resize-table", 'S', POPT_ARG_INT, &tableSize, 'S', "resize partition table", "numparts"},
+ {"typecode", 't', POPT_ARG_STRING, &typeCode, 't', "change partition type code", "partnum:{hexcode|GUID}"},
+ {"transform-bsd", 'T', POPT_ARG_INT, &bsdPartNum, 'T', "transform BSD disklabel partition to GPT", "partnum"},
+ {"partition-guid", 'u', POPT_ARG_STRING, &partGUID, 'u', "set partition GUID", "partnum:guid"},
+ {"disk-guid", 'U', POPT_ARG_STRING, &diskGUID, 'U', "set disk GUID", "guid"},
+ {"verify", 'v', POPT_ARG_NONE, NULL, 'v', "check partition table integrity", ""},
+ {"version", 'V', POPT_ARG_NONE, NULL, 'V', "display version information", ""},
+ {"zap", 'z', POPT_ARG_NONE, NULL, 'z', "zap (destroy) GPT (but not MBR) data structures", ""},
+ {"zap-all", 'Z', POPT_ARG_NONE, NULL, 'Z', "zap (destroy) GPT and MBR data structures", ""},
+ POPT_AUTOHELP { NULL, 0, 0, NULL, 0 }
+ };
+
+ // Create popt context...
+ poptCon = poptGetContext(NULL, argc, (const char**) argv, theOptions, 0);
+
+ poptSetOtherOptionHelp(poptCon, " [OPTION...] <device>");
+
+ if (argc < 2) {
+ poptPrintUsage(poptCon, stderr, 0);
+ exit(1);
+ }
+
+ // Do one loop through the options to find the device filename and deal
+ // with options that don't require a device filename, to flag destructive
+ // (o, z, or Z) options, and to flag presence of an
+ while ((opt = poptGetNextOpt(poptCon)) > 0) {
+ switch (opt) {
+ case 'A':
+ cmd = GetString(attributeOperation, 1);
+ if (cmd == "list")
+ Attributes::ListAttributes();
+ break;
+ case 'L':
+ typeHelper.ShowAllTypes();
+ break;
+ case 'P':
+ pretend = 1;
+ break;
+ case 'V':
+ cout << "GPT fdisk (sgdisk) version " << GPTFDISK_VERSION << "\n\n";
+ break;
+ default:
+ break;
+ } // switch
+ numOptions++;
+ } // while
+
+ // Assume first non-option argument is the device filename....
+ device = (char*) poptGetArg(poptCon);
+ poptResetContext(poptCon);
+
+ if (device != NULL) {
+ JustLooking(); // reset as necessary
+ BeQuiet(); // Tell called functions to be less verbose & interactive
+ if (LoadPartitions((string) device)) {
+ if ((WhichWasUsed() == use_mbr) || (WhichWasUsed() == use_bsd))
+ saveNonGPT = 0; // flag so we don't overwrite unless directed to do so
+ sSize = GetBlockSize();
+ while ((opt = poptGetNextOpt(poptCon)) > 0) {
+ switch (opt) {
+ case 'A': {
+ if (cmd != "list") {
+ partNum = (int) GetInt(attributeOperation, 1) - 1;
+ if ((partNum >= 0) && (partNum < (int) GetNumParts())) {
+ switch (ManageAttributes(partNum, GetString(attributeOperation, 2),
+ GetString(attributeOperation, 3))) {
+ case -1:
+ saveData = 0;
+ neverSaveData = 1;
+ break;
+ case 1:
+ JustLooking(0);
+ saveData = 1;
+ break;
+ default:
+ break;
+ } // switch
+ } else {
+ cerr << "Error: Invalid partition number " << partNum + 1 << "\n";
+ saveData = 0;
+ neverSaveData = 1;
+ } // if/else reasonable partition #
+ } // if (cmd != "list")
+ break;
+ } // case 'A':
+ case 'a':
+ SetAlignment(alignment);
+ break;
+ case 'b':
+ SaveGPTBackup(backupFile);
+ free(backupFile);
+ break;
+ case 'c':
+ JustLooking(0);
+ partNum = (int) GetInt(partName, 1) - 1;
+ name = GetString(partName, 2);
+ if (SetName(partNum, (UnicodeString) name.c_str())) {
+ saveData = 1;
+ } else {
+ cerr << "Unable to set partition " << partNum + 1
+ << "'s name to '" << GetString(partName, 2) << "'!\n";
+ neverSaveData = 1;
+ } // if/else
+ free(partName);
+ break;
+ case 'C':
+ JustLooking(0);
+ RecomputeCHS();
+ saveData = 1;
+ break;
+ case 'd':
+ JustLooking(0);
+ if (DeletePartition(deletePartNum - 1) == 0) {
+ cerr << "Error " << errno << " deleting partition!\n";
+ neverSaveData = 1;
+ } else saveData = 1;
+ break;
+ case 'D':
+ cout << GetAlignment() << "\n";
+ break;
+ case 'e':
+ JustLooking(0);
+ MoveSecondHeaderToEnd();
+ saveData = 1;
+ break;
+ case 'E':
+ cout << FindLastInFree(FindFirstInLargest()) << "\n";
+ break;
+ case 'f':
+ cout << FindFirstInLargest() << "\n";
+ break;
+ case 'F':
+ temp = FindFirstInLargest();
+ Align(&temp);
+ cout << temp << "\n";
+ break;
+ case 'g':
+ JustLooking(0);
+ saveData = 1;
+ saveNonGPT = 1;
+ break;
+ case 'G':
+ JustLooking(0);
+ saveData = 1;
+ RandomizeGUIDs();
+ break;
+ case 'h':
+ JustLooking(0);
+ if (BuildMBR(hybrids, 1) == 1)
+ saveData = 1;
+ break;
+ case 'i':
+ ShowPartDetails(infoPartNum - 1);
+ break;
+ case 'l':
+ LoadBackupFile(backupFile, saveData, neverSaveData);
+ free(backupFile);
+ break;
+ case 'L':
+ break;
+ case 'm':
+ JustLooking(0);
+ if (BuildMBR(mbrParts, 0) == 1) {
+ if (!pretend) {
+ if (SaveMBR()) {
+ DestroyGPT();
+ } else
+ cerr << "Problem saving MBR!\n";
+ } // if
+ saveNonGPT = 0;
+ pretend = 1; // Not really, but works around problem if -g is used with this...
+ saveData = 0;
+ } // if
+ break;
+ case 'n':
+ JustLooking(0);
+ partNum = (int) GetInt(newPartInfo, 1) - 1;
+ if (partNum < 0)
+ partNum = FindFirstFreePart();
+ low = FindFirstInLargest();
+ high = FindLastInFree(low);
+ startSector = IeeeToInt(GetString(newPartInfo, 2), sSize, low, high, low);
+ endSector = IeeeToInt(GetString(newPartInfo, 3), sSize, startSector, high, high);
+ if (CreatePartition(partNum, startSector, endSector)) {
+ saveData = 1;
+ } else {
+ cerr << "Could not create partition " << partNum + 1 << " from "
+ << startSector << " to " << endSector << "\n";
+ neverSaveData = 1;
+ } // if/else
+ free(newPartInfo);
+ break;
+ case 'N':
+ JustLooking(0);
+ startSector = FindFirstInLargest();
+ endSector = FindLastInFree(startSector);
+ if (largestPartNum < 0)
+ largestPartNum = FindFirstFreePart();
+ if (CreatePartition(largestPartNum - 1, startSector, endSector)) {
+ saveData = 1;
+ } else {
+ cerr << "Could not create partition " << largestPartNum << " from "
+ << startSector << " to " << endSector << "\n";
+ neverSaveData = 1;
+ } // if/else
+ break;
+ case 'o':
+ JustLooking(0);
+ ClearGPTData();
+ saveData = 1;
+ break;
+ case 'p':
+ DisplayGPTData();
+ break;
+ case 'P':
+ pretend = 1;
+ break;
+ case 'r':
+ JustLooking(0);
+ uint64_t p1, p2;
+ p1 = GetInt(twoParts, 1) - 1;
+ p2 = GetInt(twoParts, 2) - 1;
+ if (SwapPartitions((uint32_t) p1, (uint32_t) p2) == 0) {
+ neverSaveData = 1;
+ cerr << "Cannot swap partitions " << p1 + 1 << " and " << p2 + 1 << "\n";
+ } else saveData = 1;
+ break;
+ case 'R':
+ secondDevice = *this;
+ secondDevice.SetDisk(outDevice);
+ secondDevice.JustLooking(0);
+ if (!secondDevice.SaveGPTData(1))
+ retval = 8;
+ break;
+ case 's':
+ JustLooking(0);
+ SortGPT();
+ saveData = 1;
+ break;
+ case 'S':
+ JustLooking(0);
+ if (SetGPTSize(tableSize) == 0)
+ neverSaveData = 1;
+ else
+ saveData = 1;
+ break;
+ case 't':
+ JustLooking(0);
+ partNum = (int) GetInt(typeCode, 1) - 1;
+ typeHelper = GetString(typeCode, 2);
+ if ((typeHelper != (GUIDData) "00000000-0000-0000-0000-000000000000") &&
+ (ChangePartType(partNum, typeHelper))) {
+ saveData = 1;
+ } else {
+ cerr << "Could not change partition " << partNum + 1
+ << "'s type code to " << GetString(typeCode, 2) << "!\n";
+ neverSaveData = 1;
+ } // if/else
+ free(typeCode);
+ break;
+ case 'T':
+ JustLooking(0);
+ XFormDisklabel(bsdPartNum - 1);
+ saveData = 1;
+ break;
+ case 'u':
+ JustLooking(0);
+ saveData = 1;
+ gptPartNum = (int) GetInt(partGUID, 1) - 1;
+ SetPartitionGUID(gptPartNum, GetString(partGUID, 2).c_str());
+ break;
+ case 'U':
+ JustLooking(0);
+ saveData = 1;
+ SetDiskGUID(diskGUID);
+ break;
+ case 'v':
+ Verify();
+ break;
+ case 'z':
+ if (!pretend) {
+ DestroyGPT();
+ } // if
+ saveNonGPT = 0;
+ saveData = 0;
+ break;
+ case 'Z':
+ if (!pretend) {
+ DestroyGPT();
+ DestroyMBR();
+ } // if
+ saveNonGPT = 0;
+ saveData = 0;
+ break;
+ default:
+ cerr << "Unknown option (-" << opt << ")!\n";
+ break;
+ } // switch
+ } // while
+ } else { // if loaded OK
+ poptResetContext(poptCon);
+ // Do a few types of operations even if there are problems....
+ while ((opt = poptGetNextOpt(poptCon)) > 0) {
+ switch (opt) {
+ case 'l':
+ LoadBackupFile(backupFile, saveData, neverSaveData);
+ cout << "Information: Loading backup partition table; will override earlier problems!\n";
+ free(backupFile);
+ retval = 0;
+ break;
+ case 'o':
+ JustLooking(0);
+ ClearGPTData();
+ saveData = 1;
+ cout << "Information: Creating fresh partition table; will override earlier problems!\n";
+ retval = 0;
+ break;
+ case 'v':
+ cout << "Verification may miss some problems or report too many!\n";
+ Verify();
+ break;
+ case 'z':
+ if (!pretend) {
+ DestroyGPT();
+ } // if
+ saveNonGPT = 0;
+ saveData = 0;
+ break;
+ case 'Z':
+ if (!pretend) {
+ DestroyGPT();
+ DestroyMBR();
+ } // if
+ saveNonGPT = 0;
+ saveData = 0;
+ break;
+ } // switch
+ } // while
+ retval = 2;
+ } // if/else loaded OK
+ if ((saveData) && (!neverSaveData) && (saveNonGPT) && (!pretend)) {
+ SaveGPTData(1);
+ }
+ if (saveData && (!saveNonGPT)) {
+ cout << "Non-GPT disk; not saving changes. Use -g to override.\n";
+ retval = 3;
+ } // if
+ if (neverSaveData) {
+ cerr << "Error encountered; not saving changes.\n";
+ retval = 4;
+ } // if
+ } // if (device != NULL)
+ poptFreeContext(poptCon);
+ return retval;
+} // GPTDataCL::DoOptions()
+
+// Create a hybrid or regular MBR from GPT data structures
+int GPTDataCL::BuildMBR(char* argument, int isHybrid) {
+ int numParts, allOK = 1, i, origPartNum;
+ MBRPart newPart;
+ BasicMBRData newMBR;
+
+ if (argument != NULL) {
+ numParts = CountColons(argument) + 1;
+ if (numParts <= (4 - isHybrid)) {
+ newMBR.SetDisk(GetDisk());
+ for (i = 0; i < numParts; i++) {
+ origPartNum = GetInt(argument, i + 1) - 1;
+ if (IsUsedPartNum(origPartNum)) {
+ newPart.SetInclusion(PRIMARY);
+ newPart.SetLocation(operator[](origPartNum).GetFirstLBA(),
+ operator[](origPartNum).GetLengthLBA());
+ newPart.SetStatus(0);
+ newPart.SetType((uint8_t)(operator[](origPartNum).GetHexType() / 0x0100));
+ newMBR.AddPart(i + isHybrid, newPart);
+ } else {
+ cerr << "Partition " << origPartNum << " does not exist! Aborting operation!\n";
+ allOK = 0;
+ } // if/else
+ } // for
+ if (isHybrid) {
+ newPart.SetInclusion(PRIMARY);
+ newPart.SetLocation(1, newMBR.FindLastInFree(1));
+ newPart.SetStatus(0);
+ newPart.SetType(0xEE);
+ newMBR.AddPart(0, newPart);
+ } // if
+ SetProtectiveMBR(newMBR);
+ } else allOK = 0;
+ } else allOK = 0;
+ if (!allOK)
+ cerr << "Problem creating MBR!\n";
+ return allOK;
+} // GPTDataCL::BuildMBR()
+
+// Returns the number of colons in argument string, ignoring the
+// first character (thus, a leading colon is ignored, as GetString()
+// does).
+int CountColons(char* argument) {
+ int num = 0;
+
+ while ((argument[0] != '\0') && (argument = strchr(&argument[1], ':')))
+ num++;
+
+ return num;
+} // GPTDataCL::CountColons()
+
+// Extract integer data from argument string, which should be colon-delimited
+uint64_t GetInt(const string & argument, int itemNum) {
+ uint64_t retval;
+
+ istringstream inString(GetString(argument, itemNum));
+ inString >> retval;
+ return retval;
+} // GPTDataCL::GetInt()
+
+// Extract string data from argument string, which should be colon-delimited
+// If string begins with a colon, that colon is skipped in the counting. If an
+// invalid itemNum is specified, returns an empty string.
+string GetString(string argument, int itemNum) {
+ size_t startPos = 0, endPos = 0;
+ string retVal = "";
+ int foundLast = 0;
+ int numFound = 0;
+
+ if (argument[0] == ':')
+ argument.erase(0, 1);
+ while ((numFound < itemNum) && (!foundLast)) {
+ endPos = argument.find(':', startPos);
+ numFound++;
+ if (endPos == string::npos) {
+ foundLast = 1;
+ endPos = argument.length();
+ } else if (numFound < itemNum) {
+ startPos = endPos + 1;
+ } // if/elseif
+ } // while
+ if ((numFound == itemNum) && (numFound > 0))
+ retVal = argument.substr(startPos, endPos - startPos);
+
+ return retVal;
+} // GetString()
diff --git a/gptcl.h b/gptcl.h
new file mode 100644
index 0000000..1e6148d
--- /dev/null
+++ b/gptcl.h
@@ -0,0 +1,53 @@
+/*
+ Implementation of GPTData class derivative with popt-based command
+ line processing
+ Copyright (C) 2010-2011 Roderick W. Smith
+
+ 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 2 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+*/
+
+#ifndef __GPTCL_H
+#define __GPTCL_H
+
+#include "gpt.h"
+#include <popt.h>
+
+using namespace std;
+
+class GPTDataCL : public GPTData {
+ protected:
+ // Following are variables associated with popt parameters....
+ char *attributeOperation, *backupFile, *partName, *hybrids;
+ char *newPartInfo, *mbrParts, *twoParts, *outDevice, *typeCode;
+ char *partGUID, *diskGUID;
+ int alignment, deletePartNum, infoPartNum, largestPartNum, bsdPartNum;
+ uint32_t tableSize;
+
+ poptContext poptCon;
+ int BuildMBR(char* argument, int isHybrid);
+ public:
+ GPTDataCL(void);
+ GPTDataCL(string filename);
+ ~GPTDataCL(void);
+ void LoadBackupFile(string backupFile, int &saveData, int &neverSaveData);
+ int DoOptions(int argc, char* argv[]);
+}; // class GPTDataCL
+
+int CountColons(char* argument);
+uint64_t GetInt(const string & argument, int itemNum);
+string GetString(string argument, int itemNum);
+
+#endif
diff --git a/gptcurses.cc b/gptcurses.cc
new file mode 100644
index 0000000..b8a0371
--- /dev/null
+++ b/gptcurses.cc
@@ -0,0 +1,808 @@
+/*
+ * Implementation of GPTData class derivative with curses-based text-mode
+ * interaction
+ * Copyright (C) 2011 Roderick W. Smith
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <ncurses.h>
+#include "gptcurses.h"
+#include "support.h"
+
+using namespace std;
+
+// # of lines to reserve for general information and headers (RESERVED_TOP)
+// and for options and messages (RESERVED_BOTTOM)
+#define RESERVED_TOP 7
+#define RESERVED_BOTTOM 5
+
+int GPTDataCurses::numInstances = 0;
+
+GPTDataCurses::GPTDataCurses(void) {
+ if (numInstances > 0) {
+ refresh();
+ } else {
+ initscr();
+ cbreak();
+ noecho();
+ intrflush(stdscr, false);
+ keypad(stdscr, true);
+ nonl();
+ numInstances++;
+ } // if/else
+ firstSpace = NULL;
+ lastSpace = NULL;
+ currentSpace = NULL;
+ currentSpaceNum = -1;
+ whichOptions = ""; // current set of options
+ currentKey = 'b'; // currently selected option
+} // GPTDataCurses constructor
+
+GPTDataCurses::~GPTDataCurses(void) {
+ numInstances--;
+ if ((numInstances == 0) && !isendwin())
+ endwin();
+} // GPTDataCurses destructor
+
+/************************************************
+ * *
+ * Functions relating to Spaces data structures *
+ * *
+ ************************************************/
+
+void GPTDataCurses::EmptySpaces(void) {
+ Space *trash;
+
+ while (firstSpace != NULL) {
+ trash = firstSpace;
+ firstSpace = firstSpace->nextSpace;
+ delete trash;
+ } // if
+ numSpaces = 0;
+ lastSpace = NULL;
+} // GPTDataCurses::EmptySpaces()
+
+// Create Spaces from partitions. Does NOT creates Spaces to represent
+// unpartitioned space on the disk.
+// Returns the number of Spaces created.
+int GPTDataCurses::MakeSpacesFromParts(void) {
+ uint i;
+ Space *tempSpace;
+
+ EmptySpaces();
+ for (i = 0; i < numParts; i++) {
+ if (partitions[i].IsUsed()) {
+ tempSpace = new Space;
+ tempSpace->firstLBA = partitions[i].GetFirstLBA();
+ tempSpace->lastLBA = partitions[i].GetLastLBA();
+ tempSpace->origPart = &partitions[i];
+ tempSpace->partNum = (int) i;
+ LinkToEnd(tempSpace);
+ } // if
+ } // for
+ return numSpaces;
+} // GPTDataCurses::MakeSpacesFromParts()
+
+// Add a single empty Space to the current Spaces linked list and sort the result....
+void GPTDataCurses::AddEmptySpace(uint64_t firstLBA, uint64_t lastLBA) {
+ Space *tempSpace;
+
+ tempSpace = new Space;
+ tempSpace->firstLBA = firstLBA;
+ tempSpace->lastLBA = lastLBA;
+ tempSpace->origPart = &emptySpace;
+ tempSpace->partNum = -1;
+ LinkToEnd(tempSpace);
+ SortSpaces();
+} // GPTDataCurses::AddEmptySpace();
+
+// Add Spaces to represent the unallocated parts of the partition table.
+// Returns the number of Spaces added.
+int GPTDataCurses::AddEmptySpaces(void) {
+ int numAdded = 0;
+ Space *current;
+
+ SortSpaces();
+ if (firstSpace == NULL) {
+ AddEmptySpace(GetFirstUsableLBA(), GetLastUsableLBA());
+ numAdded++;
+ } else {
+ current = firstSpace;
+ while ((current != NULL) /* && (current->partNum != -1) */ ) {
+ if ((current == firstSpace) && (current->firstLBA > GetFirstUsableLBA())) {
+ AddEmptySpace(GetFirstUsableLBA(), current->firstLBA - 1);
+ numAdded++;
+ } // if
+ if ((current == lastSpace) && (current->lastLBA < GetLastUsableLBA())) {
+ AddEmptySpace(current->lastLBA + 1, GetLastUsableLBA());
+ numAdded++;
+ } // if
+ if ((current->prevSpace != NULL) && (current->prevSpace->lastLBA < (current->firstLBA - 1))) {
+ AddEmptySpace(current->prevSpace->lastLBA + 1, current->firstLBA - 1);
+ numAdded++;
+ } // if
+ current = current->nextSpace;
+ } // while
+ } // if/else
+ return numAdded;
+} // GPTDataCurses::AddEmptySpaces()
+
+// Remove the specified Space from the linked list and set its previous and
+// next pointers to NULL.
+void GPTDataCurses::UnlinkSpace(Space *theSpace) {
+ if (theSpace != NULL) {
+ if (theSpace->prevSpace != NULL)
+ theSpace->prevSpace->nextSpace = theSpace->nextSpace;
+ if (theSpace->nextSpace != NULL)
+ theSpace->nextSpace->prevSpace = theSpace->prevSpace;
+ if (theSpace == firstSpace)
+ firstSpace = theSpace->nextSpace;
+ if (theSpace == lastSpace)
+ lastSpace = theSpace->prevSpace;
+ theSpace->nextSpace = NULL;
+ theSpace->prevSpace = NULL;
+ numSpaces--;
+ } // if
+} // GPTDataCurses::UnlinkSpace
+
+// Link theSpace to the end of the current linked list.
+void GPTDataCurses::LinkToEnd(Space *theSpace) {
+ if (lastSpace == NULL) {
+ firstSpace = lastSpace = theSpace;
+ theSpace->nextSpace = NULL;
+ theSpace->prevSpace = NULL;
+ } else {
+ theSpace->prevSpace = lastSpace;
+ theSpace->nextSpace = NULL;
+ lastSpace->nextSpace = theSpace;
+ lastSpace = theSpace;
+ } // if/else
+ numSpaces++;
+} // GPTDataCurses::LinkToEnd()
+
+// Sort spaces into ascending order by on-disk position.
+void GPTDataCurses::SortSpaces(void) {
+ Space *oldFirst, *oldLast, *earliest = NULL, *current = NULL;
+
+ oldFirst = firstSpace;
+ oldLast = lastSpace;
+ firstSpace = lastSpace = NULL;
+ while (oldFirst != NULL) {
+ current = earliest = oldFirst;
+ while (current != NULL) {
+ if (current->firstLBA < earliest->firstLBA)
+ earliest = current;
+ current = current->nextSpace;
+ } // while
+ if (oldFirst == earliest)
+ oldFirst = earliest->nextSpace;
+ if (oldLast == earliest)
+ oldLast = earliest->prevSpace;
+ UnlinkSpace(earliest);
+ LinkToEnd(earliest);
+ } // while
+} // GPTDataCurses::SortSpaces()
+
+// Identify the spaces on the disk, a "space" being defined as a partition
+// or an empty gap between, before, or after partitions. The spaces are
+// presented to users in the main menu display.
+void GPTDataCurses::IdentifySpaces(void) {
+ MakeSpacesFromParts();
+ AddEmptySpaces();
+} // GPTDataCurses::IdentifySpaces()
+
+/**************************
+ * *
+ * Data display functions *
+ * *
+ **************************/
+
+// Display a single Space on line # lineNum.
+// Returns a pointer to the space being displayed
+Space* GPTDataCurses::ShowSpace(int spaceNum, int lineNum) {
+ Space *space;
+ int i = 0;
+ char temp[40];
+
+ space = firstSpace;
+ while ((space != NULL) && (i < spaceNum)) {
+ space = space->nextSpace;
+ i++;
+ } // while
+ if ((space != NULL) && (lineNum < (LINES - 5))) {
+ ClearLine(lineNum);
+ if (space->partNum == -1) { // space is empty
+ move(lineNum, 12);
+ printw(BytesToIeee((space->lastLBA - space->firstLBA + 1), blockSize).c_str());
+ move(lineNum, 24);
+ printw("free space");
+ } else { // space holds a partition
+ move(lineNum, 3);
+ printw("%d", space->partNum + 1);
+ move(lineNum, 12);
+ printw(BytesToIeee((space->lastLBA - space->firstLBA + 1), blockSize).c_str());
+ move(lineNum, 24);
+ printw(space->origPart->GetTypeName().c_str());
+ move(lineNum, 50);
+ #ifdef USE_UTF16
+ space->origPart->GetDescription().extract(0, 39, temp, 39);
+ printw(temp);
+ #else
+ printw(space->origPart->GetDescription().c_str());
+ #endif
+ } // if/else
+ } // if
+ return space;
+} // GPTDataCurses::ShowSpace
+
+// Display the partitions, being sure that the space #selected is displayed
+// and highlighting that space.
+// Returns the number of the space being shown (should be selected, but will
+// be -1 if something weird happens)
+int GPTDataCurses::DisplayParts(int selected) {
+ int lineNum = 5, i = 0, retval = -1, numToShow, pageNum;
+ string theLine;
+
+ move(lineNum++, 0);
+ theLine = "Part. # Size Partition Type Partition Name";
+ printw(theLine.c_str());
+ move(lineNum++, 0);
+ theLine = "----------------------------------------------------------------";
+ printw(theLine.c_str());
+ numToShow = LINES - RESERVED_TOP - RESERVED_BOTTOM;
+ pageNum = selected / numToShow;
+ for (i = pageNum * numToShow; i <= (pageNum + 1) * numToShow - 1; i++) {
+ if (i < numSpaces) { // real space; show it
+ if (i == selected) {
+ attron(A_REVERSE);
+ currentSpaceNum = i;
+ currentSpace = ShowSpace(i, lineNum++);
+ attroff(A_REVERSE);
+ DisplayOptions(i);
+ retval = selected;
+ } else {
+ ShowSpace(i, lineNum++);
+ }
+ } else { // blank in display
+ ClearLine(lineNum++);
+ } // if/else
+ } // for
+ refresh();
+ return retval;
+} // GPTDataCurses::DisplayParts()
+
+/**********************************************
+ * *
+ * Functions corresponding to main menu items *
+ * *
+ **********************************************/
+
+// Delete the specified partition and re-detect partitions and spaces....
+void GPTDataCurses::DeletePartition(int partNum) {
+ if (!GPTData::DeletePartition(partNum))
+ Report("Could not delete partition!");
+ IdentifySpaces();
+ if (currentSpaceNum >= numSpaces) {
+ currentSpaceNum = numSpaces - 1;
+ currentSpace = lastSpace;
+ } // if
+} // GPTDataCurses::DeletePartition()
+
+// Displays information on the specified partition
+void GPTDataCurses::ShowInfo(int partNum) {
+ uint64_t size;
+ char temp[NAME_SIZE / 2 + 1];
+
+ clear();
+ move(2, (COLS - 29) / 2);
+ printw("Information for partition #%d\n\n", partNum + 1);
+ printw("Partition GUID code: %s (%s)\n", partitions[partNum].GetType().AsString().c_str(),
+ partitions[partNum].GetTypeName().c_str());
+ printw("Partition unique GUID: %s\n", partitions[partNum].GetUniqueGUID().AsString().c_str());
+ printw("First sector: %lld (at %s)\n", partitions[partNum].GetFirstLBA(),
+ BytesToIeee(partitions[partNum].GetFirstLBA(), blockSize).c_str());
+ printw("Last sector: %lld (at %s)\n", partitions[partNum].GetLastLBA(),
+ BytesToIeee(partitions[partNum].GetLastLBA(), blockSize).c_str());
+ size = partitions[partNum].GetLastLBA() - partitions[partNum].GetFirstLBA();
+ printw("Partition size: %lld sectors (%s)\n", size, BytesToIeee(size, blockSize).c_str());
+ printw("Attribute flags: %016x\n", partitions[partNum].GetAttributes().GetAttributes());
+ #ifdef USE_UTF16
+ partitions[partNum].GetDescription().extract(0, NAME_SIZE / 2, temp, NAME_SIZE / 2);
+ printw("Partition name: '%s'\n", temp);
+ #else
+ printw("Partition name: '%s'\n", partitions[partNum].GetDescription().c_str());
+ #endif
+ PromptToContinue();
+} // GPTDataCurses::ShowInfo()
+
+// Prompt for and change a partition's name....
+void GPTDataCurses::ChangeName(int partNum) {
+ char temp[NAME_SIZE / 2 + 1];
+
+ if (ValidPartNum(partNum)) {
+ move(LINES - 4, 0);
+ clrtobot();
+ move(LINES - 4, 0);
+ #ifdef USE_UTF16
+ partitions[partNum].GetDescription().extract(0, NAME_SIZE / 2, temp, NAME_SIZE / 2);
+ printw("Current partition name is '%s'\n", temp);
+ #else
+ printw("Current partition name is '%s'\n", partitions[partNum].GetDescription().c_str());
+ #endif
+ printw("Enter new partition name, or <Enter> to use the current name:\n");
+ echo();
+ getnstr(temp, NAME_SIZE / 2);
+ partitions[partNum].SetName((string) temp);
+ noecho();
+ } // if
+} // GPTDataCurses::ChangeName()
+
+// Change the partition's type code....
+void GPTDataCurses::ChangeType(int partNum) {
+ char temp[80] = "L\0";
+ PartType tempType;
+
+ echo();
+ do {
+ move(LINES - 4, 0);
+ clrtobot();
+ move(LINES - 4, 0);
+ printw("Current type is %04x (%s)\n", partitions[partNum].GetType().GetHexType(), partitions[partNum].GetTypeName().c_str());
+ printw("Hex code or GUID (L to show codes, Enter = %04x): ", partitions[partNum].GetType().GetHexType());
+ getnstr(temp, 79);
+ if ((temp[0] == 'L') || (temp[0] == 'l')) {
+ ShowTypes();
+ } else {
+ if (temp[0] == '\0')
+ tempType = partitions[partNum].GetType().GetHexType();
+ tempType = temp;
+ partitions[partNum].SetType(tempType);
+ } // if
+ } while ((temp[0] == 'L') || (temp[0] == 'l') || (partitions[partNum].GetType() == (GUIDData) "0x0000"));
+ noecho();
+} // GPTDataCurses::ChangeType
+
+// Sets the partition alignment value
+void GPTDataCurses::SetAlignment(void) {
+ int alignment;
+
+ move(LINES - 4, 0);
+ clrtobot();
+ printw("Current partition alignment, in sectors, is %d.", GetAlignment());
+ do {
+ move(LINES - 3, 0);
+ printw("Type new alignment value, in sectors: ");
+ echo();
+ scanw("%d", &alignment);
+ noecho();
+ } while ((alignment == 0) || (alignment > MAX_ALIGNMENT));
+ GPTData::SetAlignment(alignment);
+} // GPTDataCurses::SetAlignment()
+
+// Verify the data structures. Note that this function leaves curses mode and
+// relies on the underlying GPTData::Verify() function to report on problems
+void GPTDataCurses::Verify(void) {
+ char junk;
+
+ def_prog_mode();
+ endwin();
+ GPTData::Verify();
+ cout << "\nPress the <Enter> key to continue: ";
+ cin.get(junk);
+ reset_prog_mode();
+ refresh();
+} // GPTDataCurses::Verify()
+
+// Create a new partition in the space pointed to by currentSpace.
+void GPTDataCurses::MakeNewPart(void) {
+ uint64_t size, newFirstLBA = 0, newLastLBA = 0;
+ int partNum;
+ char inLine[80];
+
+ move(LINES - 4, 0);
+ clrtobot();
+ while ((newFirstLBA < currentSpace->firstLBA) || (newFirstLBA > currentSpace->lastLBA)) {
+ newFirstLBA = currentSpace->firstLBA;
+ move(LINES - 4, 0);
+ clrtoeol();
+ newFirstLBA = currentSpace->firstLBA;
+ Align(&newFirstLBA);
+ printw("First sector (%lld-%lld, default = %lld): ", newFirstLBA, currentSpace->lastLBA, newFirstLBA);
+ echo();
+ getnstr(inLine, 79);
+ noecho();
+ newFirstLBA = IeeeToInt(inLine, blockSize, currentSpace->firstLBA, currentSpace->lastLBA, newFirstLBA);
+ Align(&newFirstLBA);
+ } // while
+ size = currentSpace->lastLBA - newFirstLBA + 1;
+ while ((newLastLBA > currentSpace->lastLBA) || (newLastLBA < newFirstLBA)) {
+ move(LINES - 3, 0);
+ clrtoeol();
+ printw("Size in sectors or {KMGTP} (default = %lld): ", size);
+ echo();
+ getnstr(inLine, 79);
+ noecho();
+ newLastLBA = newFirstLBA + IeeeToInt(inLine, blockSize, 1, size, size) - 1;
+ } // while
+ partNum = FindFirstFreePart();
+ if (CreatePartition(partNum, newFirstLBA, newLastLBA)) { // created OK; set type code & name....
+ ChangeType(partNum);
+ ChangeName(partNum);
+ } else {
+ Report("Error creating partition!");
+ } // if/else
+} // GPTDataCurses::MakeNewPart()
+
+// Prompt user for permission to save data and, if it's given, do so!
+void GPTDataCurses::SaveData(void) {
+ string answer = "";
+ char inLine[80];
+
+ move(LINES - 4, 0);
+ clrtobot();
+ move (LINES - 2, 14);
+ printw("Warning!! This may destroy data on your disk!");
+ echo();
+ while ((answer != "yes") && (answer != "no")) {
+ move (LINES - 4, 2);
+ printw("Are you sure you want to write the partition table to disk? (yes or no): ");
+ getnstr(inLine, 79);
+ answer = inLine;
+ if ((answer != "yes") && (answer != "no")) {
+ move(LINES - 2, 0);
+ clrtoeol();
+ move(LINES - 2, 14);
+ printw("Please enter 'yes' or 'no'");
+ } // if
+ } // while()
+ noecho();
+ if (answer == "yes") {
+ if (SaveGPTData(1)) {
+ if (!myDisk.DiskSync())
+ Report("The kernel may be using the old partition table. Reboot to use the new\npartition table!");
+ } else {
+ Report("Problem saving data! Your partition table may be damaged!");
+ }
+ }
+} // GPTDataCurses::SaveData()
+
+// Back up the partition table, prompting user for a filename....
+void GPTDataCurses::Backup(void) {
+ char inLine[80];
+
+ ClearBottom();
+ move(LINES - 3, 0);
+ printw("Enter backup filename to save: ");
+ echo();
+ getnstr(inLine, 79);
+ noecho();
+ SaveGPTBackup(inLine);
+} // GPTDataCurses::Backup()
+
+// Load a GPT backup from a file
+void GPTDataCurses::LoadBackup(void) {
+ char inLine[80];
+
+ ClearBottom();
+ move(LINES - 3, 0);
+ printw("Enter backup filename to load: ");
+ echo();
+ getnstr(inLine, 79);
+ noecho();
+ if (!LoadGPTBackup(inLine))
+ Report("Restoration failed!");
+ IdentifySpaces();
+} // GPTDataCurses::LoadBackup()
+
+// Display some basic help information
+void GPTDataCurses::ShowHelp(void) {
+ int i = 0;
+
+ clear();
+ move(0, (COLS - 22) / 2);
+ printw("Help screen for cgdisk");
+ move(2, 0);
+ printw("This is cgdisk, a curses-based disk partitioning program. You can use it\n");
+ printw("to create, delete, and modify partitions on your hard disk.\n\n");
+ attron(A_BOLD);
+ printw("Use cgdisk only on GUID Partition Table (GPT) disks!\n");
+ attroff(A_BOLD);
+ printw("Use cfdisk on Master Boot Record (MBR) disks.\n\n");
+ printw("Command Meaning\n");
+ printw("------- -------\n");
+ while (menuMain[i].key != 0) {
+ printw(" %c %s\n", menuMain[i].key, menuMain[i].desc.c_str());
+ i++;
+ } // while()
+ PromptToContinue();
+} // GPTDataCurses::ShowHelp()
+
+/************************************
+ * *
+ * User input and menuing functions *
+ * *
+ ************************************/
+
+// Change the currently-selected space....
+void GPTDataCurses::ChangeSpaceSelection(int delta) {
+ if (currentSpace != NULL) {
+ while ((delta > 0) && (currentSpace->nextSpace != NULL)) {
+ currentSpace = currentSpace->nextSpace;
+ delta--;
+ currentSpaceNum++;
+ } // while
+ while ((delta < 0) && (currentSpace->prevSpace != NULL)) {
+ currentSpace = currentSpace->prevSpace;
+ delta++;
+ currentSpaceNum--;
+ } // while
+ } // if
+ // Below will hopefully never be true; bad counting error (bug), so reset to
+ // the first Space as a failsafe....
+ if (DisplayParts(currentSpaceNum) != currentSpaceNum) {
+ currentSpaceNum = 0;
+ currentSpace = firstSpace;
+ DisplayParts(currentSpaceNum);
+ } // if
+} // GPTDataCurses
+
+// Move option selection left or right....
+void GPTDataCurses::MoveSelection(int delta) {
+ int newKeyNum;
+
+ // Begin with a sanity check to ensure a valid key is selected....
+ if (whichOptions.find(currentKey) == string::npos)
+ currentKey = 'n';
+ newKeyNum = whichOptions.find(currentKey);
+ newKeyNum += delta;
+ if (newKeyNum < 0)
+ newKeyNum = whichOptions.length() - 1;
+ newKeyNum %= whichOptions.length();
+ currentKey = whichOptions[newKeyNum];
+ DisplayOptions(currentKey);
+} // GPTDataCurses::MoveSelection()
+
+// Show user's options. Refers to currentSpace to determine which options to show.
+// Highlights the option with the key selectedKey; or a default if that's invalid.
+void GPTDataCurses::DisplayOptions(char selectedKey) {
+ uint i, j = 0, firstLine, numPerLine;
+ string optionName, optionDesc = "";
+
+ if (currentSpace != NULL) {
+ if (currentSpace->partNum == -1) { // empty space is selected
+ whichOptions = EMPTY_SPACE_OPTIONS;
+ if (whichOptions.find(selectedKey) == string::npos)
+ selectedKey = 'n';
+ } else { // a partition is selected
+ whichOptions = PARTITION_OPTIONS;
+ if (whichOptions.find(selectedKey) == string::npos)
+ selectedKey = 't';
+ } // if/else
+
+ firstLine = LINES - 4;
+ numPerLine = (COLS - 8) / 12;
+ ClearBottom();
+ move(firstLine, 0);
+ for (i = 0; i < whichOptions.length(); i++) {
+ optionName = "";
+ for (j = 0; menuMain[j].key; j++) {
+ if (menuMain[j].key == whichOptions[i]) {
+ optionName = menuMain[j].name;
+ if (whichOptions[i] == selectedKey)
+ optionDesc = menuMain[j].desc;
+ } // if
+ } // for
+ move(firstLine + i / numPerLine, (i % numPerLine) * 12 + 4);
+ if (whichOptions[i] == selectedKey) {
+ attron(A_REVERSE);
+ printw("[ %s ]", optionName.c_str());
+ attroff(A_REVERSE);
+ } else {
+ printw("[ %s ]", optionName.c_str());
+ } // if/else
+ } // for
+ move(LINES - 1, (COLS - optionDesc.length()) / 2);
+ printw(optionDesc.c_str());
+ currentKey = selectedKey;
+ } // if
+} // GPTDataCurses::DisplayOptions()
+
+// Accept user input and process it. Returns when the program should terminate.
+void GPTDataCurses::AcceptInput() {
+ int inputKey, exitNow = 0;
+
+ do {
+ refresh();
+ inputKey = getch();
+ switch (inputKey) {
+ case KEY_UP:
+ ChangeSpaceSelection(-1);
+ break;
+ case KEY_DOWN:
+ ChangeSpaceSelection(+1);
+ break;
+ case 339: // page up key
+ ChangeSpaceSelection(RESERVED_TOP + RESERVED_BOTTOM - LINES);
+ break;
+ case 338: // page down key
+ ChangeSpaceSelection(LINES - RESERVED_TOP - RESERVED_BOTTOM);
+ break;
+ case KEY_LEFT:
+ MoveSelection(-1);
+ break;
+ case KEY_RIGHT:
+ MoveSelection(+1);
+ break;
+ case KEY_ENTER: case 13:
+ exitNow = Dispatch(currentKey);
+ break;
+ case 27: // escape key
+ exitNow = 1;
+ break;
+ default:
+ exitNow = Dispatch(inputKey);
+ break;
+ } // switch()
+ } while (!exitNow);
+} // GPTDataCurses::AcceptInput()
+
+// Operation has been selected, so do it. Returns 1 if the program should
+// terminate on return from this program, 0 otherwise.
+int GPTDataCurses::Dispatch(char operation) {
+ int exitNow = 0;
+
+ switch (operation) {
+ case 'a': case 'A':
+ SetAlignment();
+ break;
+ case 'b': case 'B':
+ Backup();
+ break;
+ case 'd': case 'D':
+ if (ValidPartNum(currentSpace->partNum))
+ DeletePartition(currentSpace->partNum);
+ break;
+ case 'h': case 'H':
+ ShowHelp();
+ break;
+ case 'i': case 'I':
+ if (ValidPartNum(currentSpace->partNum))
+ ShowInfo(currentSpace->partNum);
+ break;
+ case 'l': case 'L':
+ LoadBackup();
+ break;
+ case 'm': case 'M':
+ if (ValidPartNum(currentSpace->partNum))
+ ChangeName(currentSpace->partNum);
+ break;
+ case 'n': case 'N':
+ if (currentSpace->partNum < 0) {
+ MakeNewPart();
+ IdentifySpaces();
+ } // if
+ break;
+ case 'q': case 'Q':
+ exitNow = 1;
+ break;
+ case 't': case 'T':
+ if (ValidPartNum(currentSpace->partNum))
+ ChangeType(currentSpace->partNum);
+ break;
+ case 'v': case 'V':
+ Verify();
+ break;
+ case 'w': case 'W':
+ SaveData();
+ break;
+ default:
+ break;
+ } // switch()
+ DrawMenu();
+ return exitNow;
+} // GPTDataCurses::Dispatch()
+
+// Draws the main menu
+void GPTDataCurses::DrawMenu(void) {
+ string title="cgdisk ";
+ title += GPTFDISK_VERSION;
+ string drive="Disk Drive: ";
+ drive += device;
+ ostringstream size;
+ size << "Size: " << diskSize << ", " << BytesToIeee(diskSize, blockSize);
+
+ clear();
+ move(0, (COLS - title.length()) / 2);
+ printw(title.c_str());
+ move(2, (COLS - drive.length()) / 2);
+ printw(drive.c_str());
+ move(3, (COLS - size.str().length()) / 2);
+ printw(size.str().c_str());
+ DisplayParts(currentSpaceNum);
+} // DrawMenu
+
+int GPTDataCurses::MainMenu(void) {
+ if (((LINES - RESERVED_TOP - RESERVED_BOTTOM) < 2) || (COLS < 80)) {
+ Report("Display is too small; it must be at least 80 x 14 characters!");
+ } else {
+ if (GPTData::Verify() > 0)
+ Report("Warning! Problems found on disk! Use the Verify function to learn more.\n"
+ "Using gdisk or some other program may be necessary to repair the problems.");
+ IdentifySpaces();
+ currentSpaceNum = 0;
+ DrawMenu();
+ AcceptInput();
+ } // if/else
+ endwin();
+ return 0;
+} // GPTDataCurses::MainMenu
+
+/***********************************************************
+ * *
+ * Non-class support functions (mostly related to ncurses) *
+ * *
+ ***********************************************************/
+
+// Clears the specified line of all data....
+void ClearLine(int lineNum) {
+ move(lineNum, 0);
+ clrtoeol();
+} // ClearLine()
+
+// Clear the last few lines of the display
+void ClearBottom(void) {
+ move(LINES - RESERVED_BOTTOM, 0);
+ clrtobot();
+} // ClearBottom()
+
+void PromptToContinue(void) {
+ ClearBottom();
+ move(LINES - 2, (COLS - 29) / 2);
+ printw("Press any key to continue....");
+ cbreak();
+ getch();
+} // PromptToContinue()
+
+// Display one line of text on the screen and prompt to press any key to continue.
+void Report(string theText) {
+ clear();
+ move(0, 0);
+ printw(theText.c_str());
+ move(LINES - 2, (COLS - 29) / 2);
+ printw("Press any key to continue....");
+ cbreak();
+ getch();
+} // Report()
+
+// Displays all the partition type codes and then prompts to continue....
+// NOTE: This function temporarily exits curses mode as a matter of
+// convenience.
+void ShowTypes(void) {
+ PartType tempType;
+ char junk;
+
+ def_prog_mode();
+ endwin();
+ tempType.ShowAllTypes();
+ cout << "\nPress the <Enter> key to continue: ";
+ cin.get(junk);
+ reset_prog_mode();
+ refresh();
+} // ShowTypes()
diff --git a/gptcurses.h b/gptcurses.h
new file mode 100644
index 0000000..14f43ad
--- /dev/null
+++ b/gptcurses.h
@@ -0,0 +1,129 @@
+/*
+ * Implementation of GPTData class derivative with curses-based text-mode
+ * interaction
+ * Copyright (C) 2011 Roderick W. Smith
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <iostream>
+#include <string>
+#include "gptpart.h"
+#include "gpt.h"
+
+#ifndef __GPT_CURSES
+#define __GPT_CURSES
+
+using namespace std;
+
+struct MenuItem {
+ int key; // Keyboard shortcut
+ string name; // Item name; 8 characters
+ string desc; // Description
+};
+
+static struct MenuItem menuMain[] = {
+ { 'a', "Align ", "Set partition alignment policy" },
+ { 'b', "Backup", "Back up the partition table" },
+ { 'd', "Delete", "Delete the current partition" },
+ { 'h', " Help ", "Print help screen" },
+ { 'i', " Info ", "Display information about the partition" },
+ { 'l', " Load ", "Load partition table backup from file" },
+ { 'm', " naMe ", "Change the partition's name" },
+ { 'n', " New ", "Create new partition from free space" },
+ { 'q', " Quit ", "Quit program without writing partition table" },
+ { 't', " Type ", "Change the filesystem type code GUID" },
+ { 'v', "Verify", "Verify the integrity of the disk's data structures" },
+ { 'w', "Write ", "Write partition table to disk (this might destroy data)" },
+ { 0, "", "" }
+};
+
+#define EMPTY_SPACE_OPTIONS "abhlnqvw"
+#define PARTITION_OPTIONS "abdhilmqtvw"
+
+
+// A "Space" is a partition or an unallocated chunk of disk space, maintained
+// in a doubly-linked-list data structure to facilitate creating displays of
+// partitions and unallocated chunks of space on the disk in the main
+// cgdisk partition list. This list MUST be correctly maintained and in order,
+// and the numSpaces variable in the main GPTDataCurses class must specify
+// how many Spaces are in the main linked list of Spaces.
+struct Space {
+ uint64_t firstLBA;
+ uint64_t lastLBA;
+ GPTPart *origPart;
+ int partNum;
+ Space *nextSpace;
+ Space *prevSpace;
+};
+
+class GPTDataCurses : public GPTData {
+protected:
+ static int numInstances;
+ GPTPart emptySpace;
+ Space *firstSpace;
+ Space *lastSpace;
+ Space *currentSpace;
+ int currentSpaceNum;
+ string whichOptions;
+ char currentKey;
+ int numSpaces;
+ // Functions relating to Spaces data structures
+ void EmptySpaces(void);
+ int MakeSpacesFromParts(void);
+ void AddEmptySpace(uint64_t firstLBA, uint64_t lastLBA);
+ int AddEmptySpaces(void);
+ void UnlinkSpace(Space *theSpace);
+ void LinkToEnd(Space *theSpace);
+ void SortSpaces(void);
+ void IdentifySpaces(void);
+ // Data display functions
+ Space* ShowSpace(int spaceNum, int lineNum);
+ int DisplayParts(int selected);
+public:
+ GPTDataCurses(void);
+ ~GPTDataCurses(void);
+ // Functions corresponding to main menu items
+ void DeletePartition(int partNum);
+ void ShowInfo(int partNum);
+ void ChangeName(int partNum);
+ void ChangeType(int partNum);
+ void SetAlignment(void);
+ void Verify(void);
+ void MakeNewPart(void);
+ void SaveData(void);
+ void Backup(void);
+ void LoadBackup(void);
+ void ShowHelp(void);
+ // User input and menuing functions
+ void ChangeSpaceSelection(int delta);
+ void MoveSelection(int delta);
+ void DisplayOptions(char selectedKey);
+ void AcceptInput();
+ int Dispatch(char operation);
+ void DrawMenu(void);
+ int MainMenu(void);
+}; // class GPTDataCurses
+
+// Non-class support functions (mostly to do simple curses stuff)....
+
+void ClearLine(int lineNum);
+void ClearBottom(void);
+void PromptToContinue(void);
+void Report(string theText);
+void ShowTypes(void);
+
+#endif