2 * Implementation of GPTData class derivative with curses-based text-mode
4 * Copyright (C) 2011 Roderick W. Smith
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "gptcurses.h"
31 // # of lines to reserve for general information and headers (RESERVED_TOP)
32 // and for options and messages (RESERVED_BOTTOM)
33 #define RESERVED_TOP 7
34 #define RESERVED_BOTTOM 5
36 int GPTDataCurses::numInstances = 0;
38 GPTDataCurses::GPTDataCurses(void) {
39 if (numInstances > 0) {
45 intrflush(stdscr, false);
54 whichOptions = ""; // current set of options
55 currentKey = 'b'; // currently selected option
56 } // GPTDataCurses constructor
58 GPTDataCurses::~GPTDataCurses(void) {
60 if ((numInstances == 0) && !isendwin())
62 } // GPTDataCurses destructor
64 /************************************************
66 * Functions relating to Spaces data structures *
68 ************************************************/
70 void GPTDataCurses::EmptySpaces(void) {
73 while (firstSpace != NULL) {
75 firstSpace = firstSpace->nextSpace;
80 } // GPTDataCurses::EmptySpaces()
82 // Create Spaces from partitions. Does NOT creates Spaces to represent
83 // unpartitioned space on the disk.
84 // Returns the number of Spaces created.
85 int GPTDataCurses::MakeSpacesFromParts(void) {
90 for (i = 0; i < numParts; i++) {
91 if (partitions[i].IsUsed()) {
92 tempSpace = new Space;
93 tempSpace->firstLBA = partitions[i].GetFirstLBA();
94 tempSpace->lastLBA = partitions[i].GetLastLBA();
95 tempSpace->origPart = &partitions[i];
96 tempSpace->partNum = (int) i;
101 } // GPTDataCurses::MakeSpacesFromParts()
103 // Add a single empty Space to the current Spaces linked list and sort the result....
104 void GPTDataCurses::AddEmptySpace(uint64_t firstLBA, uint64_t lastLBA) {
107 tempSpace = new Space;
108 tempSpace->firstLBA = firstLBA;
109 tempSpace->lastLBA = lastLBA;
110 tempSpace->origPart = &emptySpace;
111 tempSpace->partNum = -1;
112 LinkToEnd(tempSpace);
114 } // GPTDataCurses::AddEmptySpace();
116 // Add Spaces to represent the unallocated parts of the partition table.
117 // Returns the number of Spaces added.
118 int GPTDataCurses::AddEmptySpaces(void) {
123 if (firstSpace == NULL) {
124 AddEmptySpace(GetFirstUsableLBA(), GetLastUsableLBA());
127 current = firstSpace;
128 while ((current != NULL) /* && (current->partNum != -1) */ ) {
129 if ((current == firstSpace) && (current->firstLBA > GetFirstUsableLBA())) {
130 AddEmptySpace(GetFirstUsableLBA(), current->firstLBA - 1);
133 if ((current == lastSpace) && (current->lastLBA < GetLastUsableLBA())) {
134 AddEmptySpace(current->lastLBA + 1, GetLastUsableLBA());
137 if ((current->prevSpace != NULL) && (current->prevSpace->lastLBA < (current->firstLBA - 1))) {
138 AddEmptySpace(current->prevSpace->lastLBA + 1, current->firstLBA - 1);
141 current = current->nextSpace;
145 } // GPTDataCurses::AddEmptySpaces()
147 // Remove the specified Space from the linked list and set its previous and
148 // next pointers to NULL.
149 void GPTDataCurses::UnlinkSpace(Space *theSpace) {
150 if (theSpace != NULL) {
151 if (theSpace->prevSpace != NULL)
152 theSpace->prevSpace->nextSpace = theSpace->nextSpace;
153 if (theSpace->nextSpace != NULL)
154 theSpace->nextSpace->prevSpace = theSpace->prevSpace;
155 if (theSpace == firstSpace)
156 firstSpace = theSpace->nextSpace;
157 if (theSpace == lastSpace)
158 lastSpace = theSpace->prevSpace;
159 theSpace->nextSpace = NULL;
160 theSpace->prevSpace = NULL;
163 } // GPTDataCurses::UnlinkSpace
165 // Link theSpace to the end of the current linked list.
166 void GPTDataCurses::LinkToEnd(Space *theSpace) {
167 if (lastSpace == NULL) {
168 firstSpace = lastSpace = theSpace;
169 theSpace->nextSpace = NULL;
170 theSpace->prevSpace = NULL;
172 theSpace->prevSpace = lastSpace;
173 theSpace->nextSpace = NULL;
174 lastSpace->nextSpace = theSpace;
175 lastSpace = theSpace;
178 } // GPTDataCurses::LinkToEnd()
180 // Sort spaces into ascending order by on-disk position.
181 void GPTDataCurses::SortSpaces(void) {
182 Space *oldFirst, *oldLast, *earliest = NULL, *current = NULL;
184 oldFirst = firstSpace;
186 firstSpace = lastSpace = NULL;
187 while (oldFirst != NULL) {
188 current = earliest = oldFirst;
189 while (current != NULL) {
190 if (current->firstLBA < earliest->firstLBA)
192 current = current->nextSpace;
194 if (oldFirst == earliest)
195 oldFirst = earliest->nextSpace;
196 if (oldLast == earliest)
197 oldLast = earliest->prevSpace;
198 UnlinkSpace(earliest);
201 } // GPTDataCurses::SortSpaces()
203 // Identify the spaces on the disk, a "space" being defined as a partition
204 // or an empty gap between, before, or after partitions. The spaces are
205 // presented to users in the main menu display.
206 void GPTDataCurses::IdentifySpaces(void) {
207 MakeSpacesFromParts();
209 } // GPTDataCurses::IdentifySpaces()
211 /**************************
213 * Data display functions *
215 **************************/
217 // Display a single Space on line # lineNum.
218 // Returns a pointer to the space being displayed
219 Space* GPTDataCurses::ShowSpace(int spaceNum, int lineNum) {
225 while ((space != NULL) && (i < spaceNum)) {
226 space = space->nextSpace;
229 if ((space != NULL) && (lineNum < (LINES - 5))) {
231 if (space->partNum == -1) { // space is empty
233 printw(BytesToIeee((space->lastLBA - space->firstLBA + 1), blockSize).c_str());
235 printw("free space");
236 } else { // space holds a partition
238 printw("%d", space->partNum + 1);
240 printw(BytesToIeee((space->lastLBA - space->firstLBA + 1), blockSize).c_str());
242 printw(space->origPart->GetTypeName().c_str());
245 space->origPart->GetDescription().extract(0, 39, temp, 39);
248 printw(space->origPart->GetDescription().c_str());
253 } // GPTDataCurses::ShowSpace
255 // Display the partitions, being sure that the space #selected is displayed
256 // and highlighting that space.
257 // Returns the number of the space being shown (should be selected, but will
258 // be -1 if something weird happens)
259 int GPTDataCurses::DisplayParts(int selected) {
260 int lineNum = 5, i = 0, retval = -1, numToShow, pageNum;
264 theLine = "Part. # Size Partition Type Partition Name";
265 printw(theLine.c_str());
267 theLine = "----------------------------------------------------------------";
268 printw(theLine.c_str());
269 numToShow = LINES - RESERVED_TOP - RESERVED_BOTTOM;
270 pageNum = selected / numToShow;
271 for (i = pageNum * numToShow; i <= (pageNum + 1) * numToShow - 1; i++) {
272 if (i < numSpaces) { // real space; show it
276 currentSpace = ShowSpace(i, lineNum++);
281 ShowSpace(i, lineNum++);
283 } else { // blank in display
284 ClearLine(lineNum++);
289 } // GPTDataCurses::DisplayParts()
291 /**********************************************
293 * Functions corresponding to main menu items *
295 **********************************************/
297 // Delete the specified partition and re-detect partitions and spaces....
298 void GPTDataCurses::DeletePartition(int partNum) {
299 if (!GPTData::DeletePartition(partNum))
300 Report("Could not delete partition!");
302 if (currentSpaceNum >= numSpaces) {
303 currentSpaceNum = numSpaces - 1;
304 currentSpace = lastSpace;
306 } // GPTDataCurses::DeletePartition()
308 // Displays information on the specified partition
309 void GPTDataCurses::ShowInfo(int partNum) {
311 char temp[NAME_SIZE / 2 + 1];
314 move(2, (COLS - 29) / 2);
315 printw("Information for partition #%d\n\n", partNum + 1);
316 printw("Partition GUID code: %s (%s)\n", partitions[partNum].GetType().AsString().c_str(),
317 partitions[partNum].GetTypeName().c_str());
318 printw("Partition unique GUID: %s\n", partitions[partNum].GetUniqueGUID().AsString().c_str());
319 printw("First sector: %lld (at %s)\n", partitions[partNum].GetFirstLBA(),
320 BytesToIeee(partitions[partNum].GetFirstLBA(), blockSize).c_str());
321 printw("Last sector: %lld (at %s)\n", partitions[partNum].GetLastLBA(),
322 BytesToIeee(partitions[partNum].GetLastLBA(), blockSize).c_str());
323 size = partitions[partNum].GetLastLBA() - partitions[partNum].GetFirstLBA();
324 printw("Partition size: %lld sectors (%s)\n", size, BytesToIeee(size, blockSize).c_str());
325 printw("Attribute flags: %016x\n", partitions[partNum].GetAttributes().GetAttributes());
327 partitions[partNum].GetDescription().extract(0, NAME_SIZE / 2, temp, NAME_SIZE / 2);
328 printw("Partition name: '%s'\n", temp);
330 printw("Partition name: '%s'\n", partitions[partNum].GetDescription().c_str());
333 } // GPTDataCurses::ShowInfo()
335 // Prompt for and change a partition's name....
336 void GPTDataCurses::ChangeName(int partNum) {
337 char temp[NAME_SIZE / 2 + 1];
339 if (ValidPartNum(partNum)) {
344 partitions[partNum].GetDescription().extract(0, NAME_SIZE / 2, temp, NAME_SIZE / 2);
345 printw("Current partition name is '%s'\n", temp);
347 printw("Current partition name is '%s'\n", partitions[partNum].GetDescription().c_str());
349 printw("Enter new partition name, or <Enter> to use the current name:\n");
351 getnstr(temp, NAME_SIZE / 2);
352 partitions[partNum].SetName((string) temp);
355 } // GPTDataCurses::ChangeName()
357 // Change the partition's type code....
358 void GPTDataCurses::ChangeType(int partNum) {
359 char temp[80] = "L\0";
367 printw("Current type is %04x (%s)\n", partitions[partNum].GetType().GetHexType(), partitions[partNum].GetTypeName().c_str());
368 printw("Hex code or GUID (L to show codes, Enter = %04x): ", partitions[partNum].GetType().GetHexType());
370 if ((temp[0] == 'L') || (temp[0] == 'l')) {
374 tempType = partitions[partNum].GetType().GetHexType();
376 partitions[partNum].SetType(tempType);
378 } while ((temp[0] == 'L') || (temp[0] == 'l') || (partitions[partNum].GetType() == (GUIDData) "0x0000"));
380 } // GPTDataCurses::ChangeType
382 // Sets the partition alignment value
383 void GPTDataCurses::SetAlignment(void) {
388 printw("Current partition alignment, in sectors, is %d.", GetAlignment());
391 printw("Type new alignment value, in sectors: ");
393 scanw("%d", &alignment);
395 } while ((alignment == 0) || (alignment > MAX_ALIGNMENT));
396 GPTData::SetAlignment(alignment);
397 } // GPTDataCurses::SetAlignment()
399 // Verify the data structures. Note that this function leaves curses mode and
400 // relies on the underlying GPTData::Verify() function to report on problems
401 void GPTDataCurses::Verify(void) {
407 cout << "\nPress the <Enter> key to continue: ";
411 } // GPTDataCurses::Verify()
413 // Create a new partition in the space pointed to by currentSpace.
414 void GPTDataCurses::MakeNewPart(void) {
415 uint64_t size, newFirstLBA = 0, newLastLBA = 0;
421 while ((newFirstLBA < currentSpace->firstLBA) || (newFirstLBA > currentSpace->lastLBA)) {
422 newFirstLBA = currentSpace->firstLBA;
425 newFirstLBA = currentSpace->firstLBA;
427 printw("First sector (%lld-%lld, default = %lld): ", newFirstLBA, currentSpace->lastLBA, newFirstLBA);
431 newFirstLBA = IeeeToInt(inLine, blockSize, currentSpace->firstLBA, currentSpace->lastLBA, newFirstLBA);
434 size = currentSpace->lastLBA - newFirstLBA + 1;
435 while ((newLastLBA > currentSpace->lastLBA) || (newLastLBA < newFirstLBA)) {
438 printw("Size in sectors or {KMGTP} (default = %lld): ", size);
442 newLastLBA = newFirstLBA + IeeeToInt(inLine, blockSize, 1, size, size) - 1;
444 partNum = FindFirstFreePart();
445 if (CreatePartition(partNum, newFirstLBA, newLastLBA)) { // created OK; set type code & name....
449 Report("Error creating partition!");
451 } // GPTDataCurses::MakeNewPart()
453 // Prompt user for permission to save data and, if it's given, do so!
454 void GPTDataCurses::SaveData(void) {
460 move (LINES - 2, 14);
461 printw("Warning!! This may destroy data on your disk!");
463 while ((answer != "yes") && (answer != "no")) {
465 printw("Are you sure you want to write the partition table to disk? (yes or no): ");
468 if ((answer != "yes") && (answer != "no")) {
472 printw("Please enter 'yes' or 'no'");
476 if (answer == "yes") {
477 if (SaveGPTData(1)) {
478 if (!myDisk.DiskSync())
479 Report("The kernel may be using the old partition table. Reboot to use the new\npartition table!");
481 Report("Problem saving data! Your partition table may be damaged!");
484 } // GPTDataCurses::SaveData()
486 // Back up the partition table, prompting user for a filename....
487 void GPTDataCurses::Backup(void) {
492 printw("Enter backup filename to save: ");
496 SaveGPTBackup(inLine);
497 } // GPTDataCurses::Backup()
499 // Load a GPT backup from a file
500 void GPTDataCurses::LoadBackup(void) {
505 printw("Enter backup filename to load: ");
509 if (!LoadGPTBackup(inLine))
510 Report("Restoration failed!");
512 } // GPTDataCurses::LoadBackup()
514 // Display some basic help information
515 void GPTDataCurses::ShowHelp(void) {
519 move(0, (COLS - 22) / 2);
520 printw("Help screen for cgdisk");
522 printw("This is cgdisk, a curses-based disk partitioning program. You can use it\n");
523 printw("to create, delete, and modify partitions on your hard disk.\n\n");
525 printw("Use cgdisk only on GUID Partition Table (GPT) disks!\n");
527 printw("Use cfdisk on Master Boot Record (MBR) disks.\n\n");
528 printw("Command Meaning\n");
529 printw("------- -------\n");
530 while (menuMain[i].key != 0) {
531 printw(" %c %s\n", menuMain[i].key, menuMain[i].desc.c_str());
535 } // GPTDataCurses::ShowHelp()
537 /************************************
539 * User input and menuing functions *
541 ************************************/
543 // Change the currently-selected space....
544 void GPTDataCurses::ChangeSpaceSelection(int delta) {
545 if (currentSpace != NULL) {
546 while ((delta > 0) && (currentSpace->nextSpace != NULL)) {
547 currentSpace = currentSpace->nextSpace;
551 while ((delta < 0) && (currentSpace->prevSpace != NULL)) {
552 currentSpace = currentSpace->prevSpace;
557 // Below will hopefully never be true; bad counting error (bug), so reset to
558 // the first Space as a failsafe....
559 if (DisplayParts(currentSpaceNum) != currentSpaceNum) {
561 currentSpace = firstSpace;
562 DisplayParts(currentSpaceNum);
566 // Move option selection left or right....
567 void GPTDataCurses::MoveSelection(int delta) {
570 // Begin with a sanity check to ensure a valid key is selected....
571 if (whichOptions.find(currentKey) == string::npos)
573 newKeyNum = whichOptions.find(currentKey);
576 newKeyNum = whichOptions.length() - 1;
577 newKeyNum %= whichOptions.length();
578 currentKey = whichOptions[newKeyNum];
579 DisplayOptions(currentKey);
580 } // GPTDataCurses::MoveSelection()
582 // Show user's options. Refers to currentSpace to determine which options to show.
583 // Highlights the option with the key selectedKey; or a default if that's invalid.
584 void GPTDataCurses::DisplayOptions(char selectedKey) {
585 uint i, j = 0, firstLine, numPerLine;
586 string optionName, optionDesc = "";
588 if (currentSpace != NULL) {
589 if (currentSpace->partNum == -1) { // empty space is selected
590 whichOptions = EMPTY_SPACE_OPTIONS;
591 if (whichOptions.find(selectedKey) == string::npos)
593 } else { // a partition is selected
594 whichOptions = PARTITION_OPTIONS;
595 if (whichOptions.find(selectedKey) == string::npos)
599 firstLine = LINES - 4;
600 numPerLine = (COLS - 8) / 12;
603 for (i = 0; i < whichOptions.length(); i++) {
605 for (j = 0; menuMain[j].key; j++) {
606 if (menuMain[j].key == whichOptions[i]) {
607 optionName = menuMain[j].name;
608 if (whichOptions[i] == selectedKey)
609 optionDesc = menuMain[j].desc;
612 move(firstLine + i / numPerLine, (i % numPerLine) * 12 + 4);
613 if (whichOptions[i] == selectedKey) {
615 printw("[ %s ]", optionName.c_str());
618 printw("[ %s ]", optionName.c_str());
621 move(LINES - 1, (COLS - optionDesc.length()) / 2);
622 printw(optionDesc.c_str());
623 currentKey = selectedKey;
625 } // GPTDataCurses::DisplayOptions()
627 // Accept user input and process it. Returns when the program should terminate.
628 void GPTDataCurses::AcceptInput() {
629 int inputKey, exitNow = 0;
636 ChangeSpaceSelection(-1);
639 ChangeSpaceSelection(+1);
641 case 339: // page up key
642 ChangeSpaceSelection(RESERVED_TOP + RESERVED_BOTTOM - LINES);
644 case 338: // page down key
645 ChangeSpaceSelection(LINES - RESERVED_TOP - RESERVED_BOTTOM);
653 case KEY_ENTER: case 13:
654 exitNow = Dispatch(currentKey);
656 case 27: // escape key
660 exitNow = Dispatch(inputKey);
664 } // GPTDataCurses::AcceptInput()
666 // Operation has been selected, so do it. Returns 1 if the program should
667 // terminate on return from this program, 0 otherwise.
668 int GPTDataCurses::Dispatch(char operation) {
679 if (ValidPartNum(currentSpace->partNum))
680 DeletePartition(currentSpace->partNum);
686 if (ValidPartNum(currentSpace->partNum))
687 ShowInfo(currentSpace->partNum);
693 if (ValidPartNum(currentSpace->partNum))
694 ChangeName(currentSpace->partNum);
697 if (currentSpace->partNum < 0) {
706 if (ValidPartNum(currentSpace->partNum))
707 ChangeType(currentSpace->partNum);
720 } // GPTDataCurses::Dispatch()
722 // Draws the main menu
723 void GPTDataCurses::DrawMenu(void) {
724 string title="cgdisk ";
725 title += GPTFDISK_VERSION;
726 string drive="Disk Drive: ";
729 size << "Size: " << diskSize << ", " << BytesToIeee(diskSize, blockSize);
732 move(0, (COLS - title.length()) / 2);
733 printw(title.c_str());
734 move(2, (COLS - drive.length()) / 2);
735 printw(drive.c_str());
736 move(3, (COLS - size.str().length()) / 2);
737 printw(size.str().c_str());
738 DisplayParts(currentSpaceNum);
741 int GPTDataCurses::MainMenu(void) {
742 if (((LINES - RESERVED_TOP - RESERVED_BOTTOM) < 2) || (COLS < 80)) {
743 Report("Display is too small; it must be at least 80 x 14 characters!");
745 if (GPTData::Verify() > 0)
746 Report("Warning! Problems found on disk! Use the Verify function to learn more.\n"
747 "Using gdisk or some other program may be necessary to repair the problems.");
755 } // GPTDataCurses::MainMenu
757 /***********************************************************
759 * Non-class support functions (mostly related to ncurses) *
761 ***********************************************************/
763 // Clears the specified line of all data....
764 void ClearLine(int lineNum) {
769 // Clear the last few lines of the display
770 void ClearBottom(void) {
771 move(LINES - RESERVED_BOTTOM, 0);
775 void PromptToContinue(void) {
777 move(LINES - 2, (COLS - 29) / 2);
778 printw("Press any key to continue....");
781 } // PromptToContinue()
783 // Display one line of text on the screen and prompt to press any key to continue.
784 void Report(string theText) {
787 printw(theText.c_str());
788 move(LINES - 2, (COLS - 29) / 2);
789 printw("Press any key to continue....");
794 // Displays all the partition type codes and then prompts to continue....
795 // NOTE: This function temporarily exits curses mode as a matter of
797 void ShowTypes(void) {
803 tempType.ShowAllTypes();
804 cout << "\nPress the <Enter> key to continue: ";