Linux systems · 3 of 5

Filesystems, inodes and permissions

Inodes and links, the VFS and the page cache, file descriptors, the directory layout, /proc and /sys, and permission bits from rwx to setuid, capabilities and ACLs.

Updated 2026-09-08
On this page

On Linux a file's name and a file's contents are two different things, held in two different places. Once that clicks, hard links, deleted-but-open files, /proc, and half of the permission system stop being surprising. This page starts from the inode and works outward to the directory tree and who is allowed to touch what.

The map

Read this first when short on time. Every branch is a section below.

Figure 1. The whole article on one page. Every branch is a section below; fold what you know, open what you do not.

Inodes: where a file's metadata lives

An inode is the record a filesystem keeps for one file. It holds everything about the file except its name and its contents.

Every inode has a number, unique within its filesystem. ls -i prints it, stat prints all of the above. The name is not there, because the name lives in the directory.

flowchart TD
  D["Directory /var/log: a list of entries"] --> E1["syslog: inode 1310"]
  D --> E2["auth.log: inode 1311"]
  D --> E3["syslog.old: inode 1310"]
  E1 --> I["Inode 1310: mode 0640, owner syslog, size 48 KB, links 2, extents"]
  E3 --> I
  E2 --> J["Inode 1311: mode 0640, owner root, size 12 KB, links 1, extents"]
  I --> B["Data blocks on disk"]
  J --> B
Figure 2. Names live in directories, metadata in inodes, contents in blocks. Two directory entries pointing at inode 1310 are a hard link: one file, two names.

A directory is itself a file whose contents are a list of (name, inode number) pairs. Looking up /var/log/syslog means reading the root directory's inode to find the entry var, then that directory's inode to find log, then that one to find syslog, and finally the inode 1310 with the data. The kernel caches all of this so it happens once.

Each filesystem has a fixed number of inodes, set when it was created. df -i shows how many are used. A disk can be half empty by bytes and still refuse to create a file because a build directory, a mail spool or node_modules used every inode.

In the wild
  • ext4 and XFS store the data location as extents, so a 1 GB file written sequentially may be described by one extent instead of 262,144 block pointers.
  • find / -xdev -printf '%h\n' | sort | uniq -c | sort -n is the usual way to find the directory eating the inodes when df -i says 100 percent.
  • Backup tools such as rsync compare size and mtime from the inode to decide what changed, which is why touching a file's timestamp forces a copy.
Inode
The on-disk record of one file: metadata and data location, but not the name.
Directory entry
A (name, inode number) pair inside a directory. Also called a dentry.
Extent
A contiguous run of blocks described by a start and a length.

Links: two ways to give a file a second name

Because names are separate from inodes, one inode can have several names. Because a file can hold a path, a name can also point at another name. Those are hard links and symbolic links.

Hard link (ln a b)Symbolic link (ln -s a b)
What b isA second directory entry for a's inodeA new inode whose contents are the text "a"
If a is deletedb still works; the inode lives onb dangles and every open fails with ENOENT
Across filesystemsNo, inode numbers are per filesystemYes, it is just a path
To a directoryNot allowed, it would make loopsAllowed
Shown by ls -lLink count 2, otherwise identicall type and b -> a
PermissionsThe inode's, sharedIgnored; the target's apply

Deleting a file is unlink: remove one name and decrement the inode's link count. The data is freed only when the count reaches zero and no process has the file open. That second condition is the source of a classic problem. A process writes to a log, someone deletes the log to free space, and the space does not come back because the process still holds the descriptor. df says the disk is full and du cannot find the files. lsof +L1 lists open files with no name left; restarting the writer, or truncating through /proc/PID/fd/N, frees the space.

The same behaviour is useful on purpose. A program can open a temporary file, unlink it immediately, and keep using it: the file exists only as long as the process does and nobody else can find it. Package upgrades rely on it too. Replacing /usr/lib/libc.so.6 writes a new inode and renames it over the old name; running programs keep the old inode mapped until they exit.

