Sfdisk: Scripting Partition Table Manipulations, Automating Non-Interactive GPT Backups, and Orchestrating Block Geometries in Production
Tracing the stalled process through the deployment logs reveals an infuriating culprit: the outage was not caused by a blown power supply, a failed RAID controller, or a severed fibre-optic line. Instead, deep inside an unattended storage provisioning script, a standard disk partitioning utility has paused silently in the dark. It is waiting indefinitely for a human operator to type "yes" and confirm sector boundaries on standard inputβan interactive prompt sitting in a headless automation pipeline where no human keyboard will ever answer.
To prevent this deadlock and instantly inspect or back up any drive's layout without getting trapped in interactive menus, the single most valuable command in the system administrator's toolkit is a non-destructive layout dump:
# Safely export an exact, reusable partition manifest to standard output or a backup file
sfdisk -d /dev/sda > sda_partition_backup.dump
In a single operation, this command reads the raw partition architecture of /dev/sda and translates it into an unambiguous, version-controllable text stream that can be audited, edited, or piped directly back into another disk.
The tool behind this stream-driven capability is sfdisk, maintained as an integral component of the core util-linux package. Where classic tools like fdisk and cfdisk demand continuous human dialogue, and where parted introduces erratic scripting quirks across different storage controllers, the util-linux sfdisk utility treats disk partitioning schemes not as conversational menus, but as declarative, reproducible configuration manifests.
| Incident Metric | Production Incident Summary |
|---|---|
| Severity | P1 Outage |
| Infrastructure Component | Bare-Metal Provisioning Pipeline |
| Root Cause Trigger | Interactive standard-input block in unattended disk partitioning script |
| Architectural Resolution | Declarative partition manifest automation via sfdisk |
Architectural Foundations: GPT, MBR, and Sector Geometries
To understand why stream-oriented partitioning is essential for modern infrastructure, one must examine how block storage is physically and logically structured. Storage controllers expose non-volatile physical mediaβwhether spinning disks, solid-state SATA drives, or NVMe storage arraysβas contiguous sequences of addressable units known as Logical Block Addressing (LBA).
Historically, the Master Boot Record (MBR) partition format, introduced in 1983, governed disk structures. MBR is confined entirely to the first 512-byte sector of the disk (LBA 0). Within this tiny footprint, MBR allocates exactly 64 bytes for its partition table, limiting disks to four primary partition records of 16 bytes each. Furthermore, because MBR uses 32-bit fields to store sector addresses, it enforces an immutable capacity ceiling:
$$\text{Capacity}_{\text{max}} = 2^{32} \times 512 \text{ bytes} = 2,199,023,255,552 \text{ bytes} \approx 2.199 \text{ TiB}$$
When enterprise drives exceeded this capacity, the computing industry standardized the GUID Partition Table (GPT) as part of the UEFI Specification. GPT introduces 64-bit logical block addressing, pushing theoretical storage limits up to 9.4 zettabytes ($9.4 \times 10^{21}$ bytes).
Architecturally, GPT provides comprehensive structural resilience across the disk: 1. LBA 0 (Protective MBR): Preserves backwards compatibility and prevents legacy disk utilities from misidentifying the disk as unpartitioned and overwriting GPT structures. 2. LBA 1 (Primary GPT Header): Defines the disk GUID, records the locations of partition arrays, and houses a 32-bit Cyclic Redundancy Check (CRC32) checksum of its own header to detect data corruption. 3. LBA 2β33 (Partition Entries): Reserves 128 individual 128-byte partition records. 4. Physical Disk Terminus (Secondary/Backup GPT): Houses an identical mirror of the GPT header and partition arrays across the final 33 sectors of the block device.
Concurrently, modern solid-state electronics and high-capacity magnetic drives shifted from legacy 512-byte physical sectors to 4096-byte (4KiB) sectors, classified under Advanced Format standards as 512e (512-byte emulation) or 4Kn (4KiB Native).
If a partition boundary begins at an arbitrary sector that is not an integer multiple of the physical block size, every write operation spanning a boundary forces the storage controller into a costly Read-Modify-Write (RMW) cycle. To eliminate this bottleneck, modern partitioning standards mandate a default partition alignment offset of 2048 sectors (exactly $2048 \times 512 \text{ bytes} = 1,048,576 \text{ bytes} = 1 \text{ MiB}$), aligning cleanly with 4KiB, 8KiB, 64KiB, and multi-megabyte SSD flash erase block boundaries.
Core Flags and Rapid Execution
The operational power of sfdisk lies in its declarative flag syntax. Rather than stepping through menus, an administrator can execute precise operations using standard Unix streams:
| Flag | Long Option | Operational Function |
|---|---|---|
-d |
--dump |
Emits an exact, parseable configuration manifest of the device partition layout to standard output |
-J |
--json |
Serializes partition tables and geometries into structured JSON for ingestion by jq or Python |
-l |
--list |
Enumerates partition topologies and sector boundaries across specified devices |
-N |
--partno <num> |
Restricts modifications exclusively to the specified partition index, leaving adjacent partitions untouched |
--no-act, --test |
Runs all geometric calculations and validations in dry-run mode without modifying the disk | |
--no-reread |
Suppresses the kernel partition re-read request (BLKRRPART ioctl), critical when altering active disks |
|
--part-type <dev> <num> <type> |
Modifies a partition type code or GPT Type GUID without changing sector boundaries | |
--part-attrs <dev> <num> <attrs> |
Applies GPT attribute bitmasks (e.g. NoAutomount, ReadOnly, RequiredPartition) |
Initial Baseline Inspection
To safely inspect the partition topology of an NVMe storage device without altering disk metadata, execute sfdisk in listing mode:
sfdisk -l /dev/nvme0n1
Disk /dev/nvme0n1: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
Disk model: Samsung SSD 980 PRO 1TB
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 7E4F8B32-9A1D-4B6E-A1C8-E9D3C2B1A0F5
Device Start End Sectors Size Type
/dev/nvme0n1p1 2048 2099199 2097152 1G EFI System
/dev/nvme0n1p2 2099200 69208063 67108864 32G Linux swap
/dev/nvme0n1p3 69208064 1953523711 1884315648 898.5G Linux filesystem
This output confirms that the device follows GPT standards, starts the EFI System Partition at sector 2048 (preserving 1 MiB alignment), and establishes clean, contiguous boundaries across all three partitions.
Five Production-Grade Use Cases
1. Automated Non-Interactive Partition Backup and Cross-Device Cloning
Scenario
A systems engineer must clone an identical storage architecture across newly provisioned bare-metal nodes. Replicating raw disk images with dd is unsuitable because target drives have minor sector count variations, and block-level cloning duplicates conflicting GPT Disk Identifiers and Partition UUIDs across the network fabric.
Execution
Extract the reference layout from the master storage controller (/dev/nvme0n1), strip device-specific unique identifiers using standard stream editors, and pipe the sanitized declarative manifest directly into the target device (/dev/nvme1n1).
sfdisk -d /dev/nvme0n1 | \
sed -e '/^device-uuid:/d' -e '/^uuid:/d' | \
sfdisk /dev/nvme1n1
Simulated Terminal Output
Checking that no-one is using this disk right now ... OK
Disk /dev/nvme1n1: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
Disklabel type: gpt
Old situation:
Disklabel type: gpt
Disk identifier: 00000000-0000-0000-0000-000000000000
>>> Script header accepted.
>>> Script header accepted.
>>> Script header accepted.
>>> Script header accepted.
>>> Created a new GPT disklabel (GUID: D89F2A14-3C7E-4F8B-9A01-BCDE89A23456).
/dev/nvme1n1p1: Created a new partition 1 of type 'EFI System' and of size 1 GiB.
/dev/nvme1n1p2: Created a new partition 2 of type 'Linux swap' and of size 32 GiB.
/dev/nvme1n1p3: Created a new partition 3 of type 'Linux filesystem' and of size 898.5 GiB.
/dev/nvme1n1p4: Done.
New situation:
Disklabel type: gpt
Disk identifier: D89F2A14-3C7E-4F8B-9A01-BCDE89A23456
Device Start End Sectors Size Type
/dev/nvme1n1p1 2048 2099199 2097152 1G EFI System
/dev/nvme1n1p2 2099200 69208063 67108864 32G Linux swap
/dev/nvme1n1p3 69208064 1953523711 1884315648 898.5G Linux filesystem
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
Deep Dive Analysis
- Lines 1β5:
sfdiskperforms a safety check using theO_EXCLfile flag to ensure no active subsystem maintains an exclusive lock on/dev/nvme1n1. - Lines 6β10: The utility parses the input stream. By stripping
device-uuidanduuidfields withsed,sfdiskautomatically generates fresh, cryptographically random UUIDs (D89F2A14-3C7E-4F8B-9A01-BCDE89A23456), preventing collisions insystemdandudevdevice mappings. - Lines 11β14: Partitions are created sequentially, replicating the exact sector alignment of the reference disk.
- Lines 26β28:
sfdiskexecutes theBLKRRPARTioctl syscall, forcing the Linux kernel to drop its cached partition structures and reload the new geometry from the physical disk.
Subsequent Operational Directives
Format the newly created partitions with independent filesystems and volume labels:
mkfs.vfat -F32 /dev/nvme1n1p1
mkswap /dev/nvme1n1p2
mkfs.ext4 -F -L "NODE_ROOT" /dev/nvme1n1p3
2. Headless Bare-Metal and Cloud-Init Storage Provisioning
Scenario
An automated operating system installer executing in a continuous deployment pipeline must partition an uninitialized block storage volume (/dev/sda). The layout requires a 1 GiB UEFI system partition, an 8 GiB swap partition, and the remainder of the drive allocated to a Linux root volumeβall created headlessly without human prompts.
Execution
Supply an inline declarative manifest via a Bash heredoc directly into sfdisk, specifying configurations with human-readable size units.
sfdisk --label gpt /dev/sda << 'EOF'
label: gpt
unit: sectors
first-lba: 2048
start=2048, size=1GiB, type=U, name="ESP"
size=8GiB, type=S, name="SWAP"
size=+, type=L, name="ROOT"
EOF
Simulated Terminal Output
Checking that no-one is using this disk right now ... OK
Disk /dev/sda: 465.76 GiB, 500107862016 bytes, 976773168 sectors
Diskmodel: Crucial CT500MX5
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
>>> Script header accepted.
>>> Script header accepted.
>>> Script header accepted.
>>> Created a new GPT disklabel (GUID: A1B2C3D4-E5F6-7890-ABCD-EF1234567890).
/dev/sda1: Created a new partition 1 of type 'EFI System' and of size 1 GiB.
/dev/sda2: Created a new partition 2 of type 'Linux swap' and of size 8 GiB.
/dev/sda3: Created a new partition 3 of type 'Linux root (x86-64)' and of size 456.8 GiB.
/dev/sda4: Done.
New situation:
Disklabel type: gpt
Disk identifier: A1B2C3D4-E5F6-7890-ABCD-EF1234567890
Device Start End Sectors Size Type
/dev/sda1 2048 2099199 2097152 1G EFI System
/dev/sda2 2099200 18876415 16777216 8G Linux swap
/dev/sda3 18876416 976771071 957894656 456.8G Linux root (x86-64)
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
Deep Dive Analysis
- Heredoc Directive
label: gpt: Establishes a modern GPT structure, overwriting any legacy partition remnants. first-lba: 2048: Sets the baseline offset to ensure 1 MiB boundary alignment.- Short Type Aliases:
sfdiskresolves shorthand aliases into full GPT Type GUIDs: type=Uresolves to the official UEFI ESP GUID (C12A7328-F81F-11D2-BA4B-00A0C93EC93B).type=Sresolves to the Linux Swap GUID (0657FD6D-A4AB-43C4-84E5-0933C84B4F4F).type=Lon x86_64 resolves to the Discoverable Partitions Specification root GUID (4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709).size=+: Instructssfdiskto consume all remaining addressable sectors up to the backup GPT header at the disk terminus.
Subsequent Operational Directives
Confirm that the kernel udev daemon has instantiated the corresponding device nodes:
udevadm settle
lsblk -o NAME,PARTLABEL,PARTUUID,SIZE /dev/sda
3. Live Cloud Block Volume Partition Expansion
Scenario
A cloud block storage volume hosting an active PostgreSQL database on /dev/vdb1 has been expanded from 100 GiB to 500 GiB at the hypervisor level. The partition /dev/vdb1 must be grown to occupy the new capacity while the database remains online and the filesystem stays mounted.
Execution
Perform an in-place partition expansion targeting only partition index 1 using --no-reread to avoid errors on busy filesystems. Then update the kernel's partition record with partx and expand the filesystem online.
# Step 1: Instruct sfdisk to expand partition index 1 across available capacity
sfdisk --no-reread -N 1 /dev/vdb <<< ",+"
# Step 2: Notify the kernel block layer of the altered partition boundary
partx -u /dev/vdb1
# Step 3: Expand the active ext4 filesystem online
resize2fs /dev/vdb1
Simulated Terminal Output
Disk /dev/vdb: 500 GiB, 536870912000 bytes, 1048576000 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Old situation:
Device Start End Sectors Size Type
/dev/vdb1 2048 209715166 209713119 100G Linux filesystem
>>> GPT PMBR size mismatch (209715199 != 1048575999) will be corrected by write.
>>> Backup GPT header standard position shifted to the physical end of the disk.
/dev/vdb1: Partition 1 resized from 100 GiB to 500 GiB.
New situation:
Device Start End Sectors Size Type
/dev/vdb1 2048 1048573951 1048571904 500G Linux filesystem
The partition table has been altered.
Syncing disks.
Deep Dive Analysis
- Syntax
<<< ",+": The leading comma preserves the existing start sector (2048), while+tellssfdiskto extend the partition to the end of the available physical space. - GPT Correction Mechanism:
sfdiskrecognizes that the underlying volume size has changed. It automatically relocates the Secondary/Backup GPT Header from sector209715199to the new physical terminal sector1048575999. --no-rereadSafeguard: Because/dev/vdb1is actively mounted, a standardioctl(BLKRRPART)call would fail withEBUSY. Suppressing this check allows disk structures to update safely without disrupting live database queries.
Subsequent Operational Directives
Verify the expanded filesystem capacity (for XFS filesystems, substitute xfs_growfs /mountpoint):
resize2fs /dev/vdb1
df -hT /dev/vdb1
4. Granular Partition Type GUID and Attribute Flag Modification
Scenario
A security compliance mandate requires that a logging partition (/dev/nvme0n1p2) be flagged with UEFI GPT attribute bitmasks to prevent system automount daemons (systemd-gpt-auto-generator) from mounting it without authorization, while converting its Type GUID from a standard Linux filesystem to an enterprise LVM partition.
| Byte Offset | Structure Field | Purpose & Details |
|---|---|---|
0x00 - 0x0F |
Partition Type GUID | 16-byte unique identifier defining partition role |
0x10 - 0x1F |
Unique Partition GUID | 16-byte unique identifier per partition instance |
0x20 - 0x27 |
Starting LBA | 64-bit unsigned integer (starting sector) |
0x28 - 0x2F |
Ending LBA | 64-bit unsigned integer (ending sector) |
0x30 - 0x37 |
Attribute Flags Bitmask | 64-bit bitmask (Bit 0: System, Bit 60: Read-Only, Bit 62: Hidden, Bit 63: NoAutomount) |
0x38 - 0x7F |
Partition Name | 72 bytes (36 UTF-16LE characters) |
Execution
Apply the type and attribute changes directly to the GPT array entries using sfdisk:
# Apply LVM Type GUID to Partition 2
sfdisk --part-type /dev/nvme0n1 2 E6D6D379-F507-44C2-A23C-238F2A3DF928
# Apply "NoAutomount" and "ReadOnly" Attribute Bitmasks to Partition 2
sfdisk --part-attrs /dev/nvme0n1 2 NoAutomount,ReadOnly
Simulated Terminal Output
/dev/nvme0n1: Partition 2 type changed from 'Linux filesystem' to 'Linux LVM'.
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
/dev/nvme0n1: Partition 2 attributes changed from '0x0000000000000000' to '0x9000000000000000'.
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
Deep Dive Analysis
- Type GUID Modification: Updates the 16-byte partition type identifier in both primary and backup GPT arrays to
E6D6D379-F507-44C2-A23C-238F2A3DF928(the standard Linux LVM GUID). - Attribute Bitmask Decoding: The attribute string
NoAutomount,ReadOnlysets: - Bit 60 (
0x1000000000000000): Read-only partition marker. - Bit 63 (
0x8000000000000000): Suppress automatic discovery/mounting. - Bitwise calculation: $$0x1000000000000000 \lor 0x8000000000000000 = 0x9000000000000000$$
Subsequent Operational Directives
Inspect the updated binary attributes using the JSON output interface piped into jq:
sfdisk -J /dev/nvme0n1 | jq '.partitiontable.partitions[] | select(.node=="/dev/nvme0n1p2")'
5. In-Place Non-Destructive MBR-to-GPT Conversion
Scenario
A server originally deployed under legacy BIOS with an MBR partition layout on /dev/sdb needs to transition to UEFI boot standards. The partition layout and existing filesystems must be converted in-place to GPT without data loss.
Execution
Back up the MBR partition table, extract its layout, rewrite the label as GPT, and generate the secondary backup GPT array at the end of the disk.
# Step 1: Create an atomic local disaster-recovery backup
sfdisk -d /dev/sdb > /var/backups/sdb_mbr_legacy.dump
# Step 2: Transform the partition table format in-place to GPT
sfdisk -d /dev/sdb | \
sed 's/label: dos/label: gpt/' | \
sfdisk --label gpt /dev/sdb
Simulated Terminal Output
Checking that no-one is using this disk right now ... OK
Disk /dev/sdb: 1.82 TiB, 2000398934016 bytes, 3907029168 sectors
Disk model: ST2000DM008-2FR1
Units: sectors of 1 * 512 = 512 bytes
Old situation:
Disklabel type: dos
Disk identifier: 0x4a2e1b8c
Device Boot Start End Sectors Size Id Type
/dev/sdb1 * 2048 1050623 1048576 512M 83 Linux
/dev/sdb2 1050624 3907028991 3905978368 1.8T 83 Linux
>>> Script header accepted.
>>> Script header accepted.
>>> Created a new GPT disklabel (GUID: C4B3A2D1-E8F7-4A5B-9C0D-123456789ABC).
/dev/sdb1: Created a new partition 1 of type 'Linux filesystem' and of size 512 MiB.
/dev/sdb2: Created a new partition 2 of type 'Linux filesystem' and of size 1.8 TiB.
/dev/sdb3: Done.
New situation:
Disklabel type: gpt
Disk identifier: C4B3A2D1-E8F7-4A5B-9C0D-123456789ABC
Device Start End Sectors Size Type
/dev/sdb1 2048 1050623 1048576 512M Linux filesystem
/dev/sdb2 1050624 3907028991 3905978368 1.8T Linux filesystem
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
Deep Dive Analysis
- Preservation of Starting Sectors: The starting sectors of partition 1 (
2048) and partition 2 (1050624) remain identical, ensuring filesystem superblocks (ext4, XFS, or Btrfs) stay perfectly aligned with their data. - Conversion to Protective MBR:
sfdiskreplaces legacy DOS bootloader code in LBA 0 with a Protective MBR, builds the primary GPT array across LBAs 1β33, and allocates the backup GPT array across physical sectors3907029135β3907029167. - Zero Data Loss: Because the operation only modifies metadata structures at the disk perimeters and preserves internal sector allocations, filesystem contents remain completely intact.
Subsequent Operational Directives
Validate the structural consistency of the new GPT layout using the built-in verification flag:
sfdisk --verify /dev/sdb
Operational Safeguards and Failure Modes
Modifying partition tables carries inherent operational risk. An unvalidated command can corrupt sector boundaries and make underlying filesystems inaccessible. Automation workflows should be built around three core safeguards:
| Safeguard Step | Operational Rule | Implementation Technique |
|---|---|---|
| 1 | Dry-Run Validation | Always test manifests using --test or --no-act before writing to disk |
| 2 | Atomic State Backups | Always dump active partition manifests to persistent storage prior to changes |
| 3 | Boundary Verification | Ensure partition offsets align to 2048-sector (1 MiB) multiples |
1. Handling Kernel Re-Read Failures (BLKRRPART: Device or resource busy)
When sfdisk updates a partition layout, it calls the BLKRRPART ioctl to notify the Linux kernel. If any partition on the device is currently mounted, active as swap, or enrolled in an LVM or Device Mapper pool, the kernel rejects the call to protect filesystem integrity.
sfdisk: BLKRRPART ioctl failed: Device or resource busy
The kernel still uses the old table. The new table will be used at the next reboot or after you run partx(8) or kpartx(8).
Remediation:
In continuous deployment scripts, decouple partition table updates from kernel table reloading:
1. Pass the --no-reread flag to sfdisk during the write phase.
2. Update only the modified partition boundary with partx -u /dev/sdXN or blockdev --rereadpt /dev/sdX.
3. Allow the Linux Kernel Block Layer to reconcile internal device mappings without unmounting unaffected filesystems.
2. Sector Misalignment and Performance Degradation
If an automated manifest specifies an arbitrary starting sector (such as start=63, a common legacy DOS artifact), modern 4Kn or 512e drives will suffer severe write degradation due to unaligned physical block operations.
Remediation: Always verify sector alignment during testing. Ensure the starting sector is evenly divisible by 2048 (for standard 1 MiB alignment):
$$\text{Alignment Check} = \text{Start Sector} \pmod{2048} == 0$$
Use sfdisk in test mode to identify alignment warnings prior to committing changes:
sfdisk --test /dev/sda < custom_layout.manifest
3. Integrating Atomic Rollback Safeguards in Configuration Management
Automated tools such as Ansible, SaltStack, or Kickstart should never modify partition tables without an automated rollback safeguard. Build automation scripts that snapshot the active layout before executing changes:
# Define atomic backup routine before applying updates
BACKUP_MANIFEST="/var/log/partition_backups/$(date +%s)_${DEVICE_NAME}.dump"
mkdir -p /var/log/partition_backups
sfdisk -d /dev/${DEVICE_NAME} > ${BACKUP_MANIFEST}
# Apply declarative layout with fail-safe rollback
if ! sfdisk /dev/${DEVICE_NAME} < new_layout.manifest; then
echo "Partitioning failed! Restoring original geometry..."
sfdisk /dev/${DEVICE_NAME} < ${BACKUP_MANIFEST}
exit 1
fi
This pattern guarantees that any failed partitioning run can be restored to its exact previous state in seconds.
Today's Takeaway
Open a terminal on your workstation or staging server and execute sfdisk -d /dev/sda > $(hostname)_storage_manifest.dump (substituting your primary drive name). In less than five seconds, this produces an exact, lightweight, version-controllable text manifest of your entire storage layout. Store this file in an off-site Git repository or configuration management vault; if your partition table is ever damaged by an errant installer, failed firmware update, or human error, piping this small text file back into sfdisk will reconstruct your storage geometry down to the exact sector, turning what could be a multi-hour recovery into a five-second repair.