Files, permissions and I/O

Inodes, hard and symbolic links, the permission bits that matter, and how buffered versus direct I/O changes durability and speed.

Files, inodes and links

On Unix a filename is an entry in a directory that points to an inode, which holds the metadata and the block map. The name and the file are separate things — which explains links, hard-link counts and why deleting an open file frees nothing yet.

ls -li notes.txt         # -i prints the inode number
stat notes.txt           # size, blocks, link count, access/modify/change times

ln notes.txt copy.txt    # hard link: same inode, link count becomes 2
ln -s notes.txt link.txt # symbolic link: a small file containing a path

rm notes.txt             # the inode survives while link count > 0
lsof +L1                 # files with link count 0 still open somewhere
Hard linkSymbolic link
TargetSame inodeAnother path
Across filesystemsNoYes
To a directoryNo (normally)Yes
Broken if target is deletedNoYes
💡
Deleting a file unlinks a name; space is only reclaimed when the last hard link and every open file descriptor are gone. A full disk while df looks fine usually means a deleted but still-open log file.

Permissions that matter

BitFile meansDirectory means
r (4)Read contentList names
w (2)Modify contentCreate, delete or rename entries
x (1)ExecuteTraverse (enter) the directory
setuidRun as the file's owner
stickyOnly the owner may delete entries
chmod 644 config.yml     # rw-r--r--, typical for a config file
chmod 600 id_ed25519     # rw-------, required by ssh for private keys
chmod 755 scripts/run.sh # rwxr-xr-x
chown -R app:app /srv/app
umask                    # bits removed from new files, e.g. 022

# who am I, and what can I actually do?
id && sudo -l
  • A directory needs x to be traversed at all — r alone lets you list names but not open them.
  • Deleting a file requires write permission on the directory, not on the file.
  • setuid on a script is ignored by most kernels; it only works on real binaries.
  • Prefer groups over world-writable directories: chmod 777 is a maintenance debt, not a fix.

Buffered, unbuffered and durable writes

A write returns as soon as the data reaches the kernel's page cache. That is fast and safe against process crashes, but not against power loss. Durability requires an explicit flush to the storage device.

import os

# buffered: fast, data is in the page cache after write() returns
with open("out.txt", "w", encoding="utf-8") as f:
    f.write("hello")
    f.flush()             # push Python buffer -> kernel
    os.fsync(f.fileno())  # push kernel buffer -> device

# an atomic replace: write a temp file, fsync it, then rename
os.replace("out.txt.tmp", "out.txt")   # rename is atomic within a filesystem
  • Sequential I/O is far cheaper than random I/O on spinning disks; on SSDs the gap is smaller but write amplification still punishes small random writes.
  • Direct I/O bypasses the page cache; it helps databases with their own caching and hurts almost everything else.
  • Writes are usually buffered and coalesced by the kernel, so measuring write() latency tells you little about device latency.
  • Use iostat -x 1 to see queue depth and utilisation rather than guessing.

FAQ

Why did my file disappear but the disk is still full?
A process still holds an open descriptor to the unlinked inode. Find it with lsof +L1, restart that process, and the space returns.
Is <code>sync</code> enough before a rename?
For a crash-consistent replace, flush the file's contents first (fsync) and then rename. To be strictly safe, also fsync the containing directory so the rename itself is durable.

Memory and virtual memory Processes and threads

Last refreshed 2026-09-18.