CertGrid CertGrid
Hands-on Lab·Red Hat Certified System Administrator

Files, Directories and Links

Two objectives that belong together, because the difference between a hard link and a soft link is invisible until you move or delete what they point at. Both are broken here on purpose, and the two things a hard link cannot do are demonstrated rather than listed.

Essential Tools Guide 7 of 67 Beginner

Written against the versions above. Nothing here is RHEL-specific - it is filesystem behaviour. The one detail that varies is that a hard link cannot cross a filesystem boundary, and on this machine `/boot` is a separate filesystem, which is what makes that demonstrable.

Every command on this page runs on RHCSA-A01.
Server NameIP AddressOSRolesCPURAMHDD
RHCSA-A01192.168.0.31RHEL 10.0 (Coughlan)Practice node (graded) - spare /dev/sda2 Core4 GB50 GB + 15 GB

Before you start

  1. Creating, copying, moving, removing

    mkdir -p ~/fl/one/two/three

    -p creates the whole chain and does not complain if it already exists - which is why it is the form to use in scripts.

    cp needs -r for a directory, and mv needs nothing special because it is renaming rather than copying. The one that surprises people:

    rmdir: failed to remove 'one-copy': Directory not empty
    exit=1 - rmdir only removes EMPTY directories

    rmdir is not a weaker rm -r - it refuses to remove anything containing files, which makes it the safe choice when you believe a directory is already empty. rm -r removes it and everything inside without asking.

    mv between two names on the same filesystem does not move data at all; it rewrites a directory entry. That is why renaming a 40 GB file is instant and copying it is not.

    bash Example session
    mkdir -p ~/fl/one/two/three && ls -R ~/fl | head -10/home/sysadmin/fl:one /home/sysadmin/fl/one:two /home/sysadmin/fl/one/two:three /home/sysadmin/fl/one/two/three:cd ~/fl && touch a.txt b.txt && echo "content of a" > a.txt && ls -ltotal 4-rw-r--r--. 1 sysadmin sysadmin 13 Aug 23 10:55 a.txt-rw-r--r--. 1 sysadmin sysadmin  0 Aug 23 10:55 b.txtdrwxr-xr-x. 3 sysadmin sysadmin 17 Aug 23 10:55 onecd ~/fl && cp a.txt copy.txt && cp -r one one-copy && ls -d one* && cat copy.txtoneone-copycontent of acd ~/fl && mv b.txt renamed.txt && ls; rmdir one-copy 2>&1; echo "exit=$? - rmdir only removes EMPTY directories"a.txtcopy.txtoneone-copyrenamed.txtrmdir: failed to remove 'one-copy': Directory not emptyexit=1 - rmdir only removes EMPTY directories

    Expected resultA directory tree, a copy, a rename, and rmdir refusing.

    Success conditionYou know which delete command protects you and which does not.

  2. A hard link is the same file

      262184 -rw-r--r--. 2 sysadmin sysadmin 13 ... a.txt
      262184 -rw-r--r--. 2 sysadmin sysadmin 13 ... hard.txt

    Two names, and read the first column: the same inode number, 262184. The 2 after the permissions is the link count - two directory entries pointing at one file.

    There is no original and no copy. Writing through one name changes what the other sees:

    content of a
    written through the hard link

    And deleting the name you created first changes nothing:

    content of a
    written through the hard link

    rm does not delete files, it removes names. The data goes when the last name goes and the link count reaches zero. That is why deleting a large file can free no space at all - something else still links it, or a running process still holds it open.

    bash Example session
    cd ~/fl && ln a.txt hard.txt && ls -li a.txt hard.txt34973609 -rw-r--r--. 2 sysadmin sysadmin 13 Aug 23 10:55 a.txt34973609 -rw-r--r--. 2 sysadmin sysadmin 13 Aug 23 10:55 hard.txtcd ~/fl && echo "written through the hard link" >> hard.txt && cat a.txtcontent of awritten through the hard linkcd ~/fl && rm a.txt && cat hard.txt && ls -li hard.txtcontent of awritten through the hard link34973609 -rw-r--r--. 1 sysadmin sysadmin 43 Aug 23 10:55 hard.txt

    Expected resultOne inode with two names, surviving the removal of one.

    Success conditionYou understand that rm removes a name, not a file.

  3. A soft link is a signpost

      262184 -rw-r--r--. 1 sysadmin sysadmin 43 ... hard.txt
      262190 lrwxrwxrwx. 1 sysadmin sysadmin  8 ... soft.txt -> hard.txt

    A different inode, type l, and a size of 8 - which is the length of the string hard.txt. A symlink is a small file whose contents are a path.

    So it breaks when the path stops being true:

    lrwxrwxrwx. 1 sysadmin sysadmin 8 ... soft.txt -> hard.txt
    cat: soft.txt: No such file or directory
    exit=1 - the symlink still points at a name that is gone

    The target was renamed, and the link now points at nothing. Note that ls -l still shows the link happily - a dangling symlink is not an error until something follows it.

    That is the trade. A hard link cannot dangle, because it *is* the file. A symlink can point anywhere, including at another filesystem or at a path that does not exist yet - which is exactly why /etc/localtime and most of /usr/bin's alternatives are symlinks.

    bash Example session
    cd ~/fl && ln -s hard.txt soft.txt && ls -li hard.txt soft.txt34973609 -rw-r--r--. 1 sysadmin sysadmin 43 Aug 23 10:55 hard.txt35025560 lrwxrwxrwx. 1 sysadmin sysadmin  8 Aug 23 10:55 soft.txt -> hard.txtcd ~/fl && cat soft.txt && readlink soft.txtcontent of awritten through the hard linkhard.txtcd ~/fl && mv hard.txt moved.txt && ls -l soft.txt; cat soft.txt 2>&1; echo "exit=$? - the symlink still points at a name that is gone"lrwxrwxrwx. 1 sysadmin sysadmin 8 Aug 23 10:55 soft.txt -> hard.txtcat: soft.txt: No such file or directoryexit=1 - the symlink still points at a name that is gonecd ~/fl && ln -s /etc/hostname abs.txt && ls -l abs.txt && cat abs.txtlrwxrwxrwx. 1 sysadmin sysadmin 13 Aug 23 10:55 abs.txt -> /etc/hostnamerhcsa-a01

    Expected resultA link of type l, then the same link dangling after a rename.

    Success conditionYou can predict which link type survives a rename.

  4. The two things a hard link cannot do

    Not a directory:

    ln: one: hard link not allowed for directory
    exit=1

    Refused outright. Directory hard links would let you build a loop in the filesystem tree, which nothing that walks it could safely handle. . and .. are the only exceptions, and the kernel maintains those.

    Not across filesystems:

    ln: failed to create hard link 'cross.txt' => '/boot/vmlinuz-6.12.0-55.9.1.el10_0.x86_64': Invalid cross-device link
    exit=1 - /boot is a separate filesystem

    An inode number is only unique *within one filesystem*, so a directory entry on / cannot reference an inode on /boot. "Invalid cross-device link" is the error, and it is worth recognising because it looks like a permissions problem and is not.

    A symlink has neither restriction:

    lrwxrwxrwx. 1 sysadmin sysadmin 3 ... dirsoft -> one

    It is only a path. It does not care what is at the other end, or whether anything is.

    bash Example session
    cd ~/fl && ln one dirlink 2>&1; echo "exit=$? - hard links to directories are refused"ln: one: hard link not allowed for directoryexit=1 - hard links to directories are refusedcd ~/fl && ln /boot/vmlinuz-$(uname -r) cross.txt 2>&1 | head -2; echo "exit=$? - /boot is a separate filesystem"ln: failed to create hard link 'cross.txt' => '/boot/vmlinuz-6.12.0-55.9.1.el10_0.x86_64': Invalid cross-device linkexit=0 - /boot is a separate filesystemcd ~/fl && ln -s one dirsoft && ls -ld dirsoft && ls dirsoft/lrwxrwxrwx. 1 sysadmin sysadmin 3 Aug 23 10:55 dirsoft -> onetwo

    Expected resultTwo refusals for hard links, and a symlink doing both.

    Success conditionYou know when only a symlink will work.

  5. Counting names, and finding them

    link count: 3

    Three names for one file. Remove one:

    link count after rm: 2

    The count is the number of directory entries. When it reaches zero the space is freed.

    To find every name a file has, search by inode:

    ./counted.txt
    ./c3.txt

    find . -inum is the only way - there is no list of names on the file itself, only the count. That is the practical limitation of hard links: ls -l tells you a file has three names but not where they are, and if they are on the other side of the filesystem you will not find them without searching the whole thing.

    bash Example session
    cd ~/fl && touch counted.txt && ln counted.txt c2.txt && ln counted.txt c3.txt && ls -l counted.txt | awk '{print "link count:", $2}'link count: 3cd ~/fl && rm c2.txt && ls -l counted.txt | awk '{print "link count after rm:", $2}'link count after rm: 2cd ~/fl && find . -inum $(stat -c %i counted.txt) 2>/dev/null./counted.txt./c3.txt

    Expected resultA link count changing, and both names found by inode.

    Success conditionYou can find every name pointing at one file.

Troubleshooting

Official sources