In the wild
  • logrotate renames app.log to app.log.1 and creates a new app.log; the rename does not touch the inode, so the process keeps writing to the old one until it is told to reopen (usually with SIGHUP) or the copytruncate option is used.
  • /usr/bin/python3 is usually a symlink to python3.12, and update-alternatives on Debian manages whole chains of them.
  • Git stores every object once and hard-links between clones on the same disk (git clone without --no-hardlinks), which is why a local clone is instant.
Hard link
An extra directory entry for an existing inode. Indistinguishable from the original.
Symbolic link
A file containing a path. Resolved at every access.
Link count
The number of directory entries naming an inode. Zero plus no open descriptors means the data is freed.

The VFS: one API over many filesystems

The virtual filesystem is the layer in the kernel that makes ext4, XFS, tmpfs, NFS and /proc all answer the same open, read, write and stat. Programs never know which one they are talking to.

flowchart TD
  A["read(fd, buf, 4096)"] --> V["VFS: find the file object, the inode, the offset"]
  V --> C{"Cached?"}
  C -- yes --> R["Copy from the cached page to buf"]
  C -- no --> F["Ask the filesystem driver: ext4, XFS, NFS"]
  F --> B["Block layer: schedule the I/O, wait for the disk"]
  B --> P["Fill the page cache"]
  P --> R
Figure 3. A read through the VFS. The page cache sits in the middle; a second read of the same bytes never reaches the filesystem driver.

The VFS keeps four kinds of objects in memory: a superblock per mounted filesystem, an inode per file in use, a dentry per path component it has looked up (cached, so repeated path lookups are fast), and a file per open. Each filesystem driver fills in a table of functions for these objects, and the VFS calls through the table.

Mounting attaches one filesystem's root to a directory of another. The result is one tree, with / on one disk, /home maybe on another, /proc and /sys generated by the kernel, and /run in RAM. findmnt draws the tree; /proc/mounts is the raw list. Each container has its own mount table, which the containers article explains.

FilesystemWhat it is forNotes
ext4The default on Debian and UbuntuJournaled, extents, reliable and boring. The safe choice.
XFSThe default on RHEL; large files, many parallel writersCannot be shrunk. Good for databases and big volumes.
BtrfsSnapshots, checksums, built-in RAIDCopy-on-write. Default on Fedora and openSUSE.
tmpfsFiles in RAM: /run, /dev/shm, often /tmpGone at reboot. Can swap out.
overlayfsStacking read-only layers with a writable one on topWhat Docker uses for images and containers.
NFS, CIFSFiles on another machineA hung server puts processes in D state.
FUSEA filesystem implemented by a user space programsshfs, s3fs, rclone mount.

The page cache and durability

Almost every read and write goes through the page cache, the kernel's pool of file pages in RAM. A read that hits the cache is a memory copy. A write copies the bytes into the cache, marks the page dirty, and returns. The disk has not been touched. The kernel writes dirty pages back in the background, within about 30 seconds by default, or sooner if there are many of them.

That is fast and it is also why a power cut loses the last few seconds of writes. A program that needs the data on disk before it continues calls fsync(fd), which blocks until the file's dirty pages and metadata are written. Databases call it on every commit; that call is most of what makes a commit slow. O_DIRECT skips the cache entirely for programs that manage their own, and O_SYNC makes every write behave like write plus fsync.

Journaling protects the filesystem's own structures, not your data. ext4 writes a description of each metadata change to a journal before applying it, so a crash mid-way leaves either the old state or the new, never a half-updated directory. On a bad shutdown the kernel replays the journal at mount time instead of scanning the whole disk with fsck.

In the wild
  • Docker's overlay2 driver stacks an image's layers as read-only lower directories under one writable upper directory per container; a change to a file from a lower layer copies it up first.
  • PostgreSQL calls fsync on its write-ahead log at every commit and has had bugs where a failed fsync was treated as retryable; it now crashes on purpose instead, because the kernel may have already dropped the dirty page.
  • sync before pulling a USB drive exists for exactly the page cache reason; eject does it for you.
  • vmtouch and fincore show which pages of a file are currently in the cache, useful when a database's "cold start" is really just the cache being empty.
