Unix File Systems and Permissions

Permissions are one of those things you think you understand until you have to explain them to someone else. The nine-character string from ls -l-rw-r--r-- — looks simple, but there are enough edge cases hiding in it to keep things interesting. This covers the core concepts and the parts that actually trip people up.

The Directory Tree

Everything in Unix lives under /. There's no C: drive, no volumes by letter — it's one tree, and other disks get mounted into it at whatever point you choose.

Unix Filesystem Tree A tree diagram showing the root directory and common mount points. / /bin /home (mount point) /etc /dev (virtual fs) /var
The unified directory namespace. External partitions (like /home) and virtual filesystems (like /dev) are mounted directly into a single tree.
DirectoryPurpose
/bin, /usr/binSystem and user binaries (commands)
/sbin, /usr/sbinSystem administration binaries (often root-only)
/etcConfiguration files — plain text, almost always editable
/homeUser home directories
/varLogs, caches, mail, databases — anything that grows over time
/tmpTemporary files, usually cleared on reboot
/proc, /sysLinux virtual filesystems exposing process and kernel/device information. They are not universal Unix paths; BSD and macOS expose this information differently.
/devDevice files — disks, terminals, serial ports

File Types

The first character of ls -l output tells you what something actually is:

-   regular file
d   directory
l   symbolic link
c   character device (terminals, serial ports)
b   block device (disks)
s   socket (used by network services and databases)
p   named pipe (FIFO — for inter-process communication)

Reading the Permission Bits

Unix File Permissions Diagram breaking down the permission string into type, owner, group, and others. d r w x r - x r - - Type User Group Other
The 10-character mode string begins with the file type, followed by three independent triplets representing Owner, Group, and Other (World) access.
$ ls -l notes.txt
-rw-r--r-- 1 jason users 4096 Feb  5 12:34 notes.txt

Breakdown:
  1          number of hard links
  jason      owner
  users      group
  4096       size in bytes
  Feb  5     modification time
  notes.txt  name

For directories, x (execute) means you can enter it (cd into it) and access files inside. Without x on a directory, you can't traverse it even if you can read the directory listing.

Changing Permissions: chmod

Numeric Mode

Read = 4, Write = 2, Execute = 1. Add them for each of the three groups (owner, group, others):

CommandResultTypical use
chmod 755 script.shrwxr-xr-xExecutable: owner full, others read+execute
chmod 644 config.txtrw-r--r--Files: owner read+write, others read
chmod 600 id_rsarw-------Private keys and secrets: owner only
chmod 700 ~/.sshrwx------Private directories: owner only
chmod 2775 /srv/sharedrwxrwsr-xSetgid directory for shared team projects

Symbolic Mode

$ chmod u+x script.sh      # add execute for owner (u=user/owner)
$ chmod go-w file.txt      # remove write from group and others
$ chmod a+r readme.txt     # add read for all (a=all)
$ chmod o-rx /home/jason   # remove read+execute from others on home dir

Ownership: chown and chgrp

$ chown jason file.txt           # change owner
$ chown jason:users file.txt     # change owner and group
$ chgrp www-data /var/www/html   # change group only
$ chown -R jason:jason ~/project # recursive — useful after copying files as root

# You need root to give a file to another user.
# You can change your own files to a group you belong to without sudo.

Special Permission Bits

Setuid (4xxx)

When set on an executable, it runs as the file's owner rather than whoever ran it. The classic example is passwd — ordinary users need to be able to change their own passwords, but the password database (/etc/shadow) requires root access to modify:

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59976 ... /usr/bin/passwd
           ↑ 's' in owner execute position = setuid

Setgid (2xxx)

On an executable: same idea — runs with the file's group. On a directory: new files created inside inherit the directory's group instead of the creator's primary group. Useful for shared project directories:

$ chmod 2775 /srv/shared
$ ls -ld /srv/shared
drwxrwsr-x 2 root devteam 4096 ... /srv/shared
              ↑ 's' in group execute position = setgid

Files created inside /srv/shared automatically get group "devteam"
regardless of which team member creates them.

Sticky Bit (1xxx)

On a directory, only the file's owner (or root) can delete or rename files inside, even if others have write permission on the directory. /tmp uses this — everyone can write there, but you can't delete each other's files:

$ ls -ld /tmp
drwxrwxrwt 12 root root 4096 ... /tmp
              ↑ 't' at end = sticky bit

Access Control Lists (ACLs)

Standard Unix permissions give you three buckets — owner, group, everyone else. ACLs can add per-user or per-group permissions, but their commands and exact behaviour vary by operating system and filesystem. The following getfacl/setfacl examples describe the common Linux workflow on ACL-capable filesystems such as ext4, XFS, and Btrfs. FreeBSD also provides these commands with filesystem-specific details; macOS commonly uses chmod ACL syntax instead. Read the local manuals before changing ACLs.

$ getfacl file.txt                     # view current ACL
$ setfacl -m u:sarah:rw file.txt       # give sarah read+write
$ setfacl -m g:contractors:r file.txt  # give contractors read
$ setfacl -x u:sarah file.txt          # remove sarah's ACL entry
$ setfacl -b file.txt                  # remove all ACL entries

When a file has an ACL, ls -l shows a '+' at the end of the permissions:
  -rw-r--r--+ 1 jason users 4096 ... file.txt

Finding Files by Permission

$ find /usr/bin -perm -4000            # all setuid binaries (security audit)
$ find /home -perm -0002                # entries with the other-write bit set
$ find /var/www -not -user www-data    # files not owned by the web user
$ find /tmp -perm -002 -type f         # world-writable files in /tmp

References