Add end-alignment feature.

This commit is contained in:
Rod Smith
2022-01-29 10:51:02 -05:00
parent 43b3df969c
commit fd60f74362
13 changed files with 141 additions and 74 deletions

10
NEWS
View File

@@ -1,9 +1,17 @@
1.0.9 (?/?/2021):
1.0.9 (?/?/2022):
-----------------
- Removed stray debugging code that caused "partNum is {x}" to be printed
when changing a partition's name with sgdisk (-c/--change-name).
- Added support for aligning partitions' end points, as well as their start
points. This support affects the default partition size when using 'n' in
gdisk; it affects the default partition size in cgdisk; and it's activated
by the new '-I' option in sgdisk. See the programs' respective man pages
for details. This feature is intended to help with LUKS2 encryption, which
reacts badly to partitions that are not sized as exact multiples of the
encryption block size.
1.0.8 (6/9/2021):
-----------------

View File

@@ -186,8 +186,13 @@ new disks, GPT fdisk attempts to align partitions on 1 MiB boundaries
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.
values are detected. In either case, it can be changed by using this option.
The alignment value also affects the default end sector value when creating
a new partition; it will be aligned to one less than a multiple of the
alignment value, when possible. This should keep partitions a multiple of
the alignment value in size. Some disk encryption tools require partitions
to be sized to some value, typically 4096 bytes, so the default alignment of
1 MiB works well for them.
.TP
.B Backup

16
gdisk.8
View File

@@ -210,7 +210,8 @@ default start sector, or \fI\fB\-200M\fR\fR to specify a point 200MiB
before the last available sector. 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 end of the same block for the end
sector.
sector. Default start and end points may be adjusted to optimize partition
alignment.
.TP
.B o
@@ -491,13 +492,18 @@ Change the sector alignment value. Disks with more logical sectors per
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 1 MiB boundaries
(2048\-sectors on disks with 512-byte sectors) by default, which optimizes
new disks, GPT fdisk attempts to align partitions on 1 MiB boundaries (2048
sectors on disks with 512-byte sectors) 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.
values are detected. In either case, it can be changed by using this option.
The alignment value also affects the default end sector value when creating
a new partition; it will be aligned to one less than a multiple of the
alignment value, if possible. This should keep partitions a multiple of the
alignment value in size. Some disk encryption tools require partitions to be
sized to some value, typically 4096 bytes, so the default alignment of 1 MiB
works well for them.
.TP
.B m

34
gpt.cc
View File

