22

This is a bit of a theoretical question, but it's important to use proper names for things.

In UNIX/Linux file systems, .. points to the parent directory.

However, we know that hard links cannot point to directories, because that has the potential to break the acyclic graph structure of the filesystem and cause commands to run in an endless loop.

So, is .. really a hard link (like .)? That would make it a special type of hard link, not subject to the directory restriction, but which for all purposes behaves like one.

Or is that a special inode mapping, hardcoded into the filesystem, which ought not be called a hard link?

dr_
  • 29,602
  • 2
    Yes, this is an exception. Try ls -ld /usr you will see a number after the access rights: that's the number of hard-links. (eg: drwxrwxr-x 17 root root 4096 Apr 6 17:09 /usr) – Lorinczy Zsigmond Jun 07 '23 at 08:26
  • 1
    "Hard links cannot point to directories." That's a file-system-specific restriction, not a fundamental limit. HFS+ allows for directory hard links. (Support was dropped from APFS, so whether such support is a good idea is another matter.) – chepner Jun 08 '23 at 14:40
  • 1
    If .. made it a cyclic graph, then . would also, since a node that points to itself is a cycle, no? – David Conrad Jun 08 '23 at 17:48
  • 2
    In old UNIX systems, root could mess with . and .., making the filesystem inconsistent. Also ls -lid .. will show the link count and the number of the inode (among other tinformation). – U. Windl Jun 09 '23 at 07:47

5 Answers5

38

It depends on the filesystem. Most filesystems follow the traditional Unix design, where . and .. are hard links, i.e. they're actual directory entries in the filesystem. The hard link count of a directory is 2 + n where n is the number of subdirectories: that's the entry in the directory's parent, the directory's own . entry, and each subdirectory's .. entry. The hard link count is updated each time a subdirectory is created, removed or moved in or out of the directory. See Why does a new directory have a hard link count of 2 before anything is added to it? for a more detailed explanation.

A few filesystems deviate from this tradition, in particular btrfs.

we know that hard links cannot point to directories

This is imprecise wording. More precisely, you can't create a hard link to a directory using the ln utility or the link system call or a similar method, because the kernel prevents you. Calling mkdir does create a hard link to the parent of the new directory. It's the only way to create a new hard link to a directory on a filesystem (and conversely removing a directory is the only way to remove a hard link to a directory).

Also, note that it's misleading to think of hard links in terms of “pointing to” a primary file. Hard links are not directional, unlike symbolic links. When a file has multiple hard links, they're equivalent. After the following sequence:

mkdir a b
touch a/file
ln a/file b/file