Watch out

A successful write means the kernel has the bytes, not the disk. Anything that must survive a crash needs fsync, and renaming a temp file over the real one needs an fsync of the directory too, or the new name may vanish while the old data survives. The storage article covers how databases build on this with write-ahead logs.

VFS
Virtual filesystem. The kernel layer that gives every filesystem the same interface.
Mount
Attaching a filesystem's root at a directory so it joins the single tree.
Page cache
File pages kept in RAM. Reads hit it; writes land in it and reach disk later.
fsync
Block until a file's dirty pages and metadata are on stable storage.
Journal
A log of metadata changes written before the changes themselves, so a crash leaves the filesystem consistent.

File descriptors: how a process holds a file open

A file descriptor is the small integer open returns. It indexes a table that belongs to the process, and that table points into two more tables shared across the system. Knowing the three levels explains every odd thing about redirection, forks and offsets.

flowchart LR
  subgraph P["fd table"]
    F0["0"]
    F1["1"]
    F3["3"]
    F4["4"]
  end
  F0 --> O1["Open file description<br/>the terminal, O_RDWR"]
  F1 --> O1
  F3 --> O2["Open file description<br/>offset 4096, O_RDONLY"]
  F4 --> O3["Open file description<br/>offset 0, O_RDONLY"]
  O2 --> I["Inode<br/>/var/log/syslog"]
  O3 --> I
Figure 4. Three levels. Descriptors 0 and 1 share one open file description (the terminal). Descriptors 3 and 4 opened the same file separately and have independent offsets.
  1. The fd table is per process: an array from 0 up, each slot pointing at an open file description or empty. 0, 1 and 2 are standard input, output and error by convention only; the kernel does not care.
  2. The open file description is system-wide: it holds the current offset, the access mode and flags such as O_APPEND, and a pointer to the inode. One is created per open.
  3. The inode is the file itself.

dup and dup2 make a second descriptor pointing at the same open file description, so they share the offset. fork copies the fd table, so parent and child share every description too. That is why a child writing to inherited stdout appends after the parent's output rather than over it. Opening the same file twice creates two descriptions with independent offsets.

Descriptors survive exec unless opened with O_CLOEXEC, which is how a shell can open a file, dup2 it onto 1, and exec a program that then writes to the file without knowing. It is also how descriptors leak into child processes that should not have them, which is why modern code sets O_CLOEXEC by default.

Each process has a limit on open descriptors, ulimit -n, traditionally 1024 as a soft limit. Hitting it gives EMFILE, "Too many open files", and is the standard failure of a busy server or a program leaking descriptors. ls -l /proc/PID/fd shows every open descriptor as a symlink to what it points at, and lsof -p PID decodes them.

In the wild
  • Shell redirection is open plus dup2: cmd 2>&1 makes fd 2 a copy of fd 1, and the order of redirections matters because each one is a separate dup2.
  • nginx and Postgres ship with instructions to raise ulimit -n, and systemd's LimitNOFILE= sets it per service; the default of 1024 is too small for a few thousand connections.
  • Sockets and pipes are descriptors too, which is what lets epoll wait on a file, a socket and a timer in one call. The sockets article builds on this.
File descriptor
A per-process integer indexing an open file. Files, sockets, pipes, devices and timers all get one.
Open file description
The kernel object behind a descriptor: offset, flags, and the inode. Shared by dup and fork.
O_CLOEXEC
Close this descriptor automatically when the process execs a new program.

The directory tree: what goes where

The top-level directories follow a convention, the Filesystem Hierarchy Standard, that every distribution mostly follows. The rule underneath it is simple: separate what the distribution installs, what the administrator configures, and what changes while the machine runs.