@@ -3,7 +3,7 @@
/* By Rod Smith, initial coding January to February, 2009 */
/* This program is copyright (c) 2009-2018 by Roderick W. Smith. It is distributed
/* This program is copyright (c) 2009-2022 by Roderick W. Smith. It is distributed
under the terms of the GNU GPL version 2, as detailed in the COPYING file. */
#define __STDC_LIMIT_MACROS
@@ -410,6 +410,11 @@ int GPTData::Verify(void) {
<< "in degraded performance on some modern (2009 and later) hard disks.\n";
alignProbs++;
} // if
if ((partitions[i].IsUsed()) && ((partitions[i].GetLastLBA() + 1) % testAlignment) != 0) {
cout << "\nCaution: Partition " << i + 1 << " doesn't end on a "
<< testAlignment << "-sector boundary. This may\nresult "
<< "in problems with some disk encryption tools.\n";
} // if
} // for
if (alignProbs > 0)
cout << "\nConsult http://www.ibm.com/developerworks/linux/library/l-4kb-sector-disks/\n"
@@ -2334,18 +2339,28 @@ uint64_t GPTData::FindLastAvailable(void) {
} // GPTData::FindLastAvailable()
// Find the last available block in the free space pointed to by start.
uint64_t GPTData::FindLastInFree(uint64_t start) {
uint64_t nearestStart;
// If align == true, returns the last sector that's aligned on the
// system alignment value (unless that's less than the start value);
// if align == false, returns the last available block regardless of
// alignment. (The align variable is set to false by default.)
uint64_t GPTData::FindLastInFree(uint64_t start, bool align) {
uint64_t nearestEnd, endPlus;
uint32_t i;
nearestStart = mainHeader.lastUsableLBA;
nearestEnd = mainHeader.lastUsableLBA;
for (i = 0; i < numParts; i++) {
if ((nearestStart > partitions[i].GetFirstLBA()) &&
if ((nearestEnd > partitions[i].GetFirstLBA()) &&
(partitions[i].GetFirstLBA() > start)) {
nearestStart = partitions[i].GetFirstLBA() - 1;
nearestEnd = partitions[i].GetFirstLBA() - 1;
} // if
} // for
return (nearestStart);
if (align) {
endPlus = nearestEnd + 1;
if (Align(&endPlus) && IsFree(endPlus - 1) && (endPlus > start)) {
nearestEnd = endPlus - 1;
} // if
} // if
return (nearestEnd);
} // GPTData::FindLastInFree()
// Finds the total number of free blocks, the number of segments in which
@@ -2422,7 +2437,10 @@ int GPTData::IsUsedPartNum(uint32_t partNum) {
***********************************************************/
// Set partition alignment value; partitions will begin on multiples of
// the specified value
// the specified value, and the default end values will be set so that
// partition sizes are multiples of this value in cgdisk and gdisk, too.
// (In sgdisk, end-alignment is done only if the '-I' command-line option
// is used.)
void GPTData::SetAlignment(uint32_t n) {
if (n > 0) {
sectorAlignment = n;

4
gpt.h
View File

@@ -1,7 +1,7 @@
/* gpt.h -- GPT and data structure definitions, types, and
functions */
/* This program is copyright (c) 2009-2011 by Roderick W. Smith. It is distributed
/* This program is copyright (c) 2009-2022 by Roderick W. Smith. It is distributed
under the terms of the GNU GPL version 2, as detailed in the COPYING file. */
#ifndef __GPTSTRUCTS
@@ -185,7 +185,7 @@ public:
uint64_t FindFirstUsedLBA(void);
uint64_t FindFirstInLargest(void);
uint64_t FindLastAvailable();
uint64_t FindLastInFree(uint64_t start);
uint64_t FindLastInFree(uint64_t start, bool align = false);
uint64_t FindFreeBlocks(uint32_t *numSegments, uint64_t *largestSegment);
int IsFree(uint64_t sector, uint32_t *partNum = NULL);
int IsFreePartNum(uint32_t partNum);

View File

@@ -1,7 +1,7 @@
/*
Implementation of GPTData class derivative with popt-based command
line processing
Copyright (C) 2010-2014 Roderick W. Smith
Copyright (C) 2010-2022 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
@@ -30,6 +30,7 @@ GPTDataCL::GPTDataCL(void) {
attributeOperation = backupFile = partName = hybrids = newPartInfo = NULL;
mbrParts = twoParts = outDevice = typeCode = partGUID = diskGUID = NULL;
alignment = DEFAULT_ALIGNMENT;
alignEnd = false;
deletePartNum = infoPartNum = largestPartNum = bsdPartNum = 0;
tableSize = GPT_SIZE;
} // GPTDataCL constructor
@@ -90,6 +91,7 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) {
{"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...][:EE]"},
{"info", 'i', POPT_ARG_INT, &infoPartNum, 'i', "show detailed information on partition", "partnum"},
{"align-end", 'I', POPT_ARG_NONE, NULL, 'I', "align partition end points", ""},
{"move-main-table", 'j', POPT_ARG_INT, &mainTableLBA, 'j', "adjust the location of the main partition table", "sector"},
{"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", ""},
@@ -272,6 +274,9 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) {
case 'i':
ShowPartDetails(infoPartNum - 1);
break;
case 'I':
alignEnd = true;
break;
case 'j':
if (MoveMainTable(mainTableLBA)) {
JustLooking(0);
@@ -307,9 +312,9 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) {
newPartNum = FindFirstFreePart();
low = FindFirstInLargest();
Align(&low);
high = FindLastInFree(low);
startSector = IeeeToInt(GetString(newPartInfo, 2), sSize, low, high, low);
endSector = IeeeToInt(GetString(newPartInfo, 3), sSize, startSector, high, high);
high = FindLastInFree(low, alignEnd);
startSector = IeeeToInt(GetString(newPartInfo, 2), sSize, low, high, sectorAlignment, low);
endSector = IeeeToInt(GetString(newPartInfo, 3), sSize, startSector, high, sectorAlignment, high);
if (CreatePartition(newPartNum, startSector, endSector)) {
saveData = 1;
} else {
@@ -323,7 +328,7 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) {
JustLooking(0);
startSector = FindFirstInLargest();
Align(&startSector);
endSector = FindLastInFree(startSector);
endSector = FindLastInFree(startSector, alignEnd);
if (largestPartNum <= 0)
largestPartNum = FindFirstFreePart() + 1;
if (CreatePartition(largestPartNum - 1, startSector, endSector)) {

View File

@@ -1,7 +1,7 @@
/*
Implementation of GPTData class derivative with popt-based command
line processing
Copyright (C) 2010-2013 Roderick W. Smith
Copyright (C) 2010-2022 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
@@ -34,6 +34,7 @@ class GPTDataCL : public GPTData {
char *newPartInfo, *mbrParts, *twoParts, *outDevice, *typeCode;
char *partGUID, *diskGUID;
int alignment, deletePartNum, infoPartNum, largestPartNum, bsdPartNum;
bool alignEnd;
uint32_t tableSize;
poptContext poptCon;

View File

@@ -1,7 +1,7 @@
/*
* Implementation of GPTData class derivative with curses-based text-mode
* interaction
* Copyright (C) 2011-2018 Roderick W. Smith
* Copyright (C) 2011-2022 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
@@ -430,12 +430,18 @@ void GPTDataCurses::Verify(void) {
// Create a new partition in the space pointed to by currentSpace.
void GPTDataCurses::MakeNewPart(void) {
uint64_t size, newFirstLBA = 0, newLastLBA = 0;
uint64_t size, newFirstLBA = 0, newLastLBA = 0, lastAligned;
int partNum;
char inLine[80];
move(LINES - 4, 0);
clrtobot();
lastAligned = currentSpace->lastLBA + 1;
Align(&lastAligned);
lastAligned--;
// Discard end-alignment attempt if it's giving us an invalid end point....
if (!IsFree(lastAligned))
lastAligned = currentSpace->lastLBA;
while ((newFirstLBA < currentSpace->firstLBA) || (newFirstLBA > currentSpace->lastLBA)) {
move(LINES - 4, 0);
clrtoeol();
@@ -445,10 +451,13 @@ void GPTDataCurses::MakeNewPart(void) {
echo();
getnstr(inLine, 79);
noecho();
newFirstLBA = IeeeToInt(inLine, blockSize, currentSpace->firstLBA, currentSpace->lastLBA, newFirstLBA);
newFirstLBA = IeeeToInt(inLine, blockSize, currentSpace->firstLBA, currentSpace->lastLBA, sectorAlignment, newFirstLBA);
Align(&newFirstLBA);
} // while
if (newFirstLBA > lastAligned)
size = currentSpace->lastLBA - newFirstLBA + 1;
else
size = lastAligned - newFirstLBA + 1;
while ((newLastLBA > currentSpace->lastLBA) || (newLastLBA < newFirstLBA)) {
move(LINES - 3, 0);
clrtoeol();
@@ -456,7 +465,7 @@ void GPTDataCurses::MakeNewPart(void) {
echo();
getnstr(inLine, 79);
noecho();
newLastLBA = newFirstLBA + IeeeToInt(inLine, blockSize, 1, size, size) - 1;
newLastLBA = newFirstLBA + IeeeToInt(inLine, blockSize, 1, size, sectorAlignment, size) - 1;
} // while
partNum = FindFirstFreePart();
if (CreatePartition(partNum, newFirstLBA, newLastLBA)) { // created OK; set type code & name....

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2010-2018 <Roderick W. Smith>
Copyright (C) 2010-2022 <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
@@ -199,7 +199,7 @@ void GPTDataTextUI::MoveMainTable(void) {
// Interactively create a partition
void GPTDataTextUI::CreatePartition(void) {
uint64_t firstBlock, firstInLargest, lastBlock, sector, origSector;
uint64_t firstBlock, firstInLargest, lastBlock, sector, origSector, lastAligned;
uint32_t firstFreePart = 0;
ostringstream prompt1, prompt2, prompt3;
int partNum;
@@ -229,7 +229,7 @@ void GPTDataTextUI::CreatePartition(void) {
prompt2 << "First sector (" << firstBlock << "-" << lastBlock << ", default = "
<< firstInLargest << ") or {+-}size{KMGTP}: ";
do {
sector = GetSectorNum(firstBlock, lastBlock, firstInLargest, blockSize, prompt2.str());
sector = GetSectorNum(firstBlock, lastBlock, firstInLargest, prompt2.str());
} while (IsFree(sector) == 0);
origSector = sector;
if (Align(&sector)) {
@@ -239,15 +239,15 @@ void GPTDataTextUI::CreatePartition(void) {
if (!beQuiet)
cout << "Use 'l' on the experts' menu to adjust alignment\n";
} // if
// Align(&sector); // Align sector to correct multiple
firstBlock = sector;
// Get last block for new partitions...
lastBlock = FindLastInFree(firstBlock);
lastBlock = FindLastInFree(firstBlock, false);
lastAligned = FindLastInFree(firstBlock, true);
prompt3 << "Last sector (" << firstBlock << "-" << lastBlock << ", default = "
<< lastBlock << ") or {+-}size{KMGTP}: ";
<< lastAligned << ") or {+-}size{KMGTP}: ";
do {
sector = GetSectorNum(firstBlock, lastBlock, lastBlock, blockSize, prompt3.str());
sector = GetSectorNum(firstBlock, lastBlock, lastAligned, prompt3.str());
} while (IsFree(sector) == 0);
lastBlock = sector;
@@ -548,6 +548,28 @@ int GPTDataTextUI::XFormToMBR(void) {
return protectiveMBR.DoMenu();
} // GPTDataTextUI::XFormToMBR()
// Obtains a sector number, between low and high, from the
// user, accepting values prefixed by "+" to add sectors to low,
// or the same with "K", "M", "G", "T", or "P" as suffixes to add
// kibibytes, mebibytes, gibibytes, tebibytes, or pebibytes,
// respectively. If a "-" prefix is used, use the high value minus
// the user-specified number of sectors (or KiB, MiB, etc.). Use the
// def value as the default if the user just hits Enter.
uint64_t GPTDataTextUI::GetSectorNum(uint64_t low, uint64_t high, uint64_t def,
const string & prompt) {
uint64_t response;
char line[255];
do {
cout << prompt;
cin.getline(line, 255);
if (!cin.good())
exit(5);
response = IeeeToInt(line, blockSize, low, high, sectorAlignment, def);
} while ((response < low) || (response > high));
return response;
} // GPTDataTextUI::GetSectorNum()
/******************************************************
* *
* Display informational messages for the user.... *

View File

@@ -1,6 +1,6 @@
/*
Implementation of GPTData class derivative with basic text-mode interaction
Copyright (C) 2010-2018 Roderick W. Smith
Copyright (C) 2010-2022 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
@@ -55,6 +55,7 @@ class GPTDataTextUI : public GPTData {
void ShowDetails(void);
void MakeHybrid(void);
int XFormToMBR(void); // convert GPT to MBR, wiping GPT afterwards. Returns 1 if successful
uint64_t GetSectorNum(uint64_t low, uint64_t high, uint64_t def, const string & prompt);
// An informational function....
void WarnAboutIffyMBRPart(int partNum);

View File

@@ -154,7 +154,10 @@ to sectors that are multiples of this value, which defaults to 1 MiB (2048
on disks with 512-byte sectors) on freshly formatted disks. This alignment
value is necessary to obtain optimum performance with Western Digital
Advanced Format and similar drives with larger physical than logical sector
sizes, with some types of RAID arrays, and with SSD devices.
sizes, with some types of RAID arrays, and with SSD devices. When the
\fI\-I\fR option is used, this same alignment value is used to determine
partition end points; but partitions end at one less than a multiple of this
value, to keep the partition length a multiple of this value.
.TP
.B \-A, \-\-attributes=list|[partnum:show|or|nand|xor|=|set|clear|toggle|get[:bitnum|hexbitmask]]
@@ -288,6 +291,18 @@ unique GUID and the translation of \fBsgdisk\fR's
internal partition type code to a plain type name. The \fI\-i\fR option
displays this information for a single partition.
.TP
.B \-I, \-\-align\-end
When possible, align the end points of partitions to one less than a
multiple of the alignment value. When both start and end points are aligned,
partitions should be multiples of the alignment value in size, which is
necessary for some partition encryption tools to function correctly. This
option applies to all partitions created \fBafter\fR this option on the
command line. Note that this alignment is not always possible; for instance,
if the free space at the end of a disk is less than the alignment value,
with the current final partition being aligned, and if \fBsgdisk\fR is asked
to create a partition in that space, then it will \fBnot\fR be end\-aligned.
.TP
.B \-j, \-\-adjust\-main\-table=sector
Adjust the location of the main partition table. This value is normally 2,

View File

@@ -3,7 +3,7 @@
// Primarily by Rod Smith, February 2009, but with a few functions
// copied from other sources (see attributions below).
/* This program is copyright (c) 2009-2018 by Roderick W. Smith. It is distributed
/* This program is copyright (c) 2009-2022 by Roderick W. Smith. It is distributed
under the terms of the GNU GPL version 2, as detailed in the COPYING file. */
#define __STDC_LIMIT_MACROS
@@ -113,34 +113,11 @@ char GetYN(void) {
return response;
} // GetYN(void)
// Obtains a sector number, between low and high, from the
// user, accepting values prefixed by "+" to add sectors to low,
// or the same with "K", "M", "G", "T", or "P" as suffixes to add
// kilobytes, megabytes, gigabytes, terabytes, or petabytes,
// respectively. If a "-" prefix is used, use the high value minus
// the user-specified number of sectors (or KiB, MiB, etc.). Use the
// def value as the default if the user just hits Enter. The sSize is
// the sector size of the device.
uint64_t GetSectorNum(uint64_t low, uint64_t high, uint64_t def, uint64_t sSize,
const string & prompt) {
uint64_t response;
char line[255];
do {
cout << prompt;
cin.getline(line, 255);
if (!cin.good())
exit(5);
response = IeeeToInt(line, sSize, low, high, def);
} while ((response < low) || (response > high));
return response;
} // GetSectorNum()
// Convert an IEEE-1541-2002 value (K, M, G, T, P, or E) to its equivalent in
// number of sectors. If no units are appended, interprets as the number
// of sectors; otherwise, interprets as number of specified units and
// converts to sectors. For instance, with 512-byte sectors, "1K" converts
// to 2. If value includes a "+", adds low and subtracts 1; if SIValue
// to 2. If value includes a "+", adds low and subtracts 1; if inValue
// inclues a "-", subtracts from high. If IeeeValue is empty, returns def.
// Returns final sector value. In case inValue is invalid, returns 0 (a
// sector value that's always in use on GPT and therefore invalid); and if
@@ -153,7 +130,7 @@ uint64_t GetSectorNum(uint64_t low, uint64_t high, uint64_t def, uint64_t sSize,
// 0 values. The result is that IeeeToInt() returns UINT64_MAX when
// compiled with GCC (and so the value is rejected), whereas when VC++
// is used, the default value is returned.
uint64_t IeeeToInt(string inValue, uint64_t sSize, uint64_t low, uint64_t high, uint64_t def) {
uint64_t IeeeToInt(string inValue, uint64_t sSize, uint64_t low, uint64_t high, uint32_t sectorAlignment, uint64_t def) {
uint64_t response = def, bytesPerUnit, mult = 1, divide = 1;
size_t foundAt = 0;
char suffix = ' ', plusFlag = ' ';
@@ -208,11 +185,12 @@ uint64_t IeeeToInt(string inValue, uint64_t sSize, uint64_t low, uint64_t high,
} // if/elseif
if (plusFlag == '+') {
// Recompute response based on low part of range (if default == high
// value, which should be the case when prompting for the end of a
// range) or the defaut value (if default != high, which should be
// the case for the first sector of a partition).
if (def == high) {
// Recompute response based on low part of range (if default is within
// sectorAlignment sectors of high, which should be the case when
// prompting for the end of a range) or the defaut value (if default is
// further away from the high value, which should be the case for the
// first sector of a partition).
if ((high - def) < sectorAlignment) {
if (response > 0)
response--;
if (response > (UINT64_MAX - low))

View File

@@ -1,4 +1,4 @@
/* This program is copyright (c) 2009-2018 by Roderick W. Smith. It is distributed
/* This program is copyright (c) 2009-2022 by Roderick W. Smith. It is distributed
under the terms of the GNU GPL version 2, as detailed in the COPYING file. */
#ifndef __GPTSUPPORT
@@ -8,7 +8,7 @@
#include <stdlib.h>
#include <string>
#define GPTFDISK_VERSION "1.0.8"
#define GPTFDISK_VERSION "1.0.8.2"
#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__) || defined (__APPLE__)
// Darwin (Mac OS) & FreeBSD: disk IOCTLs are different, and there is no lseek64
@@ -74,8 +74,7 @@ using namespace std;
string ReadString(void);
uint64_t GetNumber(uint64_t low, uint64_t high, uint64_t def, const string & prompt);
char GetYN(void);
uint64_t GetSectorNum(uint64_t low, uint64_t high, uint64_t def, uint64_t sSize, const std::string& prompt);
uint64_t IeeeToInt(string IeeeValue, uint64_t sSize, uint64_t low, uint64_t high, uint64_t def = 0);
uint64_t IeeeToInt(string IeeeValue, uint64_t sSize, uint64_t low, uint64_t high, uint32_t sectorAlignment, uint64_t def = 0);
string BytesToIeee(uint64_t size, uint32_t sectorSize);
unsigned char StrToHex(const string & input, unsigned int position);
int IsHex(string input); // Returns 1 if input can be hexadecimal number....