there is nothing in the filesystem that makes b/file secondary to a/file. The two directory entries both refer to the same file. They're both hard links to the file.

  • 11
    Hard links are directional in the sense that they point from a directory entry (name) to an inode. – ilkkachu Jun 07 '23 at 11:50
  • How about this: A symlink is a directory entry that points to another directory entry, and thus relies on the existence of that other directory entry to find the inode. A hard link is a directory entry that points to an inode. It doesn't rely on any other directory entries to find the inode, meaning it's completely independent of the existence of other directory entries that point to the same inode. As such, there's no primary and secondary names when it comes to hard links; every name is equal and independent. – ikegami Jun 07 '23 at 17:14
  • 5
    @ikegami A symlink points to a path, not to a directory entry. It exists independently of the potential file it points to. For example, with a removable drive, the same symlink can point to different files at different times (and is dangling while the drive isn't mounted or if the mounted drive doesn't have a file at that particular location). Correct for hard links: “hard link” is really a fancy name for a directory entry when there happens to be more than one directory entry pointing to the same inode. – Gilles 'SO- stop being evil' Jun 07 '23 at 17:31
  • 1
    @ikegami: Every directory entry has an inode (including symlinks BTW, that's where the permission, mod-time, and perhaps target path of the symlink itself is stored). So every entry is a link from dirent to inode. creating a new file creates an inode and makes a link to it, i.e. links it into the directory structure, the opposite of the unlink system call which "deletes" files. Symlinks are a different kind of inode, but the special things about a hard link are only the creation process (using another path to find an inode to link, instead of creating a new one), and link count > 1. – Peter Cordes Jun 07 '23 at 21:17
  • 1
    IDK if my very low-level description is always the best way to think about hard / symbolic links. Probably for some people, there's a useful level of "lies to students" simplifications that might be easier to understand, if they haven't and don't want to fully grok all of those Unix filesystem concepts and terminology. And a way of thinking about them that allows correct reasoning in at least simple cases (like no hard-linked symlinks) without mentally sorting through up all the details, if that's not how they like to think about files. – Peter Cordes Jun 07 '23 at 21:19
  • 1
    @ikegami: Your comment isn't wrong, and is probably a useful way to think about it. But "A hard link is a directory entry that points to an inode" is somewhat vacuous because that's true for every directory entry. (That's what inspired my comment, plus wanting to take a stab at describing it myself. It's not all meant as a correction aimed at you, but I ran out of chars to say so, my apologies.) Your description isn't wrong, though, and is a way to describe the fact there's no other indirection going on, unlike a symlink. – Peter Cordes Jun 07 '23 at 23:40
  • @ikegami: The dir ent vs. path distinction: to me, a directory entry is an internal data structure in a filesystem (e.g. on disk or in memory), or in the array returned by readir / getdents. A "link to a directory entry" could perhaps imply it only works inside a single filesystem, and that it doesn't go through the OS's path lookup and thus wouldn't go into mount points the way a normal path does. But symlinks do treat paths the same as system calls. It's a fairly minor terminology nitpick, but I can see Gilles' point. – Peter Cordes Jun 07 '23 at 23:48
  • Please explain their MO at a mount point. – Bib Jun 08 '23 at 20:14
  • 1
    @Bib That's not really relevant here but I've added a paragraph to my reference answer on the topic. – Gilles 'SO- stop being evil' Jun 08 '23 at 20:46
  • Note that .. being present in the filesystem makes it impossible for "When a file has multiple hard links, they're equivalent" to hold for the special files we call directories. If you have a directory with names /a/b, /a/c, and /d/e, then /a/b is equivalent to /a/c, but /d/e is in an entirely separate equivalence class. – Ben Voigt Jun 09 '23 at 20:47
10

This is an implementation detail.

In POSIX conforming filesystems, .. acts like a hard link.

However, some filesystems only emulate this and don't actually store it on disk.

For functional purposes, the distinction is moot, assuming you don't have filesystem corruption. (If you do, fsck is designed to fix this.)

One of the non-posix failures to fully emulate hard links is for the hardlink count to not indicate the correct number of subdirectories inside a directory. The find option -noleaf option disables an optimization that uses this link count.

user10489
  • 6,740
  • Yup, best answer yet. The problem with the explanation of them being hard links, is that it fails at mount point junctions. – Bib Jun 08 '23 at 20:13
  • 2
    Hard links can't cross mount points. So the OS has to handle that specially anyway as part of it being a mount point. – user10489 Jun 08 '23 at 22:40
8

As a supplementary to Gilles's answer I think it's worth drawing attention to these words and expanding on them:

Also, note that it's misleading to think of hard links in terms of “pointing to” a primary file.

It's really important to understand the conceptual model of files in linux where every name inside a directory is a "link" pointing to an "inode" which may point to data.

What's confusing here is the naming convention of "Hard Link" and "Symbolic Link" sounding like two flavours of the same thing. They are not!

A regular file is:

link -> x 
        | Inode -> Data

A "hard linked file" is no different:

link_a -> x
          | Inode -> Data
link_b -> x

But a symbolic link is:

original_file_name -> x
                      | Inode -> Data

sym_link_name -> x | Inode -> File name "original_file_name"

So why can't you hardlink directories?

The best explanation I've come from is derived from this. The .. entry must be maintained within the directory. If you had more than one single name pointing at the directory itself, you could have more than one parent and this would be impossible because .. can only exist once, like every other possible name can only exist once inside a directory.

  • 3
    Even disregarding .., if we would allow creating (new) hard links to directories as easily as to files, the OS would need to take care to disallow directories from getting stranded due to loops in graph. With a loop in place, removing any links to the loop from the outside would leave the looped directories in a limbo: not removed since they'd have a non-zero link count, but also with no way to access them. And a loop could happen not just when a link is created, but also when a directory is moved, so it's easier to just forbid links to directories in the first place. :) – ilkkachu Jun 07 '23 at 13:59
  • 1
    @ilkkachu That's what-if territory. Most of that couldn't happen specifically because of the existence of .. and the cause & effect that follows from it. But if .. did not exist then there are a fair few things that would break. Shells would not survive the parent of CWD being renamed or otherwise they could never cd .. by any implementable means. However scanning trees for loops is not complex. It does require an exploration of the full tree, but not much more than that. – Philip Couling Jun 07 '23 at 14:27
  • 1
    sure, it'd require changes to the way .. works now. And no, it's hard to detect loops as such, just that it'd make directory moves much less local: instead of just looking at the two involved directories, the system would need to track a basically arbitrary number of directories to either check for a loop or for reachability to the root. (well, okay, it already has to check that a directory isn't moved inside itself.) – ilkkachu Jun 07 '23 at 15:23
  • @PhilipCouling: I built that as part of my master's thesis. It works just fine. You actually don't need to scan the full tree to detect cycles either; estimates range from trivial to most depending on what the user tried to do. Turns out the kernel synthesizes part of .. and no matter what the FS gives it, .. always refers to the parent directory on the path that was taken to the directory (ignoring symlinks). – Joshua Jun 08 '23 at 14:14
  • @Joshua Interesting. Not sure how you could prove there are 0 cycles without scanning every in the tree, because you would need to confirm that none of the new parents exist anywhere in the subtree.... and ofcourse if you change .. to always refer to the path that was taken into the directory it would fundamentally change the behaviour such that renames were effectively ignored when a client attempted to cd ... I doubt most apps would care about that though. – Philip Couling Jun 08 '23 at 16:19
  • @PhilipCouling: Renames aren't ignored on cd .. and it tracked via the path you descended through, and I found this to be pre-existing code in the kernel. – Joshua Jun 08 '23 at 16:23
  • @Joshua I might need to double check behavour. What I believe is that the shell's CWD is held open and AFAIK https://linux.die.net/man/3/scandirat is used to go up one (not using the CWD string). The chain of .. back to / changes when some grandparent directory is moved. What you've described is building that chain on the way in. By what mechanism would moving the grandparent modify that pre-built chain? – Philip Couling Jun 08 '23 at 16:28
  • @PhilipCouling: It's stored in-kernel as an array of nodes rather than a string. The move directory operation updates the arrays. As you noted, the shell doesn't actually resolve .. on executing cd .. so it can unwind through symbolic links. – Joshua Jun 08 '23 at 16:31