DirectoryHoldsWho writes it
/usrInstalled software: /usr/bin, /usr/lib, /usr/shareThe package manager. Could be mounted read-only.
/usr/localSoftware you built or installed by hand, same layoutYou. The package manager never touches it.
/bin, /sbin, /libSymlinks into /usr on current distributionsHistorical; kept so old paths work.
/optLarge self-contained third-party packages: /opt/google/chromeTheir installers.
/etcConfiguration for this host: /etc/nginx, /etc/ssh, /etc/fstab, /etc/passwdThe administrator. Text files.
/varData that changes: /var/log, /var/lib (databases, Docker), /var/cache, /var/spoolRunning services.
/runRuntime state since boot: PID files, sockets, lock files. A tmpfsServices. Empty at every boot.
/tmpTemporary files, often a tmpfs, usually cleared at bootAnyone. Sticky bit set.
/home, /rootUsers' files. /root is root's, kept on the root filesystemUsers.
/bootThe kernel, the initramfs, the bootloaderKernel packages.
/dev, /proc, /sysKernel-generated views, next sectionThe kernel.
/mnt, /mediaPlaces to mount other disks, by hand or automatically
/srvData served by this host, such as a web rootRarely used in practice.

The split explains where things go when you deploy a service. Its binary belongs under /usr/local/bin or /opt. Its configuration goes in /etc/myservice/. Its data goes in /var/lib/myservice/, its logs in /var/log/myservice/, and its socket or PID file in /run/myservice/. A backup that takes /etc, /home and /var captures everything the package manager cannot reinstall.

/run replaced /var/run, which is now a symlink to it. It was moved because /var might be on a disk that is not mounted yet during early boot, and PID files were needed before that.

In the wild
  • Docker keeps images, layers and volumes under /var/lib/docker, listens on /run/docker.sock, and is configured in /etc/docker/daemon.json: one service, three directories, exactly by the rule.
  • Container images often break the convention deliberately, putting an app in /app, because nothing else is in the image anyway.
  • /etc/passwd and /etc/shadow are the user database, plain text with one line per user; /etc/passwd is world-readable, the password hashes in /etc/shadow are not.
FHS
Filesystem Hierarchy Standard. The convention for what each top-level directory holds.
tmpfs
A filesystem in RAM. /run and /dev/shm are always one; /tmp usually.

/proc and /sys: the kernel as text files

Two directories contain no files on any disk. Their contents are generated by the kernel when you read them, and some of them change kernel state when you write them. They are how ps, top, free and Docker actually work.

/proc

/proc has one directory per process, named by PID, plus a set of files about the machine. /proc/self is a symlink to the reading process's own directory. Files show a size of 0 because there is nothing to size; the text appears on read.

cat /proc/1234/status      # name, state, PID, PPID, UIDs, threads, VmRSS
cat /proc/1234/cmdline     # argv, NUL separated (tr '\0' ' ')
cat /proc/1234/maps        # every memory region: address, perms, backing file
ls -l /proc/1234/fd        # open descriptors as symlinks
ls -l /proc/1234/exe       # the running binary, even if it was deleted
cat /proc/1234/cgroup      # which cgroup it is in
cat /proc/1234/limits      # ulimits in effect
cat /proc/1234/oom_score_adj

cat /proc/meminfo          # what free reads
cat /proc/cpuinfo          # one block per CPU
cat /proc/loadavg          # 0.52 0.58 0.59 2/1042 8123
cat /proc/mounts           # the mount table
cat /proc/net/tcp          # every TCP socket, what ss reads

/proc/sys is different: it is the kernel's tunables, and writing to a file changes the setting immediately. sysctl is a front end for it: sysctl net.ipv4.ip_forward reads /proc/sys/net/ipv4/ip_forward, and sysctl -w or echo 1 > sets it. /etc/sysctl.d/ makes a setting survive a reboot. Common ones: vm.swappiness, vm.overcommit_memory, net.core.somaxconn, fs.file-max, kernel.pid_max.

/sys

/sys is the kernel's view of devices and drivers, one directory per object and one value per file. Where /proc grew organically, /sys is strictly structured. /sys/class/net/eth0/statistics/rx_bytes is the byte counter for one interface; /sys/block/sda/queue/scheduler shows and sets the I/O scheduler; /sys/class/thermal has the temperature sensors. /sys/fs/cgroup is where the cgroup tree lives, and writing there is how a container's limits are set.

