Skip to content

LVM and Disk Partitioning

Linux exposes storage devices as block device files under /dev/. Block devices support random Access by fixed-size blocks ( 512 bytes or 4096 bytes), unlike character devices which are Accessed as a stream of bytes.

Definition. A block device is a storage device that supports reading and writing data in Fixed-size blocks, addressed by a linear block number. The kernel caches block device I/O in the Page cache.

Naming conventions:

Device TypePath PatternExampleNotes
SCSI/SATA/dev/sdX/dev/sdaLetters assigned in detection order
NVMe/dev/nvmeXnY/dev/nvme0n1X = controller, Y = namespace
Virtio (VM)/dev/vdX/dev/vdaParavirtualized disks
MMC/eMMC/dev/mmcblkX/dev/mmcblk0Embedded devices
Loop/dev/loopX/dev/loop0Loopback-mounted files
Device Mapper/dev/dm-X/dev/dm-0LVM, crypt, multipath
MD RAID/dev/mdX/dev/md0Software RAID arrays

Partitions are numbered after the device name:

/dev/sda # entire disk
/dev/sda1 # first partition
/dev/sda15 # 15th partition (GPT allows many)
/dev/nvme0n1p1 # first partition on first namespace of NVMe controller 0
/dev/nvme0n1p2 # second partition

Definition. A partition table is a data structure stored at the beginning of a disk that Describes the layout of partitions — their starting sectors, sizes, types, and status flags.

MBR uses a 512-byte boot sector at LBA 0 containing a 446-byte bootstrap code area, a 64-byte Partition table (four 16-byte entries), and a 2-byte signature (0x55AA).

PropertyMBR
Max disk size2 TiB (32-bit sector count)
Max partitions4 primary, or 3 + 1 extended (with logical)
Sector addressing32-bit LBA
Boot methodLegacy BIOS only
Partition ID1-byte type code

MBR is obsolete. Use it only when you need legacy BIOS boot on hardware that lacks UEFI.

GPT is part of the UEFI specification. It stores partition entries in a linked list structure with a Protective MBR at LBA 0 for backward compatibility.

PropertyGPT
Max disk size8 ZiB (2^64 bytes)
Max partitions128 by default (configurable)
Sector addressing64-bit LBA
Boot methodUEFI (with protective MBR for compat)
Partition ID128-bit GUID type + 128-bit GUID name
RedundancyBackup partition table at end of disk
GPT disk layout:
LBA 0: Protective MBR (512 bytes)
LBA 1: GPT header (92 bytes)
LBA 2-33: Partition entries (128 entries x 128 bytes each)
LBA 34+: First usable sector (partition data starts here)
Last LBA - 33: Backup partition entries
Last LBA - 1: Backup GPT header

Always use GPT unless you have a specific reason not to. The 2 TiB MBR limit is hit with Modern disks, and GPT”s backup table provides redundancy against corruption at the start of the Disk.

Sector SizeCommon OnNotes
512 bytesOlder HDDs, SATA SSDTraditional physical sector size
4096 bytesModern HDDs, many SSDs4K native (4Kn) or 512e (emulated) for compatibility

512e drives present 512-byte logical sectors to the OS but use 4096-byte physical sectors Internally. Misaligned writes on 512e drives cause read-modify-write cycles, degrading performance. Modern partitioning tools handle alignment automatically.

Terminal window
# Check logical and physical sector size
cat /sys/block/sda/queue/logical_block_size
cat /sys/block/sda/queue/physical_block_size
# lsblk shows sector sizes
lsblk -o NAME,LOG-SEC,PHY-SEC /dev/sda
TypeDescription
PrimaryOne of the four entries in the MBR table
ExtendedA primary partition that acts as a container for logical partitions
LogicalCreated inside an extended partition using an EBR chain
GUIDType
C12A7328-F81F-11D2-BA4B-00A0C93EC93BEFI System Partition
0657FD6D-A4AB-43C4-84E5-0933C84B4F4FLinux filesystem
44479540-F297-41B2-9AF7-D131D5F0458ALinux root (x86-64)
933AC7E1-2EB4-4F13-B844-0E14E2AEF915Linux swap
E3C9E316-0B5C-4DB8-817D-F92DF00215AEMicrosoft reserved
EBD0A0A2-B9E5-4433-87C0-68B6B72699C7Windows data
Terminal window
# View partition type GUIDs
sgdisk -i 1 /dev/sda
# List all known partition types
sgdisk -L

