xref: /third_party/gptfdisk/support.cc (revision cf200d32)
1// support.cc
2// Non-class support functions for gdisk program.
3// Primarily by Rod Smith, February 2009, but with a few functions
4// copied from other sources (see attributions below).
5
6/* This program is copyright (c) 2009-2024 by Roderick W. Smith. It is distributed
7  under the terms of the GNU GPL version 2, as detailed in the COPYING file. */
8
9#define __STDC_LIMIT_MACROS
10#ifndef __STDC_CONSTANT_MACROS
11#define __STDC_CONSTANT_MACROS
12#endif
13#ifndef __STDC_FORMAT_MACROS
14#define __STDC_FORMAT_MACROS
15#endif
16
17#include <inttypes.h>
18#include <stdio.h>
19#include <stdint.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <string.h>
23#include <sys/stat.h>
24#include <string>
25#include <cctype>
26#include <algorithm>
27#include <iostream>
28#include <sstream>
29#include "support.h"
30
31#include <sys/types.h>
32
33// As of 1/2010, BLKPBSZGET is very new, so I'm explicitly defining it if
34// it's not already defined. This should become unnecessary in the future.
35// Note that this is a Linux-only ioctl....
36#ifndef BLKPBSZGET
37#define BLKPBSZGET _IO(0x12,123)
38#endif
39
40using namespace std;
41
42// Reads a string from stdin, returning it as a C++-style string.
43// Note that the returned string will NOT include the carriage return
44// entered by the user.
45#ifdef EFI
46extern int __sscanf( const char * str , const char * format , ... ) ;
47string ReadString(void) {
48   string inString;
49   char efiString[256];
50   int stringLength;
51
52   if (fgets(efiString, 255, stdin) != NULL) {
53      stringLength = strlen(efiString);
54      if ((stringLength > 0) && (efiString[stringLength - 1] == '\n'))
55          efiString[stringLength - 1] = '\0';
56      inString = efiString;
57   } else {
58      inString = "";
59   }
60   return inString;
61} // ReadString()
62#else
63string ReadString(void) {
64   string inString;
65
66   cout << flush;
67   getline(cin, inString);
68   if (!cin.good())
69      exit(5);
70   return inString;
71} // ReadString()
72#endif
73
74// Get a numeric value from the user, between low and high (inclusive).
75// Keeps looping until the user enters a value within that range.
76// If user provides no input, def (default value) is returned.
77// (If def is outside of the low-high range, an explicit response
78// is required.)
79uint64_t GetNumber(uint64_t low, uint64_t high, uint64_t def, const string & prompt) {
80   uint64_t response, num;
81   char line[255];
82
83   if (low != high) { // bother only if low and high differ...
84      do {
85         cout << prompt << flush;
86         cin.getline(line, 255);
87         if (!cin.good())
88            exit(5);
89         num = sscanf(line, "%" PRIu64, &response);
90         if (num == 1) { // user provided a response
91            if ((response < low) || (response > high))
92               cout << "Value out of range\n";
93         } else { // user hit enter; return default
94            response = def;
95         } // if/else
96      } while ((response < low) || (response > high));
97   } else { // low == high, so return this value
98      cout << "Using " << low << "\n";
99      response = low;
100   } // else
101   return (response);
102} // GetNumber()
103
104// Gets a Y/N response (and converts lowercase to uppercase)
105char GetYN(void) {
106   char response;
107   string line;
108   bool again = 0 ;
109
110   do {
111      if ( again ) { cout << "Your option? " ; }
112      again = 1 ;
113      cout << "(Y/N): " << flush;
114      line = ReadString();
115      response = toupper(line[0]);
116   } while ((response != 'Y') && (response != 'N'));
117   return response;
118} // GetYN(void)
119
120// Convert an IEEE-1541-2002 value (K, M, G, T, P, or E) to its equivalent in
121// number of sectors. If no units are appended, interprets as the number
122// of sectors; otherwise, interprets as number of specified units and
123// converts to sectors. For instance, with 512-byte sectors, "1K" converts
124// to 2. If value includes a "+", adds low and subtracts 1; if inValue
125// inclues a "-", subtracts from high. If IeeeValue is empty, returns def.
126// Returns final sector value. In case inValue is invalid, returns 0 (a
127// sector value that's always in use on GPT and therefore invalid); and if
128// inValue works out to something outside the range low-high, returns the
129// computed value; the calling function is responsible for checking the
130// validity of this value.
131// If inValue contains a decimal number (e.g., "9.5G"), quietly truncate it
132// (to "9G" in this example).
133// NOTE: There's a difference in how GCC and VC++ treat oversized values
134// (say, "999999999999999999999") read via the ">>" operator; GCC turns
135// them into the maximum value for the type, whereas VC++ turns them into
136// 0 values. The result is that IeeeToInt() returns UINT64_MAX when
137// compiled with GCC (and so the value is rejected), whereas when VC++
138// is used, the default value is returned.
139uint64_t IeeeToInt(string inValue, uint64_t sSize, uint64_t low, uint64_t high, uint32_t sectorAlignment, uint64_t def) {
140   uint64_t response = def, bytesPerUnit, mult = 1, divide = 1;
141   size_t foundAt = 0;
142   char suffix = ' ', plusFlag = ' ';
143   string suffixes = "KMGTPE";
144   int badInput = 0; // flag bad input; once this goes to 1, other values are irrelevant
145
146   if (sSize == 0) {
147      sSize = SECTOR_SIZE;
148      cerr << "Bug: Sector size invalid in IeeeToInt()!\n";
149   } // if
150
151   // Remove leading spaces, if present
152   while (inValue[0] == ' ')
153      inValue.erase(0, 1);
154
155   // If present, flag and remove leading plus or minus sign
156   if ((inValue[0] == '+') || (inValue[0] == '-')) {
157      plusFlag = inValue[0];
158      inValue.erase(0, 1);
159   } // if
160
161   // Extract numeric response and, if present, suffix
162   istringstream inString(inValue);
163   if (((inString.peek() < '0') || (inString.peek() > '9')) && (inString.peek() != -1))
164      badInput = 1;
165   inString >> response >> suffix;
166   suffix = toupper(suffix);
167   foundAt = suffixes.find(suffix);
168   // If suffix is invalid, try to find a valid one. Done because users
169   // sometimes enter decimal numbers; when they do, suffix becomes
170   // '.', and we need to truncate the number and find the real suffix.
171   while (foundAt > (suffixes.length() - 1) && inString.peek() != -1) {
172      inString >> suffix;
173      foundAt = suffixes.find(suffix);
174      suffix = toupper(suffix);
175   }
176   // If no response, or if response == 0, use default (def)
177   if ((inValue.length() == 0) || (response == 0)) {
178      response = def;
179      suffix = ' ';
180      plusFlag = ' ';
181   } // if
182
183   // Find multiplication and division factors for the suffix
184   if (foundAt != string::npos) {
185      bytesPerUnit = UINT64_C(1) << (10 * (foundAt + 1));
186      mult = bytesPerUnit / sSize;
187      divide = sSize / bytesPerUnit;
188   } // if
189
190   // Adjust response based on multiplier and plus flag, if present
191   if (mult > 1) {
192      if (response > (UINT64_MAX / mult))
193         badInput = 1;
194      else
195         response *= mult;
196   } else if (divide > 1) {
197         response /= divide;
198   } // if/elseif
199
200   if (plusFlag == '+') {
201      // Recompute response based on low part of range (if default is within
202      // sectorAlignment sectors of high, which should be the case when
203      // prompting for the end of a range) or the defaut value (if default is
204      // further away from the high value, which should be the case for the
205      // first sector of a partition).
206      if ((high - def) < sectorAlignment) {
207         if (response > 0)
208            response--;
209         if (response > (UINT64_MAX - low))
210            badInput = 1;
211         else
212            response = response + low;
213      } else {
214         if (response > (UINT64_MAX - def))
215            badInput = 1;
216         else
217            response = response + def;
218      } // if/else
219   } else if (plusFlag == '-') {
220      if (response > high)
221         badInput = 1;
222      else
223         response = high - response;
224   } // if
225
226   if (badInput)
227      response = UINT64_C(0);
228
229   return response;
230} // IeeeToInt()
231
232// Takes a size and converts this to a size in IEEE-1541-2002 units (KiB, MiB,
233// GiB, TiB, PiB, or EiB), returned in C++ string form. The size is either in
234// units of the sector size or, if that parameter is omitted, in bytes.
235// (sectorSize defaults to 1). Note that this function uses peculiar
236// manual computation of decimal value rather than simply setting
237// theValue.precision() because this isn't possible using the available
238// EFI library.
239string BytesToIeee(uint64_t size, uint32_t sectorSize) {
240   uint64_t sizeInIeee;
241   uint64_t previousIeee;
242   float decimalIeee;
243   uint64_t index = 0;
244   string units, prefixes = " KMGTPEZ";
245   ostringstream theValue;
246
247   sizeInIeee = previousIeee = size * (uint64_t) sectorSize;
248   while ((sizeInIeee > 1024) && (index < (prefixes.length() - 1))) {
249      index++;
250      previousIeee = sizeInIeee;
251      sizeInIeee /= 1024;
252   } // while
253   if (prefixes[index] == ' ') {
254      theValue << sizeInIeee << " bytes";
255   } else {
256      units = "  iB";
257      units[1] = prefixes[index];
258      decimalIeee = ((float) previousIeee -
259                     ((float) sizeInIeee * 1024.0) + 51.2) / 102.4;
260      if (decimalIeee >= 10.0) {
261         decimalIeee = 0.0;
262         sizeInIeee++;
263      }
264      theValue << sizeInIeee << "." << (uint32_t) decimalIeee << units;
265   } // if/else
266   return theValue.str();
267} // BytesToIeee()
268
269// Converts two consecutive characters in the input string into a
270// number, interpreting the string as a hexadecimal number, starting
271// at the specified position.
272unsigned char StrToHex(const string & input, unsigned int position) {
273   unsigned char retval = 0x00;
274   unsigned int temp;
275
276   if (input.length() > position) {
277      sscanf(input.substr(position, 2).c_str(), "%x", &temp);
278      retval = (unsigned char) temp;
279   } // if
280   return retval;
281} // StrToHex()
282
283// Returns 1 if input can be interpreted as a hexadecimal number --
284// all characters must be spaces, digits, or letters A-F (upper- or
285// lower-case), with at least one valid hexadecimal digit; with the
286// exception of the first two characters, which may be "0x"; otherwise
287// returns 0.
288int IsHex(string input) {
289   int isHex = 1, foundHex = 0, i;
290
291   if (input.substr(0, 2) == "0x")
292      input.erase(0, 2);
293   for (i = 0; i < (int) input.length(); i++) {
294      if ((input[i] < '0') || (input[i] > '9')) {
295         if ((input[i] < 'A') || (input[i] > 'F')) {
296            if ((input[i] < 'a') || (input[i] > 'f')) {
297               if ((input[i] != ' ') && (input[i] != '\n')) {
298                  isHex = 0;
299               }
300            } else foundHex = 1;
301         } else foundHex = 1;
302      } else foundHex = 1;
303   } // for
304   if (!foundHex)
305      isHex = 0;
306   return isHex;
307} // IsHex()
308
309// Return 1 if the CPU architecture is little endian, 0 if it's big endian....
310int IsLittleEndian(void) {
311   int littleE = 1; // assume little-endian (Intel-style)
312   union {
313      uint32_t num;
314      unsigned char uc[sizeof(uint32_t)];
315   } endian;
316
317   endian.num = 1;
318   if (endian.uc[0] != (unsigned char) 1) {
319      littleE = 0;
320   } // if
321   return (littleE);
322} // IsLittleEndian()
323
324// Reverse the byte order of theValue; numBytes is number of bytes
325void ReverseBytes(void* theValue, int numBytes) {
326   char* tempValue = NULL;
327   int i;
328
329   tempValue = new char [numBytes];
330   if (tempValue != NULL) {
331      memcpy(tempValue, theValue, numBytes);
332      for (i = 0; i < numBytes; i++)
333         ((char*) theValue)[i] = tempValue[numBytes - i - 1];
334      delete[] tempValue;
335   } else {
336      cerr << "Could not allocate memory in ReverseBytes()! Terminating\n";
337      exit(1);
338   } // if/else
339} // ReverseBytes()
340
341// On Windows, display a warning and ask whether to continue. If the user elects
342// not to continue, exit immediately.
343void WinWarning(void) {
344   #ifdef _WIN32
345   cout << "\a************************************************************************\n"
346        << "Most versions of Windows cannot boot from a GPT disk except on a UEFI-based\n"
347        << "computer, and most varieties prior to Vista cannot read GPT disks. Therefore,\n"
348        << "you should exit now unless you understand the implications of converting MBR\n"
349        << "to GPT or creating a new GPT disk layout!\n"
350        << "************************************************************************\n\n";
351   cout << "Are you SURE you want to continue? ";
352   if (GetYN() != 'Y')
353      exit(0);
354   #endif
355} // WinWarning()
356
357// Returns the input string in lower case
358string ToLower(const string& input) {
359   string lower = input; // allocate correct size through copy
360
361   transform(input.begin(), input.end(), lower.begin(), ::tolower);
362   return lower;
363} // ToLower()
364