/dev

/dev holds device nodes: special files that stand for hardware or a kernel service. Each has a type (character or block), a major number picking the driver, and a minor number picking the device. /dev/sda is a whole disk, /dev/sda1 a partition, /dev/tty1 a console. A few are pure software: /dev/null swallows writes, /dev/zero produces zeros, /dev/urandom produces random bytes, /dev/shm is a tmpfs for shared memory. The nodes are created by the kernel (devtmpfs) and named and given permissions by udev as devices appear.

In the wild
  • ps, top and htop are loops over /proc/*/stat; there is no process-listing system call.
  • Docker and Kubernetes create a container's resource limits by writing numbers into files under /sys/fs/cgroup.
  • Prometheus's node_exporter reads nearly all of its metrics from /proc and /sys, which is why it needs the host's versions mounted when it runs in a container.
  • A container's /proc is mounted fresh inside its PID namespace, which is why ps inside a container shows only the container's processes.
Watch out

/proc/PID/exe and /proc/PID/fd/N point at inodes, not names. That makes them the way to recover a deleted binary or a deleted log (cp /proc/PID/fd/3 recovered.log), and the way to truncate an open file that is filling the disk (: > /proc/PID/fd/3).

procfs
The filesystem mounted at /proc: process information and kernel tunables, generated on read.
sysfs
The filesystem mounted at /sys: devices, drivers and kernel subsystems, one value per file.
Device node
A special file in /dev that names a driver by major and minor number.
sysctl
The command and the setting namespace for /proc/sys.

Permissions: who may do what

Every inode carries an owner, a group, and nine permission bits: read, write and execute, each for the owner, the group, and everyone else. The kernel checks them on every open, and the check picks exactly one of the three sets.

$ ls -l /etc/passwd /etc/shadow /usr/bin/passwd /tmp
-rw-r--r-- 1 root root    2847 Sep  1 10:12 /etc/passwd
-rw-r----- 1 root shadow  1424 Sep  1 10:12 /etc/shadow
-rwsr-xr-x 1 root root   68208 Mar 23 14:00 /usr/bin/passwd
drwxrwxrwt 9 root root    4096 Sep  8 09:30 /tmp

Read the first column in groups of three after the type character: rw- for the owner, r-- for the group, r-- for others. The same thing in octal is one digit per group, with r=4, w=2, x=1: rw-r--r-- is 644, rwxr-xr-x is 755. chmod 640 file and chmod g-w file are two spellings of the same operation.

The check goes: if you are the owner, only the owner bits apply. Else if you are in the group, only the group bits. Else the other bits. It stops at the first match, so a file owned by you with mode ---rwxrwx is unreadable to you and open to everyone else.

On a directory the same three bits mean something else. r lets you list the names. x lets you enter it and reach the inodes by name, which is needed to open anything inside. w lets you create, rename and delete entries. Deleting a file is a write to the directory, not to the file: you can delete a file you cannot read, and you cannot delete a file you own if the directory is not writable by you.

New files get their mode from the creating program's request (usually 666 for files, 777 for directories) minus the umask. With the common umask of 022 that yields 644 and 755; with 077 nothing is visible to others. umask is inherited across fork and exec, so a service's umask comes from systemd or its startup script.

Root, user ID 0, skips these checks. It can read and write anything and enter any directory. Only the execute bit is honoured: root cannot run a file with no x bit at all.

In the wild
  • ssh refuses a private key that is readable by anyone else, which is the origin of chmod 600 ~/.ssh/id_ed25519, and it refuses a home directory that is group-writable.
  • Web servers run as www-data, and the usual permission mistake is files in the web root owned by the deploying user with mode 600, so the server gets 403.
  • chown -R and chmod -R 777 are how people make things work at 2 a.m. and get breached at 3.
Mode
The permission bits plus the type and special bits, shown as -rwxr-xr-x or octal 0755.
umask
The bits removed from every newly created file's mode. 022 by default.
Owner, group, other
The three classes a permission check picks between. The first match wins.

Beyond the nine bits: setuid, sticky, capabilities and ACLs

Nine bits are not enough for everything, so there are three special bits in the mode, a way to split root's power into pieces, and a way to attach extra users and groups to a file.

setuid, setgid and the sticky bit

A program with the setuid bit runs with the file owner's user ID instead of the caller's. /usr/bin/passwd is setuid root (the s in rwsr-xr-x) so an ordinary user can update /etc/shadow, a file they cannot otherwise write. sudo works the same way. Every setuid root binary is a piece of code that must be bug-free, which is why distributions keep the list short and why the kernel ignores the bit on scripts.

setgid on a file does the same for the group. On a directory it means something more useful: new files inside inherit the directory's group rather than the creator's, which is how a shared project directory keeps everything owned by one group.

The sticky bit on a directory (the t in /tmp's rwxrwxrwt) means that even though everyone can create files there, only a file's owner, the directory's owner, or root can delete or rename it. Without it, a world-writable directory lets anyone delete anyone's files.

In octal they are a fourth digit in front: 4 for setuid, 2 for setgid, 1 for sticky. chmod 4755 program, chmod 2775 shared/, chmod 1777 /tmp.

Capabilities

Traditionally a process was either root, able to do everything, or not. Capabilities split root's power into about 40 named pieces so a process can have just the one it needs.

CapabilityAllowsWho wants it
CAP_NET_BIND_SERVICEListening on ports below 1024A web server that should not be root
CAP_NET_RAWRaw socketsping, tcpdump
CAP_NET_ADMINConfiguring interfaces, routes, firewallDocker, VPN clients
CAP_SYS_PTRACETracing other processesstrace, debuggers
CAP_DAC_OVERRIDEIgnoring file permission bitsBackup tools
CAP_CHOWNChanging file ownershipPackage managers
CAP_SYS_ADMINMounting, and a long list of other thingsNearly root. Avoid granting it.

setcap cap_net_bind_service=+ep /usr/local/bin/myserver lets that binary bind port 80 as an ordinary user. getcap shows what a file has, and /proc/PID/status shows a running process's sets. systemd units can set CapabilityBoundingSet= and AmbientCapabilities=, and Docker starts containers with a short default list and drops the rest, so root inside a container is far weaker than root outside.

Access control lists

When one owner and one group are not enough, an ACL attaches extra entries to the inode: this user may read, that group may write. setfacl -m u:alice:rw file adds one; getfacl file lists them. ls -l shows a + after the mode when a file has an ACL, and the group bits then show the ACL mask rather than the group's real permissions, which surprises people. ACLs are stored as extended attributes and are supported by ext4, XFS and Btrfs.

In the wild
  • Docker starts a container with 14 capabilities out of about 40, no CAP_SYS_ADMIN, and a seccomp filter on top; --cap-drop ALL --cap-add NET_BIND_SERVICE is the recommended shape for a web service.
  • systemd hardening options such as ProtectSystem=strict, NoNewPrivileges=yes and CapabilityBoundingSet= turn a service into something close to a container without one.
  • ping used to be setuid root; modern distributions give it CAP_NET_RAW as a file capability instead, or let the kernel allow ICMP echo sockets without any privilege.
Watch out

A setuid binary and a capability-bearing binary are both ignored if the filesystem is mounted with nosuid, which is the default for /tmp on systemd systems and for user-mounted drives. Something that works from /usr/local/bin and silently loses its powers when copied to /tmp is hitting this.

setuid
A mode bit making a program run as the file's owner rather than the caller.
Sticky bit
On a directory: only a file's owner may delete or rename it.
Capability
One named slice of root's privilege that a process or file can hold on its own.
ACL
Access control list. Extra per-user and per-group permission entries on one file.

Recap

  • An inode holds a file's metadata and data location; a directory holds names mapped to inode numbers. The name is not in the inode.
  • A hard link is a second name for the same inode. A symlink is a file containing a path. Deleting removes a name; the data goes when the link count is zero and nobody has it open.
  • A deleted file still held open keeps its space; lsof +L1 finds it and /proc/PID/fd reaches it.
  • The VFS gives every filesystem the same API and joins them with mounts into one tree. ext4 is the safe default, XFS for large volumes, tmpfs for RAM, overlayfs for containers.
  • Reads and writes go through the page cache. write returning does not mean the disk has the data; fsync does. Journaling protects the filesystem's structure, not your bytes.
  • A file descriptor indexes a per-process table pointing at a shared open file description (offset, flags) pointing at an inode. dup and fork share descriptions; separate opens do not.
  • /usr is the distribution's, /usr/local and /opt are yours, /etc is configuration, /var is state, /run and /tmp are RAM and cleared at boot.
  • /proc is processes and tunables, /sys is devices and subsystems, /dev is device nodes. All generated by the kernel, and writing to /proc/sys and /sys/fs/cgroup changes it.
  • Nine permission bits, first matching class wins. On directories, x means enter and w means create or delete entries. umask removes bits from new files. Root skips everything but the execute bit.
  • setuid runs a program as its owner, setgid on a directory inherits the group, sticky protects files in shared directories.
  • Capabilities split root into named pieces; give a service CAP_NET_BIND_SERVICE instead of root. ACLs add extra users and groups to one file.
  • nosuid mounts ignore setuid bits and file capabilities.

Questions

Try answering each one out loud before opening it. Lead with the one-line answer, then a couple of points, then one extra detail.

What is an inode, and what is not stored in it?

The filesystem's record for one file: type, permissions, owner, size, timestamps, link count and the location of the data blocks. It does not store the file's name; names live in directory entries that map a name to an inode number.

  • Two names can point at one inode; that is a hard link.
  • The inode number is unique only within one filesystem.

A filesystem has a fixed inode count set at creation, so df -i can hit 100 percent while df -h shows free space.

Hard link versus symbolic link?

A hard link is a second directory entry for the same inode, indistinguishable from the first; a symlink is a separate small file whose contents are a path, resolved at every access.

  • Hard links cannot cross filesystems or point at directories; symlinks can do both and can dangle.
  • Deleting the original leaves a hard link working and a symlink broken.

A symlink's own permission bits are ignored; the target's apply.

df says the disk is full but du cannot find the files. What happened?

A large file was deleted while a process still had it open; the name is gone so du cannot see it, but the inode and its blocks are kept until the last descriptor closes.

  • lsof +L1 lists open files with a link count of zero and the process holding them.
  • Fix by restarting the process, or truncating through /proc/PID/fd/N.

The same rule is why a running program survives its binary being replaced by an upgrade: it keeps the old inode mapped.

What does the VFS do?

It is the kernel layer that presents one set of file operations to programs and dispatches them to whichever filesystem driver owns the path, so ext4, tmpfs, NFS and /proc all look the same to open and read.

  • It keeps the superblock, inode, dentry and file objects in memory, and caches path lookups.
  • Mounts attach filesystem roots to directories to build one tree.

Each container gets its own mount table, which is how its / can be an image layer while the host's stays put.

When write returns, is the data on disk?

No. It is in the page cache, marked dirty, and the kernel writes it back later, typically within 30 seconds; fsync is what blocks until it is on stable storage.

  • Databases call fsync on every commit; that call is most of a commit's latency.
  • Renaming a temp file over the real one also needs an fsync of the directory.

Journaling makes the filesystem's structure consistent after a crash but does nothing for unsynced file contents.

Explain the three levels behind a file descriptor.

A per-process fd table maps small integers to system-wide open file descriptions, which hold the offset and flags and point at the inode.

  • dup and fork share one description, so they share the offset; opening the file twice gives independent offsets.
  • Descriptors survive exec unless opened with O_CLOEXEC.

Shell redirection is open then dup2 onto 0, 1 or 2 in the child before exec.

What does "Too many open files" mean and how do you fix it?

The process hit its per-process descriptor limit (ulimit -n, often 1024) and open or accept failed with EMFILE; either the limit is too small for the workload or the program is leaking descriptors.

  • Count them with ls /proc/PID/fd | wc -l and see what they are with lsof -p.
  • Raise it with LimitNOFILE= in the systemd unit or ulimit -n in the shell.

Sockets count too, so a server with 5,000 connections needs a limit above 5,000 regardless of files.

Where do a service's binary, configuration, data, logs and socket go?

Binary in /usr/local/bin or /opt, configuration in /etc/name/, data in /var/lib/name/, logs in /var/log/name/, socket and PID file in /run/name/.

  • /usr belongs to the package manager; /usr/local and /opt are never touched by it.
  • /run is a tmpfs cleared at boot; /var persists.

Docker follows this exactly: /etc/docker, /var/lib/docker, /run/docker.sock.

Why is /run separate from /var/run and /tmp?

/run is a tmpfs available from the earliest moments of boot, before /var may be mounted, for PID files, sockets and locks; /var/run is now a symlink to it, and /tmp is for arbitrary temporary files from any user.

  • Both are emptied at reboot, which is what runtime state should want.
  • /tmp has the sticky bit so users cannot delete each other's files.

systemd creates per-service directories under /run with RuntimeDirectory=.

What is /proc, and how does ps use it?

A kernel-generated filesystem with one directory per process containing its state, memory map, open descriptors and command line, plus machine-wide files like meminfo; ps and top simply read /proc/*/stat because there is no process-listing system call.

  • /proc/sys is the tunables; writing to it changes kernel settings live, and sysctl is the front end.
  • Files report size 0 because the text is generated on read.