Device names are assigned in kernel detection order and are not stable across reboots. A SATA Disk that was /dev/sda today may become /dev/sdb after a hardware change. Never use device names In /etc/fstab for persistent mounts.

Definition. A UUID (Universally Unique Identifier) is a 128-bit number assigned to a filesystem At creation time. It is globally unique and does not change when the disk is moved between systems.

Terminal window
# View UUIDs for all block devices
blkid
# View UUID for a specific device
blkid /dev/sda1
# Use UUID in fstab (preferred)
UUID=abc12345-6789-def0-1234-567890abcdef /mnt/data ext4 defaults 0 2
# Use PARTUUID for partition-level identification (works even without filesystem)
PARTUUID=12345678-1234-1234-1234-123456789abc /mnt/data ext4 defaults 0 2

Prefer PARTUUID over UUID for partition identification. PARTUUID is stored in the partition Table itself (not the filesystem), so it survives filesystem recreation and works on raw partitions. Modern distributions use PARTUUID in their default fstab entries.

fdisk is an interactive MBR/GPT partitioning tool. It is the most commonly used tool for quick Partitioning tasks.

Terminal window
# Start interactive partitioning
fdisk /dev/sdb
# Common fdisk commands inside the interactive prompt:
# n - new partition
# d - delete partition
# p - print partition table
# t - change partition type
# l - list known partition types
# w - write changes to disk
# q - quit without saving
# x - extra functionality (experts only)
# Create a partition non-interactively (scriptable)
echo -e "n\np\n1\n\n+100G\nw" | fdisk /dev/sdb
# List partitions (read-only, no interactive prompt)
fdisk -l /dev/sdb

parted supports both MBR and GPT and is scriptable, making it suitable for automation.

Terminal window
# Start interactive mode
parted /dev/sdb
# Common parted commands:
# mklabel gpt - create new GPT table
# mklabel msdos - create new MBR table
# mkpart primary ext4 1MiB 100GiB - create partition
# print - show partition table
# rm 1 - remove partition 1
# resizepart 1 200GiB - resize partition 1 to 200 GiB
# set 1 boot on - set boot flag
# unit s - switch to sector units
# unit GiB - switch to GiB units
# Scriptable (non-interactive) usage:
parted /dev/sdb --script mklabel gpt
parted /dev/sdb --script mkpart primary ext4 1MiB 100GiB
parted /dev/sdb --script set 1 boot on
# Align to 1 MiB boundaries (default for GPT in modern parted)
parted /dev/sdb --script align-check optimal 1

ordered mode is the default and the correct choice for virtually all workloads. journal mode is Used for databases requiring absolute data integrity guarantees. writeback mode is marginally Faster but can leave stale data in files after a crash (zero-length files can appear to have old Content).

Definition. The Logical Volume Manager (LVM) is a storage management framework that abstracts Physical storage into logical volumes. It provides a layer of indirection between physical disks and Filesystems, enabling flexible resizing, snapshots, and pooling of storage across multiple devices.

Physical Disks / Partitions
|
v
Physical Volumes (PV) <-- pvcreate
|
v
Volume Groups (VG) <-- vgcreate
|
v
Logical Volumes (LV) <-- lvcreate
|
v
Filesystem (ext4, XFS, ...) <-- mkfs

Physical Volume (PV): A partition or whole disk that has been initialized for LVM use. Each PV Contains a header with LVM metadata and is divided into fixed-size Physical Extents (PEs). The Default PE size is 4 MiB.

