resetting manifest requested domain to floor
[platform/upstream/gptfdisk.git] / gpttext.cc
1 /*
2     Copyright (C) 2010-2011  <Roderick W. Smith>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License along
15     with this program; if not, write to the Free Software Foundation, Inc.,
16     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 */
19
20 /* This class implements an interactive text-mode interface atop the
21    GPTData class */
22
23 #include <string.h>
24 #include <errno.h>
25 #include <stdint.h>
26 #include <limits.h>
27 #include <iostream>
28 #include <fstream>
29 #include <sstream>
30 #include <cstdio>
31 #include "attributes.h"
32 #include "gpttext.h"
33 #include "support.h"
34
35 using namespace std;
36
37 /********************************************
38  *                                          *
39  * GPTDataText class and related structures *
40  *                                          *
41  ********************************************/
42
43 GPTDataTextUI::GPTDataTextUI(void) : GPTData() {
44 } // default constructor
45
46 GPTDataTextUI::GPTDataTextUI(string filename) : GPTData(filename) {
47 } // constructor passing filename
48
49 GPTDataTextUI::~GPTDataTextUI(void) {
50 } // default destructor
51
52 /*********************************************************************
53  *                                                                   *
54  * The following functions are extended (interactive) versions of    *
55  * simpler functions in the base class....                           *
56  *                                                                   *
57  *********************************************************************/
58
59 // Overridden function; calls base-class function and then makes
60 // additional queries of the user, if the base-class function can't
61 // decide what to do.
62 WhichToUse GPTDataTextUI::UseWhichPartitions(void) {
63    WhichToUse which;
64    MBRValidity mbrState;
65    int answer;
66
67    which = GPTData::UseWhichPartitions();
68    if ((which != use_abort) || beQuiet)
69       return which;
70
71    // If we get past here, it means that the non-interactive tests were
72    // inconclusive, so we must ask the user which table to use....
73    mbrState = protectiveMBR.GetValidity();
74
75    if ((state == gpt_valid) && (mbrState == mbr)) {
76       cout << "Found valid MBR and GPT. Which do you want to use?\n";
77       answer = GetNumber(1, 3, 2, " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: ");
78       if (answer == 1) {
79          which = use_mbr;
80       } else if (answer == 2) {
81          which = use_gpt;
82          cout << "Using GPT and creating fresh protective MBR.\n";
83       } else which = use_new;
84    } // if
85
86    // Nasty decisions here -- GPT is present, but corrupt (bad CRCs or other
87    // problems)
88    if (state == gpt_corrupt) {
89       if ((mbrState == mbr) || (mbrState == hybrid)) {
90          cout << "Found valid MBR and corrupt GPT. Which do you want to use? (Using the\n"
91               << "GPT MAY permit recovery of GPT data.)\n";
92          answer = GetNumber(1, 3, 2, " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: ");
93          if (answer == 1) {
94             which = use_mbr;
95          } else if (answer == 2) {
96             which = use_gpt;
97          } else which = use_new;
98       } else if (mbrState == invalid) {
99          cout << "Found invalid MBR and corrupt GPT. What do you want to do? (Using the\n"
100               << "GPT MAY permit recovery of GPT data.)\n";
101          answer = GetNumber(1, 2, 1, " 1 - Use current GPT\n 2 - Create blank GPT\n\nYour answer: ");
102          if (answer == 1) {
103             which = use_gpt;
104          } else which = use_new;
105       } // if/else/else
106    } // if (corrupt GPT)
107
108    return which;
109 } // UseWhichPartitions()
110
111 // Ask the user for a partition number; and prompt for verification
112 // if the requested partition isn't of a known BSD type.
113 // Lets the base-class function do the work, and returns its value (the
114 // number of converted partitions).
115 int GPTDataTextUI::XFormDisklabel(void) {
116    uint32_t partNum;
117    uint16_t hexCode;
118    int goOn = 1, numDone = 0;
119    BSDData disklabel;
120
121    partNum = GetPartNum();
122
123    // Now see if the specified partition has a BSD type code....
124    hexCode = partitions[partNum].GetHexType();
125    if ((hexCode != 0xa500) && (hexCode != 0xa900)) {
126       cout << "Specified partition doesn't have a disklabel partition type "
127            << "code.\nContinue anyway? ";
128       goOn = (GetYN() == 'Y');
129    } // if
130
131    if (goOn)
132       numDone = GPTData::XFormDisklabel(partNum);
133
134    return numDone;
135 } // GPTData::XFormDisklabel(int i)
136
137
138 /*********************************************************************
139  *                                                                   *
140  * Begin functions that obtain information from the users, and often *
141  * do something with that information (call other functions)         *
142  *                                                                   *
143  *********************************************************************/
144
145 // Prompts user for partition number and returns the result. Returns "0"
146 // (the first partition) if none are currently defined.
147 uint32_t GPTDataTextUI::GetPartNum(void) {
148    uint32_t partNum;
149    uint32_t low, high;
150    ostringstream prompt;
151
152    if (GetPartRange(&low, &high) > 0) {
153       prompt << "Partition number (" << low + 1 << "-" << high + 1 << "): ";
154       partNum = GetNumber(low + 1, high + 1, low, prompt.str());
155    } else partNum = 1;
156    return (partNum - 1);
157 } // GPTDataTextUI::GetPartNum()
158
159 // What it says: Resize the partition table. (Default is 128 entries.)
160 void GPTDataTextUI::ResizePartitionTable(void) {
161    int newSize;
162    ostringstream prompt;
163    uint32_t curLow, curHigh;
164
165    cout << "Current partition table size is " << numParts << ".\n";
166    GetPartRange(&curLow, &curHigh);
167    curHigh++; // since GetPartRange() returns numbers starting from 0...
168    // There's no point in having fewer than four partitions....
169    if (curHigh < (blockSize / GPT_SIZE))
170       curHigh = blockSize / GPT_SIZE;
171    prompt << "Enter new size (" << curHigh << " up, default " << NUM_GPT_ENTRIES << "): ";
172    newSize = GetNumber(4, 65535, 128, prompt.str());
173    if (newSize < 128) {
174       cout << "Caution: The partition table size should officially be 16KB or larger,\n"
175            << "which works out to 128 entries. In practice, smaller tables seem to\n"
176            << "work with most OSes, but this practice is risky. I'm proceeding with\n"
177            << "the resize, but you may want to reconsider this action and undo it.\n\n";
178    } // if
179    SetGPTSize(newSize);
180 } // GPTDataTextUI::ResizePartitionTable()
181
182 // Interactively create a partition
183 void GPTDataTextUI::CreatePartition(void) {
184    uint64_t firstBlock, firstInLargest, lastBlock, sector, origSector;
185    uint32_t firstFreePart = 0;
186    ostringstream prompt1, prompt2, prompt3;
187    int partNum;
188
189    // Find first free partition...
190    while (partitions[firstFreePart].GetFirstLBA() != 0) {
191       firstFreePart++;
192    } // while
193
194    if (((firstBlock = FindFirstAvailable()) != 0) &&
195        (firstFreePart < numParts)) {
196       lastBlock = FindLastAvailable();
197       firstInLargest = FindFirstInLargest();
198       Align(&firstInLargest);
199
200       // Get partition number....
201       do {
202          prompt1 << "Partition number (" << firstFreePart + 1 << "-" << numParts
203                  << ", default " << firstFreePart + 1 << "): ";
204          partNum = GetNumber(firstFreePart + 1, numParts,
205                              firstFreePart + 1, prompt1.str()) - 1;
206          if (partitions[partNum].GetFirstLBA() != 0)
207             cout << "partition " << partNum + 1 << " is in use.\n";
208       } while (partitions[partNum].GetFirstLBA() != 0);
209
210       // Get first block for new partition...
211       prompt2 << "First sector (" << firstBlock << "-" << lastBlock << ", default = "
212               << firstInLargest << ") or {+-}size{KMGTP}: ";
213       do {
214          sector = GetSectorNum(firstBlock, lastBlock, firstInLargest, blockSize, prompt2.str());
215       } while (IsFree(sector) == 0);
216       origSector = sector;
217       if (Align(&sector)) {
218          cout << "Information: Moved requested sector from " << origSector << " to "
219               << sector << " in\norder to align on " << sectorAlignment
220               << "-sector boundaries.\n";
221          if (!beQuiet)
222             cout << "Use 'l' on the experts' menu to adjust alignment\n";
223       } // if
224       //      Align(&sector); // Align sector to correct multiple
225       firstBlock = sector;
226
227       // Get last block for new partitions...
228       lastBlock = FindLastInFree(firstBlock);
229       prompt3 << "Last sector (" << firstBlock << "-" << lastBlock << ", default = "
230             << lastBlock << ") or {+-}size{KMGTP}: ";
231       do {
232          sector = GetSectorNum(firstBlock, lastBlock, lastBlock, blockSize, prompt3.str());
233       } while (IsFree(sector) == 0);
234       lastBlock = sector;
235
236       firstFreePart = GPTData::CreatePartition(partNum, firstBlock, lastBlock);
237       partitions[partNum].ChangeType();
238       partitions[partNum].SetDefaultDescription();
239    } else {
240       if (firstFreePart >= numParts)
241          cout << "No table partition entries left\n";
242       else
243          cout << "No free sectors available\n";
244    } // if/else
245 } // GPTDataTextUI::CreatePartition()
246
247 // Interactively delete a partition (duh!)
248 void GPTDataTextUI::DeletePartition(void) {
249    int partNum;
250    uint32_t low, high;
251    ostringstream prompt;
252
253    if (GetPartRange(&low, &high) > 0) {
254       prompt << "Partition number (" << low + 1 << "-" << high + 1 << "): ";
255       partNum = GetNumber(low + 1, high + 1, low, prompt.str());
256       GPTData::DeletePartition(partNum - 1);
257    } else {
258       cout << "No partitions\n";
259    } // if/else
260 } // GPTDataTextUI::DeletePartition()
261
262 // Prompt user for a partition number, then change its type code
263 void GPTDataTextUI::ChangePartType(void) {
264    int partNum;
265    uint32_t low, high;
266
267    if (GetPartRange(&low, &high) > 0) {
268       partNum = GetPartNum();
269       partitions[partNum].ChangeType();
270    } else {
271       cout << "No partitions\n";
272    } // if/else
273 } // GPTDataTextUI::ChangePartType()
274
275 // Prompt user for a partition number, then change its unique
276 // GUID.
277 void GPTDataTextUI::ChangeUniqueGuid(void) {
278    int partNum;
279    uint32_t low, high;
280    string guidStr;
281
282    if (GetPartRange(&low, &high) > 0) {
283       partNum = GetPartNum();
284       cout << "Enter the partition's new unique GUID ('R' to randomize): ";
285       guidStr = ReadString();
286       if ((guidStr.length() >= 32) || (guidStr[0] == 'R') || (guidStr[0] == 'r')) {
287          SetPartitionGUID(partNum, (GUIDData) guidStr);
288          cout << "New GUID is " << partitions[partNum].GetUniqueGUID() << "\n";
289       } else {
290          cout << "GUID is too short!\n";
291       } // if/else
292    } else
293       cout << "No partitions\n";
294 } // GPTDataTextUI::ChangeUniqueGuid()
295
296 // Partition attributes seem to be rarely used, but I want a way to
297 // adjust them for completeness....
298 void GPTDataTextUI::SetAttributes(uint32_t partNum) {
299    partitions[partNum].SetAttributes();
300 } // GPTDataTextUI::SetAttributes()
301
302 // Prompts the user for a partition name and sets the partition's
303 // name. Returns 1 on success, 0 on failure (invalid partition
304 // number). (Note that the function skips prompting when an
305 // invalid partition number is detected.)
306 int GPTDataTextUI::SetName(uint32_t partNum) {
307    UnicodeString theName = "";
308    int retval = 1;
309
310    if (IsUsedPartNum(partNum)) {
311       cout << "Enter name: ";
312       theName = ReadUString();
313       partitions[partNum].SetName(theName);
314    } else {
315       cerr << "Invalid partition number (" << partNum << ")\n";
316       retval = 0;
317    } // if/else
318
319    return retval;
320 } // GPTDataTextUI::SetName()
321
322 // Ask user for two partition numbers and swap them in the table. Note that
323 // this just reorders table entries; it doesn't adjust partition layout on
324 // the disk.
325 // Returns 1 if successful, 0 if not. (If user enters identical numbers, it
326 // counts as successful.)
327 int GPTDataTextUI::SwapPartitions(void) {
328    int partNum1, partNum2, didIt = 0;
329    uint32_t low, high;
330    ostringstream prompt;
331    GPTPart temp;
332
333    if (GetPartRange(&low, &high) > 0) {
334       partNum1 = GetPartNum();
335       if (high >= numParts - 1)
336          high = 0;
337       prompt << "New partition number (1-" << numParts
338              << ", default " << high + 2 << "): ";
339       partNum2 = GetNumber(1, numParts, high + 2, prompt.str()) - 1;
340       didIt = GPTData::SwapPartitions(partNum1, partNum2);
341    } else {
342       cout << "No partitions\n";
343    } // if/else
344    return didIt;
345 } // GPTDataTextUI::SwapPartitionNumbers()
346
347 // This function destroys the on-disk GPT structures. Returns 1 if the user
348 // confirms destruction, 0 if the user aborts or if there's a disk error.
349 int GPTDataTextUI::DestroyGPTwPrompt(void) {
350    int allOK = 1;
351
352    if ((apmFound) || (bsdFound)) {
353       cout << "WARNING: APM or BSD disklabel structures detected! This operation could\n"
354            << "damage any APM or BSD partitions on this disk!\n";
355    } // if APM or BSD
356    cout << "\a\aAbout to wipe out GPT on " << device << ". Proceed? ";
357    if (GetYN() == 'Y') {
358       if (DestroyGPT()) {
359          // Note on below: Touch the MBR only if the user wants it completely
360          // blanked out. Version 0.4.2 deleted the 0xEE partition and re-wrote
361          // the MBR, but this could wipe out a valid MBR that the program
362          // had subsequently discarded (say, if it conflicted with older GPT
363          // structures).
364          cout << "Blank out MBR? ";
365          if (GetYN() == 'Y') {
366             DestroyMBR();
367          } else {
368             cout << "MBR is unchanged. You may need to delete an EFI GPT (0xEE) partition\n"
369                  << "with fdisk or another tool.\n";
370          } // if/else
371       } else allOK = 0; // if GPT structures destroyed
372    } else allOK = 0; // if user confirms destruction
373    return (allOK);
374 } // GPTDataTextUI::DestroyGPTwPrompt()
375
376 // Get partition number from user and then call ShowPartDetails(partNum)
377 // to show its detailed information
378 void GPTDataTextUI::ShowDetails(void) {
379    int partNum;
380    uint32_t low, high;
381
382    if (GetPartRange(&low, &high) > 0) {
383       partNum = GetPartNum();
384       ShowPartDetails(partNum);
385    } else {
386       cout << "No partitions\n";
387    } // if/else
388 } // GPTDataTextUI::ShowDetails()
389
390 // Create a hybrid MBR -- an ugly, funky thing that helps GPT work with
391 // OSes that don't understand GPT.
392 void GPTDataTextUI::MakeHybrid(void) {
393    uint32_t partNums[3];
394    string line;
395    int numPartsToCvt, i, j, mbrNum = 0;
396    unsigned int hexCode = 0;
397    MBRPart hybridPart;
398    MBRData hybridMBR;
399    char eeFirst = 'Y'; // Whether EFI GPT (0xEE) partition comes first in table
400
401    cout << "\nWARNING! Hybrid MBRs are flaky and dangerous! If you decide not to use one,\n"
402         << "just hit the Enter key at the below prompt and your MBR partition table will\n"
403         << "be untouched.\n\n\a";
404
405    // Use a local MBR structure, copying from protectiveMBR to keep its
406    // boot loader code intact....
407    hybridMBR = protectiveMBR;
408    hybridMBR.EmptyMBR(0);
409
410    // Now get the numbers of up to three partitions to add to the
411    // hybrid MBR....
412    cout << "Type from one to three GPT partition numbers, separated by spaces, to be\n"
413         << "added to the hybrid MBR, in sequence: ";
414    line = ReadString();
415    numPartsToCvt = sscanf(line.c_str(), "%d %d %d", &partNums[0], &partNums[1], &partNums[2]);
416
417    if (numPartsToCvt > 0) {
418       cout << "Place EFI GPT (0xEE) partition first in MBR (good for GRUB)? ";
419       eeFirst = GetYN();
420    } // if
421
422    for (i = 0; i < numPartsToCvt; i++) {
423       j = partNums[i] - 1;
424       if (partitions[j].IsUsed()) {
425          mbrNum = i + (eeFirst == 'Y');
426          cout << "\nCreating entry for GPT partition #" << j + 1
427               << " (MBR partition #" << mbrNum + 1 << ")\n";
428          hybridPart.SetType(GetMBRTypeCode(partitions[j].GetHexType() / 256));
429          hybridPart.SetLocation(partitions[j].GetFirstLBA(), partitions[j].GetLengthLBA());
430          hybridPart.SetInclusion(PRIMARY);
431          cout << "Set the bootable flag? ";
432          if (GetYN() == 'Y')
433             hybridPart.SetStatus(0x80);
434          else
435             hybridPart.SetStatus(0x00);
436          hybridPart.SetInclusion(PRIMARY);
437       } else {
438          cerr << "\nGPT partition #" << j + 1 << " does not exist; skipping.\n";
439       } // if/else
440       hybridMBR.AddPart(mbrNum, hybridPart);
441    } // for
442
443    if (numPartsToCvt > 0) { // User opted to create a hybrid MBR....
444       // Create EFI protective partition that covers the start of the disk.
445       // If this location (covering the main GPT data structures) is omitted,
446       // Linux won't find any partitions on the disk.
447       hybridPart.SetLocation(1, hybridMBR.FindLastInFree(1));
448       hybridPart.SetStatus(0);
449       hybridPart.SetType(0xEE);
450       hybridPart.SetInclusion(PRIMARY);
451       // newNote firstLBA and lastLBA are computed later...
452       if (eeFirst == 'Y') {
453          hybridMBR.AddPart(0, hybridPart);
454       } else {
455          hybridMBR.AddPart(3, hybridPart);
456       } // else
457       hybridMBR.SetHybrid();
458
459       // ... and for good measure, if there are any partition spaces left,
460       // optionally create another protective EFI partition to cover as much
461       // space as possible....
462       if (hybridMBR.CountParts() < 4) { // unused entry....
463          cout << "\nUnused partition space(s) found. Use one to protect more partitions? ";
464          if (GetYN() == 'Y') {
465             cout << "Note: Default is 0xEE, but this may confuse Mac OS X.\n";
466             // Comment on above: Mac OS treats disks with more than one
467             // 0xEE MBR partition as MBR disks, not as GPT disks.
468             hexCode = GetMBRTypeCode(0xEE);
469             hybridMBR.MakeBiggestPart(3, hexCode);
470          } // if (GetYN() == 'Y')
471       } // if unused entry
472       protectiveMBR = hybridMBR;
473    } // if (numPartsToCvt > 0)
474 } // GPTDataTextUI::MakeHybrid()
475
476 // Convert the GPT to MBR form, storing partitions in the protectiveMBR
477 // variable. This function is necessarily limited; it may not be able to
478 // convert all partitions, depending on the disk size and available space
479 // before each partition (one free sector is required to create a logical
480 // partition, which are necessary to convert more than four partitions).
481 // Returns the number of converted partitions; if this value
482 // is over 0, the calling function should call DestroyGPT() to destroy
483 // the GPT data, call SaveMBR() to save the MBR, and then exit.
484 int GPTDataTextUI::XFormToMBR(void) {
485    uint32_t i;
486
487    protectiveMBR.EmptyMBR(0);
488    for (i = 0; i < numParts; i++) {
489       if (partitions[i].IsUsed()) {
490          protectiveMBR.MakePart(i, partitions[i].GetFirstLBA(),
491                                 partitions[i].GetLengthLBA(),
492                                 partitions[i].GetHexType() / 0x0100, 0);
493       } // if
494    } // for
495    protectiveMBR.MakeItLegal();
496    return protectiveMBR.DoMenu();
497 } // GPTDataTextUI::XFormToMBR()
498
499
500 /*********************************************************************
501  *                                                                   *
502  * The following functions provide the main menus for the gdisk      *
503  * program....                                                       *
504  *                                                                   *
505  *********************************************************************/
506
507 // Accept a command and execute it. Returns only when the user
508 // wants to exit (such as after a 'w' or 'q' command).
509 void GPTDataTextUI::MainMenu(string filename) {
510    int goOn = 1;
511    PartType typeHelper;
512    uint32_t temp1, temp2;
513
514    do {
515       cout << "\nCommand (? for help): ";
516       switch (ReadString()[0]) {
517          case '\0':
518             goOn = cin.good();
519             break;
520          case 'b': case 'B':
521             cout << "Enter backup filename to save: ";
522             SaveGPTBackup(ReadString());
523             break;
524          case 'c': case 'C':
525             if (GetPartRange(&temp1, &temp2) > 0)
526                SetName(GetPartNum());
527             else
528                cout << "No partitions\n";
529             break;
530          case 'd': case 'D':
531             DeletePartition();
532             break;
533          case 'i': case 'I':
534             ShowDetails();
535             break;
536          case 'l': case 'L':
537             typeHelper.ShowAllTypes();
538             break;
539          case 'n': case 'N':
540             CreatePartition();
541             break;
542          case 'o': case 'O':
543             cout << "This option deletes all partitions and creates a new protective MBR.\n"
544                  << "Proceed? ";
545             if (GetYN() == 'Y') {
546                ClearGPTData();
547                MakeProtectiveMBR();
548             } // if
549             break;
550          case 'p': case 'P':
551             DisplayGPTData();
552             break;
553          case 'q': case 'Q':
554             goOn = 0;
555             break;
556          case 'r': case 'R':
557             RecoveryMenu(filename);
558             goOn = 0;
559             break;
560          case 's': case 'S':
561             SortGPT();
562             cout << "You may need to edit /etc/fstab and/or your boot loader configuration!\n";
563             break;
564          case 't': case 'T':
565             ChangePartType();
566             break;
567          case 'v': case 'V':
568             Verify();
569             break;
570          case 'w': case 'W':
571             if (SaveGPTData() == 1)
572                goOn = 0;
573             break;
574          case 'x': case 'X':
575             ExpertsMenu(filename);
576             goOn = 0;
577             break;
578          default:
579             ShowCommands();
580             break;
581       } // switch
582    } while (goOn);
583 } // GPTDataTextUI::MainMenu()
584
585 void GPTDataTextUI::ShowCommands(void) {
586    cout << "b\tback up GPT data to a file\n";
587    cout << "c\tchange a partition's name\n";
588    cout << "d\tdelete a partition\n";
589    cout << "i\tshow detailed information on a partition\n";
590    cout << "l\tlist known partition types\n";
591    cout << "n\tadd a new partition\n";
592    cout << "o\tcreate a new empty GUID partition table (GPT)\n";
593    cout << "p\tprint the partition table\n";
594    cout << "q\tquit without saving changes\n";
595    cout << "r\trecovery and transformation options (experts only)\n";
596    cout << "s\tsort partitions\n";
597    cout << "t\tchange a partition's type code\n";
598    cout << "v\tverify disk\n";
599    cout << "w\twrite table to disk and exit\n";
600    cout << "x\textra functionality (experts only)\n";
601    cout << "?\tprint this menu\n";
602 } // GPTDataTextUI::ShowCommands()
603
604 // Accept a recovery & transformation menu command. Returns only when the user
605 // issues an exit command, such as 'w' or 'q'.
606 void GPTDataTextUI::RecoveryMenu(string filename) {
607    uint32_t numParts;
608    int goOn = 1, temp1;
609    
610    do {
611       cout << "\nRecovery/transformation command (? for help): ";
612       switch (ReadString()[0]) {
613          case '\0':
614             goOn = cin.good();
615             break;
616          case 'b': case 'B':
617             RebuildMainHeader();
618             break;
619          case 'c': case 'C':
620             cout << "Warning! This will probably do weird things if you've converted an MBR to\n"
621             << "GPT form and haven't yet saved the GPT! Proceed? ";
622             if (GetYN() == 'Y')
623                LoadSecondTableAsMain();
624             break;
625          case 'd': case 'D':
626             RebuildSecondHeader();
627             break;
628          case 'e': case 'E':
629             cout << "Warning! This will probably do weird things if you've converted an MBR to\n"
630             << "GPT form and haven't yet saved the GPT! Proceed? ";
631             if (GetYN() == 'Y')
632                LoadMainTable();
633             break;
634          case 'f': case 'F':
635             cout << "Warning! This will destroy the currently defined partitions! Proceed? ";
636             if (GetYN() == 'Y') {
637                if (LoadMBR(filename) == 1) { // successful load
638                   XFormPartitions();
639                } else {
640                   cout << "Problem loading MBR! GPT is untouched; regenerating protective MBR!\n";
641                   MakeProtectiveMBR();
642                } // if/else
643             } // if
644             break;
645          case 'g': case 'G':
646             numParts = GetNumParts();
647             temp1 = XFormToMBR();
648             if (temp1 > 0)
649                cout << "\nConverted " << temp1 << " partitions. Finalize and exit? ";
650             if ((temp1 > 0) && (GetYN() == 'Y')) {
651                if ((DestroyGPT() > 0) && (SaveMBR())) {
652                   goOn = 0;
653                } // if
654             } else {
655                MakeProtectiveMBR();
656                SetGPTSize(numParts, 0);
657                cout << "Note: New protective MBR created\n\n";
658             } // if/else
659             break;
660          case 'h': case 'H':
661             MakeHybrid();
662             break;
663          case 'i': case 'I':
664             ShowDetails();
665             break;
666          case 'l': case 'L':
667             cout << "Enter backup filename to load: ";
668             LoadGPTBackup(ReadString());
669             break;
670          case 'm': case 'M':
671             MainMenu(filename);
672             goOn = 0;
673             break;
674          case 'o': case 'O':
675             DisplayMBRData();
676             break;
677          case 'p': case 'P':
678             DisplayGPTData();
679             break;
680          case 'q': case 'Q':
681             goOn = 0;
682             break;
683          case 't': case 'T':
684             XFormDisklabel();
685             break;
686          case 'v': case 'V':
687             Verify();
688             break;
689          case 'w': case 'W':
690             if (SaveGPTData() == 1) {
691                goOn = 0;
692             } // if
693             break;
694          case 'x': case 'X':
695             ExpertsMenu(filename);
696             goOn = 0;
697             break;
698          default:
699             ShowRecoveryCommands();
700             break;
701       } // switch
702    } while (goOn);
703 } // GPTDataTextUI::RecoveryMenu()
704
705 void GPTDataTextUI::ShowRecoveryCommands(void) {
706    cout << "b\tuse backup GPT header (rebuilding main)\n";
707    cout << "c\tload backup partition table from disk (rebuilding main)\n";
708    cout << "d\tuse main GPT header (rebuilding backup)\n";
709    cout << "e\tload main partition table from disk (rebuilding backup)\n";
710    cout << "f\tload MBR and build fresh GPT from it\n";
711    cout << "g\tconvert GPT into MBR and exit\n";
712    cout << "h\tmake hybrid MBR\n";
713    cout << "i\tshow detailed information on a partition\n";
714    cout << "l\tload partition data from a backup file\n";
715    cout << "m\treturn to main menu\n";
716    cout << "o\tprint protective MBR data\n";
717    cout << "p\tprint the partition table\n";
718    cout << "q\tquit without saving changes\n";
719    cout << "t\ttransform BSD disklabel partition\n";
720    cout << "v\tverify disk\n";
721    cout << "w\twrite table to disk and exit\n";
722    cout << "x\textra functionality (experts only)\n";
723    cout << "?\tprint this menu\n";
724 } // GPTDataTextUI::ShowRecoveryCommands()
725
726 // Accept an experts' menu command. Returns only after the user
727 // selects an exit command, such as 'w' or 'q'.
728 void GPTDataTextUI::ExpertsMenu(string filename) {
729    GPTData secondDevice;
730    uint32_t temp1, temp2;
731    int goOn = 1;
732    string guidStr, device;
733    GUIDData aGUID;
734    ostringstream prompt;
735    
736    do {
737       cout << "\nExpert command (? for help): ";
738       switch (ReadString()[0]) {
739          case '\0':
740             goOn = cin.good();
741             break;
742          case 'a': case 'A':
743             if (GetPartRange(&temp1, &temp2) > 0)
744                SetAttributes(GetPartNum());
745             else
746                cout << "No partitions\n";
747             break;
748          case 'c': case 'C':
749             ChangeUniqueGuid();
750             break;
751          case 'd': case 'D':
752             cout << "Partitions will begin on " << GetAlignment()
753             << "-sector boundaries.\n";
754             break;
755          case 'e': case 'E':
756             cout << "Relocating backup data structures to the end of the disk\n";
757             MoveSecondHeaderToEnd();
758             break;
759          case 'f': case 'F':
760             RandomizeGUIDs();
761             break;
762          case 'g': case 'G':
763             cout << "Enter the disk's unique GUID ('R' to randomize): ";
764             guidStr = ReadString();
765             if ((guidStr.length() >= 32) || (guidStr[0] == 'R') || (guidStr[0] == 'r')) {
766                SetDiskGUID((GUIDData) guidStr);
767                cout << "The new disk GUID is " << GetDiskGUID() << "\n";
768             } else {
769                cout << "GUID is too short!\n";
770             } // if/else
771             break;
772          case 'h': case 'H':
773             RecomputeCHS();
774             break;
775          case 'i': case 'I':
776             ShowDetails();
777             break;
778          case 'l': case 'L':
779             prompt.seekp(0);
780             prompt << "Enter the sector alignment value (1-" << MAX_ALIGNMENT << ", default = "
781                    << DEFAULT_ALIGNMENT << "): ";
782             temp1 = GetNumber(1, MAX_ALIGNMENT, DEFAULT_ALIGNMENT, prompt.str());
783             SetAlignment(temp1);
784             break;
785          case 'm': case 'M':
786             MainMenu(filename);
787             goOn = 0;
788             break;
789          case 'n': case 'N':
790             MakeProtectiveMBR();
791             break;
792          case 'o': case 'O':
793             DisplayMBRData();
794             break;
795          case 'p': case 'P':
796             DisplayGPTData();
797             break;
798          case 'q': case 'Q':
799             goOn = 0;
800             break;
801          case 'r': case 'R':
802             RecoveryMenu(filename);
803             goOn = 0;
804             break;
805          case 's': case 'S':
806             ResizePartitionTable();
807             break;
808          case 't': case 'T':
809             SwapPartitions();
810             break;
811          case 'u': case 'U':
812             cout << "Type device filename, or press <Enter> to exit: ";
813             device = ReadString();
814             if (device.length() > 0) {
815                secondDevice = *this;
816                secondDevice.SetDisk(device);
817                secondDevice.SaveGPTData(0);
818             } // if
819             break;
820          case 'v': case 'V':
821             Verify();
822             break;
823          case 'w': case 'W':
824             if (SaveGPTData() == 1) {
825                goOn = 0;
826             } // if
827             break;
828          case 'z': case 'Z':
829             if (DestroyGPTwPrompt() == 1) {
830                goOn = 0;
831             }
832             break;
833          default:
834             ShowExpertCommands();
835             break;
836       } // switch
837    } while (goOn);
838 } // GPTDataTextUI::ExpertsMenu()
839
840 void GPTDataTextUI::ShowExpertCommands(void) {
841    cout << "a\tset attributes\n";
842    cout << "c\tchange partition GUID\n";
843    cout << "d\tdisplay the sector alignment value\n";
844    cout << "e\trelocate backup data structures to the end of the disk\n";
845    cout << "g\tchange disk GUID\n";
846    cout << "h\trecompute CHS values in protective/hybrid MBR\n";
847    cout << "i\tshow detailed information on a partition\n";
848    cout << "l\tset the sector alignment value\n";
849    cout << "m\treturn to main menu\n";
850    cout << "n\tcreate a new protective MBR\n";
851    cout << "o\tprint protective MBR data\n";
852    cout << "p\tprint the partition table\n";
853    cout << "q\tquit without saving changes\n";
854    cout << "r\trecovery and transformation options (experts only)\n";
855    cout << "s\tresize partition table\n";
856    cout << "t\ttranspose two partition table entries\n";
857    cout << "u\tReplicate partition table on new device\n";
858    cout << "v\tverify disk\n";
859    cout << "w\twrite table to disk and exit\n";
860    cout << "z\tzap (destroy) GPT data structures and exit\n";
861    cout << "?\tprint this menu\n";
862 } // GPTDataTextUI::ShowExpertCommands()
863
864
865
866 /********************************
867  *                              *
868  * Non-class support functions. *
869  *                              *
870  ********************************/
871
872 // GetMBRTypeCode() doesn't really belong in the class, since it's MBR-
873 // specific, but it's also user I/O-related, so I want to keep it in
874 // this file....
875
876 // Get an MBR type code from the user and return it
877 int GetMBRTypeCode(int defType) {
878    string line;
879    int typeCode;
880
881    cout.setf(ios::uppercase);
882    cout.fill('0');
883    do {
884       cout << "Enter an MBR hex code (default " << hex;
885       cout.width(2);
886       cout << defType << "): " << dec;
887       line = ReadString();
888       if (line[0] == '\0')
889          typeCode = defType;
890       else
891          typeCode = StrToHex(line, 0);
892    } while ((typeCode <= 0) || (typeCode > 255));
893    cout.fill(' ');
894    return typeCode;
895 } // GetMBRTypeCode
896
897 // Note: ReadUString() is here rather than in support.cc so that the ICU
898 // libraries need not be linked to fixparts.
899
900 // Reads a Unicode string from stdin, returning it as an ICU-style string.
901 // Note that the returned string will NOT include the carriage return
902 // entered by the user. Relies on the ICU constructor from a string
903 // encoded in the current codepage to work.
904 UnicodeString ReadUString(void) {
905    return ReadString().c_str();
906 } // ReadUString()
907