Inside a container /proc is mounted for that PID namespace, so it shows only the container's processes.

What is the difference between /proc and /sys?

/proc is about processes and kernel tunables and grew without much structure; /sys is the device and driver model with one directory per kernel object and one value per file.

  • Interface statistics, block device queues, thermal sensors and the cgroup tree are under /sys.
  • Container runtimes set limits by writing into /sys/fs/cgroup.

Monitoring agents read nearly everything from these two trees, which is why they need the host's mounted when containerised.

What do r, w and x mean on a directory?

r lets you list the names, x lets you enter it and reach inodes by name, w lets you create, rename and delete entries.

  • Deleting a file is a write to its directory, so you can delete a file you cannot read.
  • A directory with x but not r lets you open files whose names you already know.

The sticky bit narrows w so only the file's owner can delete it, which is why /tmp is safe to share.

How is a new file's permission decided?

The creating call asks for a mode, usually 666 for files and 777 for directories, and the process's umask removes bits from it; with umask 022 that gives 644 and 755.

  • umask is inherited across fork and exec, so a service's comes from systemd or its start script.
  • The owner is the creating user; the group is the user's primary group unless the directory is setgid.

Umask 077 is what you want for anything holding secrets.

What does the setuid bit do, and why is it dangerous?

It makes a program run with its file owner's user ID instead of the caller's, so passwd can edit /etc/shadow as root for any user; any bug in a setuid root binary is a full privilege escalation.

  • The kernel ignores it on scripts and on nosuid mounts.
  • Capabilities let a binary have one slice of root instead of all of it.

setgid on a directory is unrelated and safe: new files inherit the directory's group.

How would you let a web server bind port 80 without running it as root?

Give the binary or the service the CAP_NET_BIND_SERVICE capability: setcap cap_net_bind_service=+ep on the file, or AmbientCapabilities=CAP_NET_BIND_SERVICE in its systemd unit.

  • Alternatives: bind a high port behind a reverse proxy, or lower net.ipv4.ip_unprivileged_port_start.
  • Docker grants that capability to containers by default, which is why nginx in a container can bind 80 as an unprivileged user.

Never grant CAP_SYS_ADMIN for this; it is nearly root.

What is an ACL and when would you use one?

An access control list adds extra per-user and per-group permission entries to one file beyond the single owner and group; use it when a file must be readable by two specific groups or one extra user without opening it to everyone.

  • setfacl -m u:alice:rw file and getfacl file manage them; ls -l shows a +.
  • With an ACL present the group column of ls -l shows the mask, not the group's real bits.

ACLs live in extended attributes and are lost by tools that do not copy them, such as a plain cp without -p.