Volume Group (VG): A pool of storage created from one or more PVs. The VG aggregates all PEs From its member PVs into a single addressable space. Think of a VG as a virtual disk that can span Multiple physical disks.

Logical Volume (LV): A virtual block device carved from a VG. An LV is made up of Logical Extents (LEs) that map to PEs on the underlying PVs. Filesystems are created on LVs, not on raw Partitions.

Physical Extent (PE): The smallest unit of allocation in LVM. Default size is 4 MiB. A PE on a PV maps 1:1 to a Logical Extent (LE) on an LV. When you extend an LV, you allocate additional PEs From the VG.

LVM metadata is stored at the start of each PV (in the first few MiB). It describes:

  • The VG configuration (name, UUID, extent size, attribute flags)
  • The PV layout (which PEs are allocated, which are free)
  • The LV definitions (name, UUID, which PEs belong to each LE)
  • Snapshot relationships

Metadata is stored in circular text format at two locations on each PV for redundancy. If one copy Is corrupted, LVM can recover from the backup copy.

Terminal window
# View raw LVM metadata from a PV
pvdisplay --maps /dev/sdb1 # show PE mappings
vgcfgrestore --list vg_name # list available metadata backups
# Metadata backups are stored here by default:
ls /etc/lvm/archive/ # historical backups (vg_name_*.vg)
ls /etc/lvm/backup/ # latest backup (vg_name.vg)

A typical production layout:

/dev/sdb (1 TiB disk)
/dev/sdb1 (100 GiB partition, type 8e00 "Linux LVM") --> pvcreate --> PV
/dev/sdc (1 TiB disk)
/dev/sdc1 (100 GiB partition, type 8e00 "Linux LVM") --> pvcreate --> PV
VG "vg_data" = PV(sdb1) + PV(sdc1) = 200 GiB total
LV "lv_mysql" = 80 GiB from vg_data
LV "lv_logs" = 40 GiB from vg_data
LV "lv_backup" = 60 GiB from vg_data (with 20 GiB free in VG)

You can use whole disks as PVs (pvcreate /dev/sdb) instead of partitions, but using partitions Provides a layer of protection — if LVM metadata is corrupted, partition boundaries remain visible To non-LVM tools for recovery.