6

. and .. are Special Hard Links to Inodes

From a purely pragmatic point of view, .. is a hard link. On Linux, it gets treated as a hard link to the mount point when called from the top level of a mounted volume, and on Linux and macOS / hard-links both . and .. to inode 2 as a special case.

More Detail

As others have pointed out, . and .. are both hard links to inodes on filesystems that support inodes. There are a few filesystems that don't have inodes, but that's a bit of a side issue. For practical purposes, whether a system directly supports inodes or not, a hard link just points to the same metadata block on a given filesystem. Sometimes the hard link is a real link on the filesystem, and sometimes it's synthesized because POSIX systems expect . and .. to exist for historical reasons. Either way, it's treated as a hard link to directories, and some filesystems such as HFS+ do allow you to hard link directories that don't cross filesystem boundaries.

Hard Linking of Directories

MacOS does allow hard links on HFS+ file systems to point to directories, because this was historically the way Time Machine optimized disk usage. However, newer versions of Time Machine use APFS filesystems and filesystem snapshots, so there's really no need for this exception except backwards compatibility, and it doesn't work on APFS. Don't rely on it.

So, it's generally the filesystem itself that determines whether or not a directory can be hard-linked or not. On Ubuntu 22.04, man 1 ln says the ln utility supports directory links if the system allows it:

       -d, -F, --directory
              allow  the  superuser to attempt to hard link directories (note:
              will probably fail due to system restrictions, even for the  su‐
              peruser)

The part that probably confuses most people is mount points. If you have a disk mounted as /mnt/foo, then cd /mnt/foo; ls -ld .. points to the inode of the mount point, which in this case would be the inode for /mnt. Either the hard link for the .. directory on a mount doesn't really cross filesystem boundaries because it's just looking at the hard link of /mnt/foo (which exists whether or not a volume is currently mounted there) or it gets treated specially by the kernel to support mounted volumes. This is most likely an implementation detail and I can't point to any kernel source related to it, but functionally it's still technically a hard link because .. points to an inode rather than a soft link pointing to a file name.

You can test this for yourself with:

cd /
stat .
stat ..

On Linux, and on macOS when called with the -x flag, you will see that both . and .. consistently point to inode 2. Many filesystems reserve inode 1 for bad blocks.

Since you can create hard links to directories on some filesystems, including EXT3/4 with debugfs, it's not really true that you can't hard link directories. It's generally not supported in order to ensure the directory graph is acyclic, and tools like fsck will complain about hard-linked directories other than . and .. on filesystems that don't support directory hard links for anything other than the current working directory (.) and parent directory (..) as a special legacy case.

CodeGnome
  • 7,820
  • What are you trying to say by "hard links to inodes"? That sounds like there are some other hard links that don't point to inodes, which sounds really odd (what else could a hard link point to?). – TooTea Jun 08 '23 at 13:57
  • 1
    TooTea: I think that @CodeGnome wanted just to emphasize that hard links point to inodes, not that there are other kinds of hard links pointing to something else. – dr_ Jun 08 '23 at 14:43
  • . and .. are special insofar as the filesystem checking program knows their special role: Assume you create something like ln . junk and then use find to locate some file: find may run into junk/junk/junk/junk/.... The filesystem checker makes sure that the hierarchical filesystem still is a tree (e.g. contains no loops), and find also knows the special roles of . and .. when traversing the filesystem hierarchy. – U. Windl Jun 09 '23 at 07:56
1

So, is .. really a hard link (like .)?

There is no such thing (object) as hard link.

None of the filenames (including . or ..) can be a hard link; and none of the filenames (including . or ..) can be a non hard link. That's because hard link is not a property like this.

Hard link is the abstract concept of more than 1 directory listing entry pointing to the same object (inode). It's the concept of refcounting, and that refcount possibly being larger than 1.

And indeed, . and .. are part of this game, they always point to an object that is pointed to from elsewhere, too; i.e. always have a refcount of at least 2.

egmont
  • 5,866