courses

Working with Files

Permissions are access control, made concrete

Beyond navigating and editing, real Linux work means controlling who can do what to which file — which is exactly the discretionary access control (DAC) model introduced conceptually on the Access Control page, here in its operational form. Getting permissions right (or wrong) is the difference between a private SSH key and a leaked one, between a confined service and a privilege-escalation foothold.

This page covers file permissions (chmod, chown, special bits, umask), archiving and compression (tar, gzip/xz, zip), efficient copying/sync (rsync), and the two kinds of links the filesystem supports.

File Permissions

Linux uses a discretionary access control (DAC) model. Every file and directory has an owner (a user), an owning group, and a set of permission bits.

Reading permissions

The ls -l output shows permissions as a 10-character string:

-rwxr-xr--  1  alice  staff  4096  May 1 12:00  script.sh
^            ^  ^      ^
|            |  |      |
|            |  |      group owner
|            |  user owner
|            link count
file type and permission bits

Permission string breakdown:

- r w x  r - x  r - -
^  ^^^   ^^^    ^^^
|  |     |      other (everyone else)
|  |     group permissions
|  user (owner) permissions
file type: - file, d directory, l symlink, c char device, b block device

Each group of three bits: r (read=4), w (write=2), x (execute=1).

chmod — change permissions

# Symbolic mode
chmod u+x script.sh         # add execute for owner
chmod g-w file.txt          # remove write for group
chmod o=r file.txt          # set other to read-only
chmod a+r file.txt          # add read for all (u, g, o)
chmod u+x,g-w file.txt      # multiple changes

# Octal mode (most common in scripts and documentation)
chmod 755 script.sh         # rwxr-xr-x
chmod 644 file.txt          # rw-r--r--
chmod 600 ~/.ssh/id_rsa     # rw------- (private key)
chmod 700 ~/.ssh            # rwx------
chmod -R 755 /var/www       # recursive

Common permission patterns:

Octal Symbolic Typical use
755 rwxr-xr-x Directories, executables
644 rw-r--r-- Regular files
600 rw------- Private keys, sensitive configs
777 rwxrwxrwx World-writable (avoid)
700 rwx------ Private directories
664 rw-rw-r-- Group-collaborative files

chown — change ownership

chown alice file.txt                # change owner to alice
chown alice:staff file.txt          # change owner and group
chown :staff file.txt               # change group only (same as chgrp)
chown -R alice:staff /var/www       # recursive
chown --reference=ref.txt file.txt  # copy ownership from another file

chgrp — change group

chgrp staff file.txt
chgrp -R staff /var/www

Special permission bits

chmod u+s /usr/bin/program  # setuid: run as file owner, not caller
chmod g+s /shared/dir       # setgid: new files inherit directory's group
chmod +t /tmp               # sticky bit: only owner can delete their files

# Octal: add a leading digit
chmod 4755 /usr/bin/program  # setuid + 755
chmod 2775 /shared/dir       # setgid + 775
chmod 1777 /tmp              # sticky + 777

umask — default permission mask

umask               # show current mask (e.g. 0022)
umask 027           # new files: 640 (rw-r-----), dirs: 750
umask -S            # symbolic display

New file permissions = 0666 - umask; new directory permissions = 0777 - umask.

Worked example — set up a shared directory:

mkdir /shared
chown root:devteam /shared
chmod 2775 /shared      # setgid: new files inherit 'devteam' group
# Now all members of devteam can read/write; files stay group-owned

⚠️ setuid and 777 are where permissions become security holes. A setuid-root binary runs as root no matter who calls it, so a single buggy one is a privilege-escalation prize (find / -perm -4000 enumerates them). And chmod 777 grants everyone write — never use it as a lazy fix; grant the least access that works (least privilege). The access control page works these as exploitation/defense examples.

Archiving and Compressing

tar — tape archive

tar bundles multiple files into a single archive, optionally compressed.

# Create archives
tar -czf archive.tar.gz  directory/    # create gzip-compressed archive
tar -cjf archive.tar.bz2 directory/   # create bzip2-compressed archive
tar -cJf archive.tar.xz  directory/   # create xz-compressed archive
tar -cf  archive.tar      directory/   # uncompressed archive

# Extract archives
tar -xzf archive.tar.gz               # extract gzip archive
tar -xjf archive.tar.bz2              # extract bzip2 archive
tar -xJf archive.tar.xz               # extract xz archive
tar -xf  archive.tar.gz               # auto-detect compression (GNU tar)

# Extract to specific directory
tar -xzf archive.tar.gz -C /tmp/

# List archive contents
tar -tzf archive.tar.gz
tar -tf  archive.tar.gz               # auto-detect

# Verbose output during operation
tar -czvf archive.tar.gz directory/

Common tar flags:

Flag Meaning
-c Create archive
-x Extract archive
-t List contents
-f Specify archive filename (must be followed by filename)
-z gzip compression
-j bzip2 compression
-J xz compression
-v Verbose (show files being processed)
-C dir Change to directory before extracting
--strip-components=N Strip N leading path components