Terminal window
# Initialize a partition or disk as a PV
pvcreate /dev/sdb1
pvcreate /dev/sdc # whole disk (wipes partition table)
# Wipe existing signatures before pvcreate
wipefs -a /dev/sdb1
pvcreate -ff /dev/sdb1 # -ff = force (double confirmation required)
# Display PV information
pvdisplay /dev/sdb1
pvs # concise summary
pvs -o+pv_name,vg_name,pe_count,free_pe # custom columns
# Remove a PV (must be freed from VG first)
pvremove /dev/sdb1
pvremove -ff -y /dev/sdb1 # force, no prompts
# Resize a PV after growing the underlying partition
pvresize /dev/sdb1 # auto-detect new size
pvresize --setphysicalvolumesize 200G /dev/sdb1 # set specific size
Terminal window
# Create a VG from one or more PVs
vgcreate vg_data /dev/sdb1
vgcreate vg_data /dev/sdb1 /dev/sdc1 /dev/sdd1
# Set PE size at creation (4 MiB default, can be 1 MiB to 64 GiB)
vgcreate -s 8M vg_data /dev/sdb1 # 8 MiB PE size
# Add a PV to an existing VG (extend the VG)
vgextend vg_data /dev/sdc1
# Remove a PV from a VG (must move data off it first)
pvmove /dev/sdc1 # migrate all data to other PVs
vgreduce vg_data /dev/sdc1 # then remove the PV
# Display VG information
vgdisplay vg_data
vgs # concise summary
vgs -o+vg_name,vg_size,vg_free,pv_count # custom columns
# Activate/deactivate a VG
vgchange -a y vg_data # activate (default)
vgchange -a n vg_data # deactivate (LVs become unavailable)
# Rename a VG (must be inactive)
vgrename old_name new_name
# Split a VG (move some PVs to a new VG)
vgsplit vg_data vg_backup /dev/sdd1
# Merge two VGs
vgmerge vg_data vg_backup # merge vg_backup into vg_data
Terminal window
# Create an LV
lvcreate -L 50G -n lv_mysql vg_data # 50 GiB LV
lvcreate -l 100%FREE -n lv_logs vg_data # use all free space
lvcreate -l 50%FREE -n lv_temp vg_data # half of free space
lvcreate -L 100G -n lv_web -i 2 -I 64 vg_data # striped across 2 PVs, 64 KiB stripe
# Display LV information
lvdisplay /dev/vg_data/lv_mysql
lvs # concise summary
lvs -o+lv_name,vg_name,lv_size,lv_attr # custom columns
# Change LV name
lvrename vg_data lv_mysql lv_production
# Remove an LV
lvremove /dev/vg_data/lv_temp
lvremove -f /dev/vg_data/lv_temp # force (no confirmation)
# Activate/deactivate an LV
lvchange -a y /dev/vg_data/lv_mysql
lvchange -a n /dev/vg_data/lv_mysql
# Set LV to active on boot
lvchange --activationmode partial /dev/vg_data/lv_mysql # activate even if PVs missing
# Change LV attributes
lvchange -ay -K /dev/vg_data/lv_mysql # ignore monitoring (for broken VG)
TaskPV CommandVG CommandLV Command
Createpvcreatevgcreatelvcreate
Displaypvs``pvdisplayvgs``vgdisplaylvs``lvdisplay
Extend/Growpvresizevgextendlvextend
Reduce/Shrinkpvresizevgreducelvreduce
Removepvremovevgremovelvremove
RenameN/Avgrenamelvrename
Move dataN/ApvmoveN/A
Backup metadataN/AvgcfgbackupN/A
Restore metadataN/AvgcfgrestoreN/A

Extending is safe to do online (while mounted). The general process is: extend the underlying Storage, then extend the LV, then extend the filesystem. Order matters — the filesystem cannot be Larger than the LV.

Terminal window
# Scenario: extend lv_mysql from 50 GiB to 100 GiB
# Step 1: Extend the LV
lvextend -L +50G /dev/vg_data/lv_mysql # add 50 GiB
lvextend -L 100G /dev/vg_data/lv_mysql # set to 100 GiB
lvextend -l +100%FREE /dev/vg_data/lv_mysql # use all free space in VG
# Step 2: Resize the filesystem
# For ext4:
resize2fs /dev/vg_data/lv_mysql # auto-detect and fill LV
resize2fs /dev/vg_data/lv_mysql 100G # specific size
# For XFS:
xfs_growfs /mnt/mysql # specify mount point, not device
# Shortcut: lvextend with --resizefs does both steps
lvextend --resizefs -L +50G /dev/vg_data/lv_mysql # ext4 only
lvextend -r -L +50G /dev/vg_data/lv_mysql # -r = --resizefs

Use superblock version 1.0 for /boot (needed by GRUB) and 1.2 for all other arrays. Version 1.2 Places metadata at the 4 KiB offset, avoiding conflicts with partition tables and making it easy to Use whole disks as array members.

Terminal window
# Create a swap partition (type 8200 in GPT, type 82 in MBR)
# Then format it:
mkswap /dev/sdb1
mkswap -L swap_volume /dev/sdb1 # set label
mkswap -U abc12345 /dev/sdb1 # set UUID
# Enable swap
swapon /dev/sdb1
swapon /dev/sdb1 -p 10 # priority -32767 to 32767 (higher = preferred)
# Enable all swap defined in /etc/fstab
swapon -a
# Disable swap
swapoff /dev/sdb1
swapoff -a # disable all swap
# View swap usage
swapon --show
cat /proc/swaps
free -h

Swap files are often preferred over swap partitions because they are easier to resize and do not Require a dedicated partition.

