blob: f49cab1062ac89aec99b1162c7554357314b1759 [file] [log] [blame]
srs5694fad06422010-02-19 17:19:17 -05001/*
2 Copyright (C) <2010> <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 <iostream>
26#include <sstream>
27#include "attributes.h"
28#include "gpttext.h"
29
30using namespace std;
31
32/*******************************************
33* *
34* GPTDataText class and related structures *
35* *
36********************************************/
37
38GPTDataTextUI::GPTDataTextUI(void) : GPTData() {
39} // default constructor
40
41GPTDataTextUI::GPTDataTextUI(string filename) : GPTData(filename) {
42} // constructor passing filename
43
44GPTDataTextUI::~GPTDataTextUI(void) {
45} // default destructor
46
47/*********************************************************************
48 * *
49 * The following functions are extended (interactive) versions of *
50 * simpler functions in the base class.... *
51 * *
52 *********************************************************************/
53
54// Overridden function; calls base-class function and then makes
55// additional queries of the user, if the base-class function can't
56// decide what to do.
57WhichToUse GPTDataTextUI::UseWhichPartitions(void) {
58 WhichToUse which;
59 MBRValidity mbrState;
60 int answer;
61
62 which = GPTData::UseWhichPartitions();
63 if ((which != use_abort) || beQuiet)
64 return which;
65
66 // If we get past here, it means that the non-interactive tests were
67 // inconclusive, so we must ask the user which table to use....
68 mbrState = protectiveMBR.GetValidity();
69
70 if ((state == gpt_valid) && (mbrState == mbr)) {
71 cout << "Found valid MBR and GPT. Which do you want to use?\n";
72 answer = GetNumber(1, 3, 2, " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: ");
73 if (answer == 1) {
74 which = use_mbr;
75 } else if (answer == 2) {
76 which = use_gpt;
77 cout << "Using GPT and creating fresh protective MBR.\n";
78 } else which = use_new;
79 } // if
80
81 // Nasty decisions here -- GPT is present, but corrupt (bad CRCs or other
82 // problems)
83 if (state == gpt_corrupt) {
84 if ((mbrState == mbr) || (mbrState == hybrid)) {
85 cout << "Found valid MBR and corrupt GPT. Which do you want to use? (Using the\n"
86 << "GPT MAY permit recovery of GPT data.)\n";
87 answer = GetNumber(1, 3, 2, " 1 - MBR\n 2 - GPT\n 3 - Create blank GPT\n\nYour answer: ");
88 if (answer == 1) {
89 which = use_mbr;
90 } else if (answer == 2) {
91 which = use_gpt;
92 } else which = use_new;
93 } else if (mbrState == invalid) {
94 cout << "Found invalid MBR and corrupt GPT. What do you want to do? (Using the\n"
95 << "GPT MAY permit recovery of GPT data.)\n";
96 answer = GetNumber(1, 2, 1, " 1 - GPT\n 2 - Create blank GPT\n\nYour answer: ");
97 if (answer == 1) {
98 which = use_gpt;
99 } else which = use_new;
100 } // if/else/else
101 } // if (corrupt GPT)
102
103 return which;
104} // UseWhichPartitions()
105
106// Ask the user for a partition number; and prompt for verification
107// if the requested partition isn't of a known BSD type.
108// Lets the base-class function do the work, and returns its value (the
109// number of converted partitions).
110int GPTDataTextUI::XFormDisklabel(void) {
111 uint32_t partNum;
112 uint16_t hexCode;
113 int goOn = 1, numDone = 0;
114 BSDData disklabel;
115
116 partNum = GetPartNum();
117
118 // Now see if the specified partition has a BSD type code....
119 hexCode = partitions[partNum].GetHexType();
120 if ((hexCode != 0xa500) && (hexCode != 0xa900)) {
121 cout << "Specified partition doesn't have a disklabel partition type "
122 << "code.\nContinue anyway? ";
123 goOn = (GetYN() == 'Y');
124 } // if
125
126 if (goOn)
127 numDone = GPTData::XFormDisklabel(partNum);
128
129 return numDone;
130} // GPTData::XFormDisklabel(int i)
131
132/*********************************************************************
133 * *
134 * Begin functions that obtain information from the users, and often *
135 * do something with that information (call other functions) *
136 * *
137 *********************************************************************/
138
139// Prompts user for partition number and returns the result.
140uint32_t GPTDataTextUI::GetPartNum(void) {
141 uint32_t partNum;
142 uint32_t low, high;
143 ostringstream prompt;
144
145 if (GetPartRange(&low, &high) > 0) {
146 prompt << "Partition number (" << low + 1 << "-" << high + 1 << "): ";
147 partNum = GetNumber(low + 1, high + 1, low, prompt.str());
148 } else partNum = 1;
149 return (partNum - 1);
150} // GPTDataTextUI::GetPartNum()
151
152// What it says: Resize the partition table. (Default is 128 entries.)
153void GPTDataTextUI::ResizePartitionTable(void) {
154 int newSize;
155 ostringstream prompt;
156 uint32_t curLow, curHigh;
157
158 cout << "Current partition table size is " << mainHeader.numParts << ".\n";
159 GetPartRange(&curLow, &curHigh);
160 curHigh++; // since GetPartRange() returns numbers starting from 0...
161 // There's no point in having fewer than four partitions....
162 if (curHigh < (blockSize / GPT_SIZE))
163 curHigh = blockSize / GPT_SIZE;
164 prompt << "Enter new size (" << curHigh << " up, default " << NUM_GPT_ENTRIES << "): ";
165 newSize = GetNumber(4, 65535, 128, prompt.str());
166 if (newSize < 128) {
167 cout << "Caution: The partition table size should officially be 16KB or larger,\n"
168 << "which works out to 128 entries. In practice, smaller tables seem to\n"
169 << "work with most OSes, but this practice is risky. I'm proceeding with\n"
170 << "the resize, but you may want to reconsider this action and undo it.\n\n";
171 } // if
172 SetGPTSize(newSize);
173} // GPTDataTextUI::ResizePartitionTable()
174
175// Interactively create a partition
176void GPTDataTextUI::CreatePartition(void) {
177 uint64_t firstBlock, firstInLargest, lastBlock, sector;
178 uint32_t firstFreePart = 0;
179 ostringstream prompt1, prompt2, prompt3;
180 int partNum;
181
182 // Find first free partition...
183 while (partitions[firstFreePart].GetFirstLBA() != 0) {
184 firstFreePart++;
185 } // while
186
187 if (((firstBlock = FindFirstAvailable()) != 0) &&
188 (firstFreePart < mainHeader.numParts)) {
189 lastBlock = FindLastAvailable();
190 firstInLargest = FindFirstInLargest();
191
192 // Get partition number....
193 do {
194 prompt1 << "Partition number (" << firstFreePart + 1 << "-" << mainHeader.numParts
195 << ", default " << firstFreePart + 1 << "): ";
196 partNum = GetNumber(firstFreePart + 1, mainHeader.numParts,
197 firstFreePart + 1, prompt1.str()) - 1;
198 if (partitions[partNum].GetFirstLBA() != 0)
199 cout << "partition " << partNum + 1 << " is in use.\n";
200 } while (partitions[partNum].GetFirstLBA() != 0);
201
202 // Get first block for new partition...
203 prompt2 << "First sector (" << firstBlock << "-" << lastBlock << ", default = "
204 << firstInLargest << ") or {+-}size{KMGT}: ";
205 do {
206 sector = GetSectorNum(firstBlock, lastBlock, firstInLargest, prompt2.str());
207 } while (IsFree(sector) == 0);
208 Align(&sector); // Align sector to correct multiple
209 firstBlock = sector;
210
211 // Get last block for new partitions...
212 lastBlock = FindLastInFree(firstBlock);
213 prompt3 << "Last sector (" << firstBlock << "-" << lastBlock << ", default = "
214 << lastBlock << ") or {+-}size{KMGT}: ";
215 do {
216 sector = GetSectorNum(firstBlock, lastBlock, lastBlock, prompt3.str());
217 } while (IsFree(sector) == 0);
218 lastBlock = sector;
219
220 firstFreePart = GPTData::CreatePartition(partNum, firstBlock, lastBlock);
221 partitions[partNum].ChangeType();
222 partitions[partNum].SetDefaultDescription();
223 } else {
224 cout << "No free sectors available\n";
225 } // if/else
226} // GPTDataTextUI::CreatePartition()
227
228// Interactively delete a partition (duh!)
229void GPTDataTextUI::DeletePartition(void) {
230 int partNum;
231 uint32_t low, high;
232 ostringstream prompt;
233
234 if (GetPartRange(&low, &high) > 0) {
235 prompt << "Partition number (" << low + 1 << "-" << high + 1 << "): ";
236 partNum = GetNumber(low + 1, high + 1, low, prompt.str());
237 GPTData::DeletePartition(partNum - 1);
238 } else {
239 cout << "No partitions\n";
240 } // if/else
241} // GPTDataTextUI::DeletePartition()
242
243// Prompt user for a partition number, then change its type code
244// using ChangeGPTType(struct GPTPartition*) function.
245void GPTDataTextUI::ChangePartType(void) {
246 int partNum;
247 uint32_t low, high;
248
249 if (GetPartRange(&low, &high) > 0) {
250 partNum = GetPartNum();
251 partitions[partNum].ChangeType();
252 } else {
253 cout << "No partitions\n";
254 } // if/else
255} // GPTDataTextUI::ChangePartType()
256
257// Partition attributes seem to be rarely used, but I want a way to
258// adjust them for completeness....
259void GPTDataTextUI::SetAttributes(uint32_t partNum) {
260 Attributes theAttr;
261
262 theAttr.SetAttributes(partitions[partNum].GetAttributes());
263 theAttr.DisplayAttributes();
264 theAttr.ChangeAttributes();
265 partitions[partNum].SetAttributes(theAttr.GetAttributes());
266} // GPTDataTextUI::SetAttributes()
267
268// Ask user for two partition numbers and swap them in the table. Note that
269// this just reorders table entries; it doesn't adjust partition layout on
270// the disk.
271// Returns 1 if successful, 0 if not. (If user enters identical numbers, it
272// counts as successful.)
273int GPTDataTextUI::SwapPartitions(void) {
274 int partNum1, partNum2, didIt = 0;
275 uint32_t low, high;
276 ostringstream prompt;
277 GPTPart temp;
278
279 if (GetPartRange(&low, &high) > 0) {
280 partNum1 = GetPartNum();
281 if (high >= mainHeader.numParts - 1)
282 high = 0;
283 prompt << "New partition number (1-" << mainHeader.numParts
284 << ", default " << high + 2 << "): ";
285 partNum2 = GetNumber(1, mainHeader.numParts, high + 2, prompt.str()) - 1;
286 didIt = GPTData::SwapPartitions(partNum1, partNum2);
287 } else {
288 cout << "No partitions\n";
289 } // if/else
290 return didIt;
291} // GPTDataTextUI::SwapPartitionNumbers()
292
293// This function destroys the on-disk GPT structures. Returns 1 if the user
294// confirms destruction, 0 if the user aborts or if there's a disk error.
295int GPTDataTextUI::DestroyGPTwPrompt(void) {
296 int allOK = 1;
297
298 if ((apmFound) || (bsdFound)) {
299 cout << "WARNING: APM or BSD disklabel structures detected! This operation could\n"
300 << "damage any APM or BSD partitions on this disk!\n";
301 } // if APM or BSD
302 cout << "\a\aAbout to wipe out GPT on " << device << ". Proceed? ";
303 if (GetYN() == 'Y') {
304 if (DestroyGPT()) {
305 // Note on below: Touch the MBR only if the user wants it completely
306 // blanked out. Version 0.4.2 deleted the 0xEE partition and re-wrote
307 // the MBR, but this could wipe out a valid MBR that the program
308 // had subsequently discarded (say, if it conflicted with older GPT
309 // structures).
310 cout << "Blank out MBR? ";
311 if (GetYN() == 'Y') {
312 DestroyMBR();
313 } else {
314 cout << "MBR is unchanged. You may need to delete an EFI GPT (0xEE) partition\n"
315 << "with fdisk or another tool.\n";
316 } // if/else
317 } else allOK = 0; // if GPT structures destroyed
318 } else allOK = 0; // if user confirms destruction
319 return (allOK);
320} // GPTDataTextUI::DestroyGPTwPrompt()
321
322// Get partition number from user and then call ShowPartDetails(partNum)
323// to show its detailed information
324void GPTDataTextUI::ShowDetails(void) {
325 int partNum;
326 uint32_t low, high;
327
328 if (GetPartRange(&low, &high) > 0) {
329 partNum = GetPartNum();
330 ShowPartDetails(partNum);
331 } else {
332 cout << "No partitions\n";
333 } // if/else
334} // GPTDataTextUI::ShowDetails()
335
336// Create a hybrid MBR -- an ugly, funky thing that helps GPT work with
337// OSes that don't understand GPT.
338void GPTDataTextUI::MakeHybrid(void) {
339 uint32_t partNums[3];
340 char line[255];
341 char* junk;
342 int numParts, i, j, mbrNum, bootable = -1;
343 int gptParts[4], mbrTypes[4];
344 char fillItUp = 'M'; // fill extra partition entries? (Yes/No/Maybe)
345 char eeFirst = 'Y'; // Whether EFI GPT (0xEE) partition comes first in table
346
347 cout << "\nWARNING! Hybrid MBRs are flaky and potentially dangerous! If you decide not\n"
348 << "to use one, just hit the Enter key at the below prompt and your MBR\n"
349 << "partition table will be untouched.\n\n\a";
350
351 // Now get the numbers of up to three partitions to add to the
352 // hybrid MBR....
353 cout << "Type from one to three GPT partition numbers, separated by spaces, to be\n"
354 << "added to the hybrid MBR, in sequence: ";
355 junk = fgets(line, 255, stdin);
356 numParts = sscanf(line, "%d %d %d", &partNums[0], &partNums[1], &partNums[2]);
357
358 if (numParts > 0) {
359 cout << "Place EFI GPT (0xEE) partition first in MBR (good for GRUB)? ";
360 eeFirst = GetYN();
361 } // if
362
363 for (i = 0; i < 4; i++)
364 gptParts[i] = MBR_EMPTY;
365
366 for (i = 0; i < numParts; i++) {
367 j = partNums[i] - 1;
368 mbrNum = i + (eeFirst == 'Y');
369 cout << "\nCreating entry for GPT partition #" << j + 1
370 << " (MBR partition #" << mbrNum + 1 << ")\n";
371 gptParts[mbrNum] = j;
372 mbrTypes[mbrNum] = GetMBRTypeCode(partitions[j].GetHexType() / 256);
373 cout << "Set the bootable flag? ";
374 if (GetYN() == 'Y')
375 bootable = mbrNum;
376 } // for
377
378 if (numParts > 0) { // User opted to create a hybrid MBR....
379 // Create EFI protective partition that covers the start of the disk.
380 // If this location (covering the main GPT data structures) is omitted,
381 // Linux won't find any partitions on the disk.
382 if (eeFirst == 'Y')
383 mbrNum = 0;
384 else
385 mbrNum = numParts;
386 gptParts[mbrNum] = MBR_EFI_GPT;
387 mbrTypes[mbrNum] = 0xEE;
388 protectiveMBR.SetHybrid();
389
390 // ... and for good measure, if there are any partition spaces left,
391 // optionally create another protective EFI partition to cover as much
392 // space as possible....
393 for (i = 0; i < 4; i++) {
394 if (gptParts[i] == MBR_EMPTY) { // unused entry....
395 if (fillItUp == 'M') {
396 cout << "\nUnused partition space(s) found. Use one to protect more partitions? ";
397 fillItUp = GetYN();
398 mbrTypes[i] = 0x00; // use this to flag a need to get type code
399 } // if
400 if (fillItUp == 'Y') {
401 while ((mbrTypes[i] <= 0) || (mbrTypes[i] > 255)) {
402 cout << "Enter an MBR hex code (EE is EFI GPT, but may confuse MacOS): ";
403 // Comment on above: Mac OS treats disks with more than one
404 // 0xEE MBR partition as MBR disks, not as GPT disks.
405 junk = fgets(line, 255, stdin);
406 sscanf(line, "%x", &mbrTypes[i]);
407 if (line[0] == '\n')
408 mbrTypes[i] = 0;
409 } // while
410 gptParts[i] = MBR_EFI_GPT;
411 } // if (fillItUp == 'Y')
412 } // if unused entry
413 } // for (i = 0; i < 4; i++)
414 PartsToMBR(gptParts, mbrTypes);
415 if (bootable > 0)
416 protectiveMBR.SetPartBootable(bootable);
417 } // if (numParts > 0)
418} // GPTDataTextUI::MakeHybrid()
419
420// Convert the GPT to MBR form. This function is necessarily limited; it
421// handles at most four partitions and creates layouts that ignore CHS
422// geometries. Returns the number of converted partitions; if this value
423// is over 0, the calling function should call DestroyGPT() to destroy
424// the GPT data, and then exit.
425int GPTDataTextUI::XFormToMBR(void) {
426 char line[255];
427 char* junk;
428 int j, numParts, bootable = -1, numConverted = 0, numReallyConverted;
429 int gptParts[4], mbrTypes[4];
430 uint32_t i, partNums[4];
431
432 // Get the numbers of up to four partitions to add to the
433 // hybrid MBR....
434 numParts = CountParts();
435 cout << "Counted " << numParts << " partitions.\n";
436
437 if (numParts > 4) { // Over four partitions; engage in triage
438 cout << "Type from one to four GPT partition numbers, separated by spaces, to be\n"
439 << "used in the MBR, in sequence: ";
440 junk = fgets(line, 255, stdin);
441 numParts = sscanf(line, "%d %d %d %d", &partNums[0], &partNums[1],
442 &partNums[2], &partNums[3]);
443 } else { // Four or fewer partitions; convert them all
444 i = j = 0;
445 while ((j < numParts) && (i < mainHeader.numParts)) {
446 if (partitions[i].GetFirstLBA() > 0) { // if GPT part. is defined
447 partNums[j++] = ++i; // flag it for conversion
448 } else i++;
449 } // while
450 } // if/else
451
452 for (i = 0; i < 4; i++)
453 gptParts[i] = MBR_EMPTY;
454
455 for (i = 0; i < (uint32_t) numParts; i++) {
456 j = partNums[i] - 1;
457 cout << "\nCreating entry for GPT partition #" << j + 1
458 << " (MBR partition #" << i + 1 << ")\n";
459 gptParts[i] = j;
460 mbrTypes[i] = GetMBRTypeCode(partitions[j].GetHexType() / 256);
461 cout << "Set the bootable flag? ";
462 if (GetYN() == 'Y')
463 bootable = i;
464 numConverted++;
465 } // for
466 numReallyConverted = PartsToMBR(gptParts, mbrTypes);
467 if (bootable > 0)
468 protectiveMBR.SetPartBootable(bootable);
469 if (numReallyConverted == numConverted) {
470 protectiveMBR.WriteMBRData(&myDisk);
471 } else {
472 cerr << "Error converting partitions to MBR; aborting operation!\n";
473 numConverted = 0;
474 protectiveMBR.MakeProtectiveMBR();
475 } // if/else
476 return numConverted;
477} // GPTDataTextUI::XFormToMBR()
478
479/*********************************************************************
480 * *
481 * The following doesn't really belong in the class, since it's MBR- *
482 * specific, but it's also user I/O-related, so I want to keep it in *
483 * this file.... *
484 * *
485 *********************************************************************/
486
487// Get an MBR type code from the user and return it
488int GetMBRTypeCode(int defType) {
489 char line[255];
490 char* junk;
491 int typeCode;
492
493 cout.setf(ios::uppercase);
494 cout.fill('0');
495 do {
496 cout << "Enter an MBR hex code (default " << hex;
497 cout.width(2);
498 cout << defType << "): " << dec;
499 junk = fgets(line, 255, stdin);
500 if (line[0] == '\n')
501 typeCode = defType;
502 else
503 sscanf(line, "%x", &typeCode);
504 } while ((typeCode <= 0) || (typeCode > 255));
505 cout.fill(' ');
506 return typeCode;
507} // GetMBRTypeCode