Worked example — back up and restore a directory:

# Backup with timestamp
tar -czf "backup-$(date +%Y%m%d).tar.gz" /etc/nginx/

# List before extracting
tar -tzf backup-20260501.tar.gz | head

# Extract to a staging directory
tar -xzf backup-20260501.tar.gz -C /tmp/restore/

gzip, bzip2, xz — standalone compression

gzip file.txt           # compress → file.txt.gz (original removed)
gzip -d file.txt.gz     # decompress (same as gunzip)
gunzip file.txt.gz      # decompress
gzip -k file.txt        # keep original file
gzip -l file.txt.gz     # show compression ratio

bzip2 file.txt          # compress → file.txt.bz2
bunzip2 file.txt.bz2    # decompress

xz file.txt             # compress → file.txt.xz (best ratio, slowest)
unxz file.txt.xz        # decompress
xz -k file.txt          # keep original

zcat file.txt.gz        # view compressed file without extracting
zless file.txt.gz       # page through compressed file

zip / unzip

zip -r archive.zip directory/   # create zip (recursive)
zip archive.zip file1 file2     # add specific files
unzip archive.zip               # extract
unzip -l archive.zip            # list contents
unzip archive.zip -d /tmp/      # extract to directory

Copying and Renaming

See also the navigation basics page for cp and mv fundamentals. This section covers more advanced patterns.

rsync — efficient file transfer and sync

rsync copies files efficiently, transferring only changed portions. It is preferred over cp for large trees and remote copies.

rsync -av source/ destination/         # sync directories, verbose
rsync -av --delete src/ dst/           # delete files in dst not in src
rsync -avz user@host:/remote/ /local/  # sync from remote over SSH
rsync -av --exclude='*.log' src/ dst/  # exclude files by pattern
rsync -n -av src/ dst/                 # dry run (show what would happen)
rsync -av --progress src/ dst/         # show per-file progress

Key rsync flags:

Flag Meaning
-a Archive: recursive, preserve symlinks, permissions, timestamps, owner
-v Verbose
-z Compress during transfer
-n Dry run
--delete Remove destination files not in source
--exclude=PATTERN Exclude matching files
--progress Show transfer progress
--bwlimit=KB Limit bandwidth

Worked example — deploy a website directory:

# Dry run first
rsync -navz --delete build/ user@webserver:/var/www/html/

# Apply for real
rsync -avz --delete build/ user@webserver:/var/www/html/

Renaming multiple files

The rename command (available in Perl and util-linux flavors) handles bulk renames.

# Perl rename (Debian/Ubuntu: 'rename'; RHEL: 'prename')
rename 's/\.txt$/.md/' *.txt           # change extension
rename 's/foo/bar/g' *.txt             # replace in all filenames
rename -n 's/\.txt$/.md/' *.txt        # dry run

# util-linux rename (simpler, on many systems)
rename .txt .md *.txt                  # replace suffix

# Using mv in a loop (portable)
for f in *.txt; do mv "$f" "${f%.txt}.md"; done

Linux supports two types of filesystem links, each with different properties.

A hard link is an additional directory entry pointing to the same inode (the actual data on disk). All hard links to a file are equally authoritative — there is no “original.”

ln file.txt hardlink.txt        # create hard link
ls -li file.txt hardlink.txt    # -i shows inode number (same for both)
stat file.txt                   # shows link count

Properties of hard links:

A symbolic link (symlink) is a special file containing a path to another file or directory. It is like a shortcut.

ln -s /path/to/target linkname          # create symbolic link
ln -s ../relative/path linkname         # relative symlink (preferred)
ln -sf /new/target linkname             # force: replace existing link
ls -la                                   # symlinks shown with -> target
readlink linkname                        # print target of symlink
readlink -f linkname                     # resolve all symlinks to absolute path

Properties of symbolic links:

Comparison

Property Hard link Symbolic link
Points to Inode (data) Path string
Cross-filesystem No Yes
Links to directories No (normally) Yes
Survives target deletion Yes (data intact) No (dangling link)
ls -l indicator None (same as file) l type, -> target
Inode number Same as original Different

Worked example — create a versioned binary symlink:

# Install python 3.12 and create a symlink
ln -s /usr/bin/python3.12 /usr/local/bin/python3
python3 --version

# Update to new version without changing scripts
ln -sf /usr/bin/python3.13 /usr/local/bin/python3

Worked example — find dangling symlinks:

find /etc -xtype l          # find symlinks whose targets don't exist

Key takeaways

References


Related course pages: Access Control and Authorization · Navigation Basics · Shell and Other Basics · User Management · Host Security

🛠️ Maintenance note: permission mechanics are stable. The rename command genuinely differs by distro (Perl rename/prename vs util-linux rename, with incompatible syntax) — verify which ships on the course VM before relying on the examples.