Terminal window
# Create a 4 GiB swap file
fallocate -l 4G /swapfile
# or:
dd if=/dev/zero of=/swapfile bs=1M count=4096
# Set correct permissions (swap files must be root-only)
chmod 600 /swapfile
# Format as swap
mkswap /swapfile
# Enable
swapon /swapfile
# Persistent entry in /etc/fstab:
# /swapfile none swap sw 0 0
# Verify
swapon --show

Zram is most useful on systems with limited RAM (embedded devices, VMs with small allocations). On Systems with ample RAM, zram adds CPU overhead for compression/decompression with little benefit. Use disk swap (or no swap) on systems with 16+ GiB of RAM.

iostat (from sysstat package) reports CPU and I/O statistics.

Terminal window
# Basic I/O stats (updated every 2 seconds, 5 reports)
iostat 2 5
# Extended device stats
iostat -x 2 5
# Human-readable output
iostat -h 2 5
# Specific device
iostat -x /dev/sda 2 5
# Key columns in -x output:
# %util - percentage of time the device was busy
# await - average I/O time (ms) including queue time
# r_await - average read wait time (ms)
# w_await - average write wait time (ms)
# svctm - average service time (ms)
# aqu-sz - average queue depth
# r/s, w/s - read/write operations per second
# rkB/s, wkB/s - read/write throughput in KiB/s
# Persistent counter stats (since boot)
iostat -x --human

iotop shows real-time I/O usage by process (requires root).

Terminal window
# Interactive I/O monitor
iotop
# Non-interactive (batch mode)
iotop -b -o -n 3
# -b = batch, -o = only show processes doing I/O, -n = 3 iterations
# Show only threads (not processes)
iotop -P
# Show accumulated I/O since iotop started
iotop -a

smartctl (from smartmontools package) reads S.M.A.R.T. (Self-Monitoring, Analysis and Reporting Technology) data from disks.

Terminal window
# View overall health
smartctl -H /dev/sda
# View all S.M.A.R.T. attributes
smartctl -a /dev/sda
smartctl -x /dev/sda # extended (includes logs)
# Run a self-test
smartctl -t short /dev/sda # short test (1-2 minutes)
smartctl -t long /dev/sda # long/extended test (hours)
smartctl -t conveyance /dev/sda # vendor-specific transport test
# View test results
smartctl -l selftest /dev/sda
# Enable SMART (if disabled)
smartctl -s on /dev/sda
# View error log
smartctl -l error /dev/sda
# Automated monitoring
# /etc/smartd.conf:
# /dev/sda -a -m admin@example.com -M exec /usr/local/bin/smart-alert
smartctl -a /dev/sda | grep -E "Reallocated_Sector|Current_Pending|Offline_Uncorrectable"

For NVMe devices, use nvme-cli instead of smartctl for NVMe-specific health data.

Terminal window
# View NVMe device info
nvme id-ctrl /dev/nvme0
nvme id-ns /dev/nvme0n1
# View SMART health
nvme smart-log /dev/nvme0
nvme smart-log /dev/nvme0 | grep -E "critical|temperature|percentage_used"
# View error log
nvme error-log /dev/nvme0
# Get firmware version
nvme get-feature -f 2 /dev/nvme0 # firmware slot
# Format/secure erase (DESTRUCTIVE)
nvme format /dev/nvme0 -s 1 -l 1 # secure erase, block erase
# Flush the namespace
nvme flush /dev/nvme0n1
Terminal window
# Filesystem usage (human-readable)
df -h
df -Th # with filesystem type
df -ih # show inodes instead of blocks
# Show specific filesystem
df -h /mnt/data
# Show only specific type
df -h -t ext4
# Directory sizes
du -sh /var/log # total size
du -h --max-depth=1 /var # one level deep
du -ah /var/log | sort -rh | head # largest files
# ncdu — interactive disk usage analyzer
ncdu /var
ncdu -x / # stay on same filesystem
ncdu -e /var # enable extended info

Definition. LUKS (Linux Unified Key Setup) is a disk encryption standard that provides a Platform-independent on-disk format for encrypted block devices.

