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.
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.
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.
- Type: regular file, directory, symlink, device, socket, FIFO.
- Permission bits and owner (user ID, group ID).
- Size in bytes, and how many blocks are allocated.
- Three timestamps: last access (
atime), last content change (mtime), last metadata change (ctime). - The link count: how many names point at this inode.
- Where the data is: a list of block pointers, or on ext4 and XFS a list of extents (start block, length).
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
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.
- 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 -nis the usual way to find the directory eating the inodes whendf -isays 100 percent.- Backup tools such as rsync compare size and
mtimefrom 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 is | A second directory entry for a's inode | A new inode whose contents are the text "a" |
If a is deleted | b still works; the inode lives on | b dangles and every open fails with ENOENT |
| Across filesystems | No, inode numbers are per filesystem | Yes, it is just a path |
| To a directory | Not allowed, it would make loops | Allowed |
Shown by ls -l | Link count 2, otherwise identical | l type and b -> a |
| Permissions | The inode's, shared | Ignored; 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.
- logrotate renames
app.logtoapp.log.1and creates a newapp.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 thecopytruncateoption is used. /usr/bin/python3is usually a symlink topython3.12, andupdate-alternativeson Debian manages whole chains of them.- Git stores every object once and hard-links between clones on the same disk (
git clonewithout--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
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.
| Filesystem | What it is for | Notes |
|---|---|---|
| ext4 | The default on Debian and Ubuntu | Journaled, extents, reliable and boring. The safe choice. |
| XFS | The default on RHEL; large files, many parallel writers | Cannot be shrunk. Good for databases and big volumes. |
| Btrfs | Snapshots, checksums, built-in RAID | Copy-on-write. Default on Fedora and openSUSE. |
| tmpfs | Files in RAM: /run, /dev/shm, often /tmp | Gone at reboot. Can swap out. |
| overlayfs | Stacking read-only layers with a writable one on top | What Docker uses for images and containers. |
| NFS, CIFS | Files on another machine | A hung server puts processes in D state. |
| FUSE | A filesystem implemented by a user space program | sshfs, 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.
- 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
fsyncon 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. syncbefore pulling a USB drive exists for exactly the page cache reason;ejectdoes it for you.vmtouchandfincoreshow which pages of a file are currently in the cache, useful when a database's "cold start" is really just the cache being empty.
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
- 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.
- 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 peropen. - 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.
- Shell redirection is
openplusdup2:cmd 2>&1makes fd 2 a copy of fd 1, and the order of redirections matters because each one is a separatedup2. - nginx and Postgres ship with instructions to raise
ulimit -n, and systemd'sLimitNOFILE=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
epollwait 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.
| Directory | Holds | Who writes it |
|---|---|---|
/usr | Installed software: /usr/bin, /usr/lib, /usr/share | The package manager. Could be mounted read-only. |
/usr/local | Software you built or installed by hand, same layout | You. The package manager never touches it. |
/bin, /sbin, /lib | Symlinks into /usr on current distributions | Historical; kept so old paths work. |
/opt | Large self-contained third-party packages: /opt/google/chrome | Their installers. |
/etc | Configuration for this host: /etc/nginx, /etc/ssh, /etc/fstab, /etc/passwd | The administrator. Text files. |
/var | Data that changes: /var/log, /var/lib (databases, Docker), /var/cache, /var/spool | Running services. |
/run | Runtime state since boot: PID files, sockets, lock files. A tmpfs | Services. Empty at every boot. |
/tmp | Temporary files, often a tmpfs, usually cleared at boot | Anyone. Sticky bit set. |
/home, /root | Users' files. /root is root's, kept on the root filesystem | Users. |
/boot | The kernel, the initramfs, the bootloader | Kernel packages. |
/dev, /proc, /sys | Kernel-generated views, next section | The kernel. |
/mnt, /media | Places to mount other disks, by hand or automatically | — |
/srv | Data served by this host, such as a web root | Rarely 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.
- 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/passwdand/etc/shadoware the user database, plain text with one line per user;/etc/passwdis world-readable, the password hashes in/etc/shadoware not.
- FHS
- Filesystem Hierarchy Standard. The convention for what each top-level directory holds.
- tmpfs
- A filesystem in RAM.
/runand/dev/shmare always one;/tmpusually.
/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.
ps,topandhtopare 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
/procand/sys, which is why it needs the host's versions mounted when it runs in a container. - A container's
/procis mounted fresh inside its PID namespace, which is whypsinside a container shows only the container's processes.
/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
/devthat 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.
- 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 -Randchmod -R 777are 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-xor octal0755. - 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.
| Capability | Allows | Who wants it |
|---|---|---|
CAP_NET_BIND_SERVICE | Listening on ports below 1024 | A web server that should not be root |
CAP_NET_RAW | Raw sockets | ping, tcpdump |
CAP_NET_ADMIN | Configuring interfaces, routes, firewall | Docker, VPN clients |
CAP_SYS_PTRACE | Tracing other processes | strace, debuggers |
CAP_DAC_OVERRIDE | Ignoring file permission bits | Backup tools |
CAP_CHOWN | Changing file ownership | Package managers |
CAP_SYS_ADMIN | Mounting, and a long list of other things | Nearly 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.
- 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_SERVICEis the recommended shape for a web service. - systemd hardening options such as
ProtectSystem=strict,NoNewPrivileges=yesandCapabilityBoundingSet=turn a service into something close to a container without one. pingused to be setuid root; modern distributions give itCAP_NET_RAWas a file capability instead, or let the kernel allow ICMP echo sockets without any privilege.
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 +L1finds it and/proc/PID/fdreaches 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.
writereturning does not mean the disk has the data;fsyncdoes. 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.
dupandforkshare descriptions; separate opens do not. /usris the distribution's,/usr/localand/optare yours,/etcis configuration,/varis state,/runand/tmpare RAM and cleared at boot./procis processes and tunables,/sysis devices and subsystems,/devis device nodes. All generated by the kernel, and writing to/proc/sysand/sys/fs/cgroupchanges it.- Nine permission bits, first matching class wins. On directories,
xmeans enter andwmeans 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_SERVICEinstead of root. ACLs add extra users and groups to one file. nosuidmounts 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 +L1lists 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
fsyncon every commit; that call is most of a commit's latency. - Renaming a temp file over the real one also needs an
fsyncof 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.
dupandforkshare one description, so they share the offset; opening the file twice gives independent offsets.- Descriptors survive
execunless opened withO_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 -land see what they are withlsof -p. - Raise it with
LimitNOFILE=in the systemd unit orulimit -nin 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/.
/usrbelongs to the package manager;/usr/localand/optare never touched by it./runis a tmpfs cleared at boot;/varpersists.
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.
/tmphas 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/sysis the tunables; writing to it changes kernel settings live, andsysctlis 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
xbut notrlets 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
nosuidmounts. - 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 fileandgetfacl filemanage them;ls -lshows a+.- With an ACL present the group column of
ls -lshows 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.