Why "which disk is this path on?" comes first
/ can be fine while /data is full, and a hard link can work in one directory and fail in the next. Every disk question starts with which filesystem a path actually lives on - so first, how one directory tree is built out of several disks.
What you need to know already: 1.20 (lsblk, vda, LVM in one sentence), 4.1 (/proc/mounts), 4.13 (inodes).
The words, once
- Block device - storage the kernel reads in fixed-size blocks: a disk (
vda,vdb: "v" for a virtual disk in a VM, then a letter per disk). - Partition - a disk cut into independent slices:
vda1,vda2,vda3. - Filesystem - the structure written onto a partition (by
mkfs) that turns raw blocks into files, directories and inodes. ext4 is Ubuntu's default type; each filesystem has its own blocks and its own inodes. - Mount - attaching a filesystem at a directory, the mount point. After
mount /dev/vdb1 /data, everything under/datalives on vdb1.umountdetaches it. - LVM (Logical Volume Manager) - a layer between partitions and filesystems that lets you resize storage later. A partition becomes a PV (physical volume); PVs are pooled into a VG (volume group, here
ubuntu-vg); from the pool you carve LVs (logical volumes, hereubuntu-lv), and a filesystem goes on the LV. The kernel names the LV/dev/mapper/ubuntu--vg-ubuntu--lv(VG and LV names joined by-, with their own dashes doubled).
One tree, many filesystems
There is one directory tree, but it is stitched together from several filesystems, each mounted on a directory. Which one a path lives on decides how much space it has, how many inodes, and whether a hard link across it is possible.
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vda 253:0 0 40G 0 disk
├─vda1 253:1 0 1G 0 part /boot/efi
├─vda2 253:2 0 2G 0 part /boot
└─vda3 253:3 0 36.9G 0 part
└─ubuntu--vg-ubuntu--lv 252:0 0 18G 0 lvm /
vdb 253:16 0 50G 0 disk
└─vdb1 253:17 0 50G 0 part /data
vdc 253:32 0 2G 0 disk
└─vdc1 253:33 0 2G 0 part /srv/cache
lsblk ("list block devices") columns: MAJ:MIN driver and instance numbers, RM removable (1 = yes), SIZE, RO read-only, TYPE disk / part(ition) / lvm, and where it is mounted.
Read it as a tree: disk vda has three partitions; the third is an LVM PV holding the 18G LV that is /. Two more disks are mounted at /data and /srv/cache. The root LV is 18G on a 36.9G partition - Ubuntu's installer leaves the rest of the VG free so you can grow it later (lvextend, 1.20).
findmnt shows what is mounted where, with the options:
$ findmnt /data
TARGET SOURCE FSTYPE OPTIONS
/data /dev/vdb1 ext4 rw,relatime
TARGET the mount point, SOURCE the device, FSTYPE the filesystem type, OPTIONS how it is mounted (rw read-write; relatime a timestamp optimisation). findmnt -T <path> answers "which filesystem is this path on" - the first question when a write fails.
The permanent list is /etc/fstab ("filesystem table"): one line per mount that comes back at boot. A mount made by hand with mount disappears on reboot.
df, column by column
df -h lists every mounted filesystem (-h human units):
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 593M 1.4M 592M 1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv 19G 7.5G 9.7G 44% /
tmpfs 2.9G 0 2.9G 0% /dev/shm
/dev/vda2 2.0G 186M 1.7G 11% /boot
/dev/vdb1 50G 42G 6.2G 87% /data
/dev/vdc1 2.0G 754M 1.2G 39% /srv/cache
- Size - the filesystem's usable size (after its own bookkeeping).
- Used - blocks allocated to files, including files that no longer have a name but are still open (4.13).
- Avail - what an ordinary user can still write. It is not Size minus Used: ext4's root-only reserve (5% by default, 4.21) is subtracted too. Add Used and Avail and you get less than Size.
- Use% - Used / (Used + Avail), rounded up. So a volume shows 100% the moment ordinary users are out of space, even though root can still write.
tmpfs lines are RAM, not disk. /dev/shm and /run live in memory and what you write there counts as memory used.
The other resource: inodes
$ df -i /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv 1179648 118789 1060859 11% /
df -i shows inodes instead of bytes: total, used, free, percent used. One inode per file, directory or symlink, fixed when the filesystem was created. ext4's default is roughly one inode per 16 KiB of space; the small 2G /srv/cache volume has only 12288. When IFree hits 0 you get the same No space left on device as a full disk.
tune2fs: the filesystem's own numbers
tune2fs reads and changes settings of an ext4 filesystem; -l lists them:
$ sudo tune2fs -l /dev/vdc1
tune2fs 1.47.2 (1-Jan-2025)
Filesystem volume name: <none>
Last mounted on: /srv/cache
...
Inode count: 12288
Block count: 524288
Reserved block count: 26214
Free blocks: 331413
Free inodes: 12274
Block size: 4096
Reserved blocks uid: 0 (user root)
The numbers df turns into columns: Block count x Block size is the size, Reserved block count is the root-only reserve (26214 of 524288 = 5%), Inode count is the hard inode limit. tune2fs -m 1 changes the reserve on a live filesystem; nothing changes the inode count short of re-creating it.
du walks names; df asks the filesystem
$ du -sh /srv/cache
12K /srv/cache
du (next lesson) adds up what it can see, by name. df asks the filesystem what is allocated. Ordinary differences are small (bookkeeping). A big difference means one of: files hidden under a mount point, files you could not read, or - the famous one - files deleted while still open. Keep this picture; the next two lessons use it.
Mounting over a non-empty directory
One more way space hides: if something wrote to /data while the disk was not mounted (a boot where the mount failed), those files are on the root filesystem, underneath the mount point. Once /dev/vdb1 is mounted over /data, they are invisible to du and still eating /. The check is a bind mount - making a directory appear at a second place - of / somewhere, then looking: sudo mount --bind / /mnt && sudo du -sh /mnt/data.
What you can now do
- Read
lsblk: disks, partitions, and the LVM PV/VG/LV chain. - Find which filesystem a path is on with
findmnt -T. - Read every
df -handdf -icolumn, and explain why Used + Avail < Size.