FeatureLUKS1LUKS2
Header version12
Key slots8Up to 32
Anti-forensicNoYes (memory-hard key derivation)
MetadataBinary header onlyJSON metadata area
PBKDF2YesYes, plus Argon2i/Argon2id (stronger)
Token supportNoYes (systemd, keyring, etc.)
IntegrityNoOptional (dm-integrity)
Header backupluksHeaderBackupluksHeaderBackup (larger header)
Terminal window
# Check LUKS version
cryptsetup luksDump /dev/sdb1 | grep "Version"
Terminal window
# Format a partition as LUKS2
cryptsetup luksFormat --type luks2 /dev/sdb1
# WARNING: This will overwrite data on /dev/sdb1 irreversibly.
# With specific parameters
cryptsetup luksFormat --type luks2 \
--cipher aes-xts-plain64 \
--key-size 512 \
--hash sha512 \
--iter-time 3000 \
/dev/sdb1
# Open (decrypt) the volume
cryptsetup luksOpen /dev/sdb1 crypt_data
# Creates /dev/mapper/crypt_data
# Create filesystem on the decrypted device
mkfs.ext4 /dev/mapper/crypt_data
# Mount
mount /dev/mapper/crypt_data /mnt/data
# Unmount and close
umount /mnt/data
cryptsetup luksClose crypt_data
Terminal window
# Add a passphrase (key slot 0 is used at creation, this adds to slot 1)
cryptsetup luksAddKey /dev/sdb1
# Remove a passphrase
cryptsetup luksRemoveKey /dev/sdb1
# Change passphrase
cryptsetup luksChangeKey /dev/sdb1
# Add a keyfile
dd if=/dev/urandom of=/root/luks-key bs=4096 count=1
chmod 400 /root/luks-key
cryptsetup luksAddKey /dev/sdb1 /root/luks-key
# Open with a keyfile
cryptsetup luksOpen /dev/sdb1 crypt_data --key-file /root/luks-key
# Backup LUKS header (critical — losing header means losing data)
cryptsetup luksHeaderBackup /dev/sdb1 --header-backup-file /root/sdb1.header
# Restore LUKS header
cryptsetup luksHeaderRestore /dev/sdb1 --header-backup-file /root/sdb1.header

The crypttab file defines encrypted block devices to be unlocked at boot:

# <name> <device> <keyfile> <options>
crypt_root UUID=abc12345-... /crypto_key luks,discard
crypt_data /dev/disk/by-id/ata-ST5000 none luks
crypt_swap /dev/disk/by-uuid/def67890 /dev/urandom swap,cipher=aes-xts-plain64,size=256
FieldDescription
NameMapper name (appears as /dev/mapper/<name>)
DeviceUUID, device path, or /dev/disk/by-id/ identifier
KeyfilePath to key file, none for passphrase prompt, /dev/urandom for swap
OptionsComma-separated: luks``discard``timeout=X``try-empty-password

Two common architectures for combining LVM and LUKS:

