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 + + 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 +#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 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 +# +# 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 +#include +#include +#include +#include +#include +#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...] "); + + 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 + +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 +#include +#include +#include +#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 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 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 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 +#include +#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