file permissions, one command per page
Twenty-eight lessons on file permissions, one command or concept per page: the ls -l mode string and its three rwx triplets; stat's %A against %a; octal digits as 4, 2 and 1 and symbolic ugoa with +, - and =; chown's one-operand colon forms and chgrp beside it; the ownership gate that answers who may chmod, and the two denials, Permission denied and Operation not permitted; umask math where 666 less 022 makes 644; the execute bit that means search on a directory; setuid on passwd, setgid on files, the fourth octal digit and the sticky t on /tmp; getfacl, setfacl -m, the mask ceiling and -x against -b; install -m, chattr's immutable i and lsattr's flag string; and one final stat -c readout that audits it all.
One command, one mode string, one real bit. That's a page.
file permissions, one command per page
Twenty-eight lessons on file permissions, one command or concept per page: the ls -l mode string and its three rwx triplets; stat's %A against %a; octal digits as 4, 2 and 1 and symbolic ugoa with +, - and =; chown's one-operand colon forms and chgrp beside it; the ownership gate that answers who may chmod, and the two denials, Permission denied and Operation not permitted; umask math where 666 less 022 makes 644; the execute bit that means search on a directory; setuid on passwd, setgid on files, the fourth octal digit and the sticky t on /tmp; getfacl, setfacl -m, the mask ceiling and -x against -b; install -m, chattr's immutable i and lsattr's flag string; and one final stat -c readout that audits it all.
Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).
Every fact in this book is as the official source states it, fetched and read during this build: the Linux man-pages man7.org pages for chmod(1), chown(1), chgrp(1), ls(1), stat(1) and install(1) from GNU coreutils; chmod(2), umask(2) and inode(7) from the man-pages project; acl(5), getfacl(1) and setfacl(1) from the acl project; chattr(1) and lsattr(1) from e2fsprogs; and the POSIX umask utility from The Open Group (IEEE Std 1003.1-2017). No facts are sourced from Reddit, Stack Overflow or vendor blogs. Every command and output pair on every page was run on this machine (coreutils 9.4, acl 2.3.2, e2fsprogs 1.47.0, bash 5.2.21, kernel 6.8.0-147-generic) and confirmed before printing. The setuid, setgid and sticky demonstrations were run on scratch files in a scratch directory, never on system files. An independent guide, not affiliated with or endorsed by the GNU coreutils, man-pages, acl or e2fsprogs projects, The Open Group, or the Linux kernel developers.
Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.
© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.
Edition 1.0 · stevehodgkiss.net
Contents
Per Linux man-pages ls(1) (GNU coreutils): -l, the long listing format, lists one file per line with its mode, link count, owner, group, size and name; per stat(1), %A prints the permission bits and file type in human readable form, the same string ls -l leads with. This machine's ls -l report.txt printed -rw-r--r-- 1 root root 0 Oct 26 22:23 report.txt, and ls -ld project printed drwxr-xr-x 2 root root 4096 Oct 26 22:23 project.
Read the mode string
The first ten characters are the mode string: one type character, then three triplets for owner, group and other. Here -rw-r--r--.
After it: 1 hard link, owner root, group root, size 0, mtime, name. Our project directory reads drwxr-xr-x for the same reason: d for directory.
Read the string left to right and it answers who may read, write or run this file. The next two pages take the triplets apart.
Run ls -l on any file you own and say each of the ten leading characters out loud.
Per Linux man-pages chmod(1): chmod changes the file mode bits of each given file according to mode, which can be either a symbolic representation of changes to make, or an octal number representing the bit pattern for the new mode bits; an octal mode sets the whole pattern at once. This machine ran chmod 755 report.txt and stat -c '%A %a' printed -rwxr-xr-x 755; a later scratch run of chmod 400 probe.txt was followed by stat -c '%A %a' printing -r-------- 400.
chmod 644 or 755
chmod 755 report.txt set the whole mode at once: owner rwx, group and other r-x. Our stat printed -rwxr-xr-x 755.
Octal is total: chmod 400 left the probe file -r-------- 400, owner read only. Unmentioned bits are cleared, not kept.
That totality is the difference from symbolic modes, which change named bits and leave the rest alone.
Set a scratch file to 755, then to 400, and stat it after each: watch what disappears.
Per Linux man-pages chown(1) (GNU coreutils): if only an owner is given, that user is made the owner of each given file and the files' group is not changed; if the owner is followed by a colon and a group name, with no spaces between them, the group ownership is changed as well; if the colon and group are given but the owner is omitted, only the group is changed, and chown performs the same function as chgrp. This machine's chgrp daemon note.txt then stat -c '%U:%G %n' printed root:daemon note.txt, and chown :root printed root:root note.txt.
chown user:group
One operand, three shapes: user changes the owner only; user:group changes both; :group changes the group only.
Our capture: root:daemon after a group change, then root:root after chown :root gave it back. No spaces around the colon, per chown(1).
The colon-with-no-owner form is exactly chgrp, says chown(1) itself. The next page takes that shorter spelling.
Change a scratch file to user:group form and back with the colon-only form, stat -c '%U:%G' between.
Per Linux man-pages umask(2): in the usual case where the mode argument to open(2) is 0666 when creating a new file, the permissions on the resulting file will be S_IRUSR, S_IWUSR, S_IRGRP, S_IROTH, because 0666 & ~022 = 0644, i.e. rw-r--r--. This machine ran bash -c 'umask 077; touch fresh.txt; stat -c %a fresh.txt' and it printed 600; with umask 027, touch printed 640 and mkdir printed 750.
umask does the math
Files are requested as 666, directories as 777; the mask is subtracted. 666 and 022 leave 644; 777 and 022 leave 755.
Our runs: umask 077 gave a 600 file; umask 027 gave a 640 file and a 750 directory. umask(2) writes the canonical line: 0666 & ~022 = 0644.
No execute bit is ever requested for files, so no mask can add one. Directories always ask for x.
With umask 027, predict a new file and a new directory, then touch and mkdir to check.
Per Linux man-pages chmod(1): a numeric mode is from one to four octal digits, and the first digit selects the set user ID (4) and set group ID (2) and restricted deletion or sticky (1) attributes; omitted digits are assumed to be leading zeros. This machine's chmod 4755 script.sh printed -rwsr-xr-x 4755 and chmod 2755 printed -rwxr-sr-x 2755; the mode 755 alone, 0755, leaves all three attributes off.
The fourth digit
The digit you usually omit: 4 setuid, 2 setgid, 1 sticky, and they add. chmod 4755 gave -rwsr-xr-x; chmod 2755 gave -rwxr-sr-x.
Per chmod(1), omitted digits are leading zeros: plain 755 is 0755, all three special bits off.
5755 would set setuid and sticky at once. Read a mode like 4755 right to left: 755 the permissions, 4 the crown.
Compute what 2750 sets, then run it on a scratch file and verify every letter with stat -c %A.
Per Linux man-pages getfacl(1) (acl project): for each file, getfacl displays the file name, owner, the group, and the Access Control List; lines user::, group:: and other:: correspond to the user, group and other fields of the file mode permission bits and are called the base ACL entries; a # flags: line indicates the setuid, setgid and sticky bits and is included if any of those bits is set. This machine's getfacl note.txt printed the # file, # owner and # group header, then user::rw-, user:daemon:rw-, group::rw-, mask::rw-, other::r--.
getfacl
getfacl prints the whole list, not just nine bits: header lines, then one entry per line.
Our note.txt: the base entries user::rw-, group::rw-, other::r-- plus the extension, user:daemon:rw-, and a mask. The named user is an ACL entry, not a mode bit.
Per getfacl(1) the base entries are the mode bits themselves. Where ls -l says 664, getfacl says the same thing in long form.
Run getfacl on a file you own and on /tmp and compare the entry counts.
Per Linux man-pages chattr(1) (e2fsprogs): a file with the 'i' attribute cannot be modified: it cannot be deleted or renamed, no link can be created to this file, most of the file's metadata can not be modified, and the file can not be opened in write mode; only the superuser or a process possessing the CAP_LINUX_IMMUTABLE capability can set or clear this attribute; per chmod(2), EPERM is returned when the file is marked immutable or append-only. This machine's chattr +i app.sh was followed by lsattr app.sh printing ----i---------e------- app.sh, and chattr -i restored --------------e-------.
chattr +i
Attributes sit outside the mode: the i flag freezes the file past chmod, past rm, past rename.
Per chattr(1), an i file cannot be modified, deleted, renamed, hard linked, or opened for writing; only root or CAP_LINUX_IMMUTABLE may set or clear it. Our lsattr read ----i---------e-------.
chmod(2) reports it as EPERM: the file is marked immutable. Not a permission problem; a different layer.
Set +i on a scratch file, try to delete it, then clear it with chattr -i before you clean up.