LVM on LUKS (recommended for full-disk encryption):
/dev/sda (partitioned)
/dev/sda1 (boot, unencrypted)
/dev/sda2 (LUKS encrypted partition)
crypt_root (decrypted block device)
vg_root (LVM volume group on the decrypted device)
lv_root (filesystem: /)
lv_swap (swap)
lv_home (filesystem: /home)
LUKS on LVM:
/dev/sda (partitioned)
/dev/sda1 (boot)
/dev/sda2 (Linux LVM partition)
vg_root
lv_crypt (LUKS encrypted LV)
crypt_data (decrypted block device, filesystem: /data)
lv_root (unencrypted, filesystem: /)
AspectLVM on LUKSLUKS on LVM
SecurityBetter (entire VG is encrypted)LV-level granularity
FlexibilityCannot have unencrypted LVs on same diskCan mix encrypted and plain LVs
SnapshotsOn encrypted data (transparent)Snapshots of encrypted LVs
Key managementSingle key unlocks entire VGPer-LV keys
Boot complexityHigher (need initramfs with cryptsetup)Lower (root can be unencrypted)
Typical use caseLaptops, full-disk encryptionServers with selective encryption
Terminal window
# List available metadata backups
vgcfgrestore --list vg_data
# Restore from the latest backup
vgcfgrestore -f /etc/lvm/archive/vg_data_00001-xxxxxx.vg vg_data
# Restore from the backup directory
vgcfgrestore --backup vg_data
# Restore from the archive directory (specific backup)
vgcfgrestore -f /etc/lvm/archive/vg_data_00005-1234567.vg vg_data
# If no backups exist, attempt manual recovery
# Scan for LVM physical volumes
pvscan --cache
vgscan
lvscan
# Activate all volume groups
vgchange -ay
# If a PV header is corrupted:
# Attempt to restore the PV header (last resort)
pvcreate --uuid <original-uuid> --restorefile /etc/lvm/archive/<file>.vg \
--force /dev/sdb1
Terminal window
# Verify GPT consistency
sgdisk --verify /dev/sda
# Recover GPT from backup (at end of disk)
sgdisk --load-backup=/root/sda-backup.gpt /dev/sda
# If no backup exists, rebuild GPT from disk scan
sgdisk --recompute-chs /dev/sda
# For MBR: use fdisk to check and fix
fdisk -l /dev/sda
# Use gdisk to convert MBR to GPT (non-destructive if enough space)
gdisk /dev/sda
# Command: w (write, converts MBR protective to GPT)

testdisk is a powerful data recovery tool for recovering lost partitions and files.

Terminal window
# Start testdisk
testdisk
# Common workflow:
# 1. Select the disk
# 2. Choose partition type (Intel/Mac/None)
# 3. Select [Analyse] to scan for lost partitions
# 4. Review found partitions
# 5. Select [Write] to save recovered partition table
# 6. Quit
# Recover specific files from a damaged filesystem
testdisk /dev/sdb
# Navigate to [Advanced] -> [Filesystem Utils] -> [List]
# Select files to copy to another disk

gpart guesses lost partition types by scanning for filesystem signatures.

Terminal window
# Scan a disk for lost partitions
gpart /dev/sda
# Write the guessed partition table
gpart -W /dev/sda_output.txt /dev/sda
Terminal window
# Common scenario: kernel cannot find root filesystem
# Boot into recovery shell or live system, then:
# Check if VG is visible
vgscan
vgchange -ay
# Check if root LV exists
lvs
# Mount root and check fstab
mount /dev/vg_root/lv_root /mnt
cat /mnt/etc/fstab
# If initramfs is missing cryptsetup for LUKS:
chroot /mnt
update-initramfs -u -k all # Debian/Ubuntu
dracut --force # RHEL/Fedora
# If /boot is on a separate partition, verify it is mounted correctly:
ls /mnt/boot/vmlinuz-*
Terminal window
# If a VG fails to activate because a PV is missing:
vgchange -ay --partial vg_data
# List missing PVs
pvs -a -o+missing
# Remove missing PVs from VG (data on missing PV is lost)
vgreduce --removemissing --force vg_data
# If an LV is stuck in inactive state:
lvchange -ay --activationmode partial /dev/vg_data/lv_mysql
lvchange -ay -K /dev/vg_data/lv_mysql # ignore monitoring

When shrinking an LV and filesystem, the filesystem must be shrunk first, then the LV. Shrinking The LV before the filesystem truncates the filesystem and causes corruption.

CORRECT ORDER for shrinking:
1. umount
2. e2fsck -f
3. resize2fs /dev/vg/lv 50G (shrink filesystem first)
4. lvreduce -L 50G /dev/vg/lv (then shrink LV)
WRONG ORDER (will corrupt data):
1. lvreduce -L 50G /dev/vg/lv (LV shrinks, filesystem still thinks it's larger)
2. resize2fs /dev/vg/lv 50G (too late. Filesystem metadata may be beyond LV boundary)