. and ..

Let’s create a working directory using -p switch of the mkdir command to create all nested directories at once and navigate to b:

$ mkdir -p a/b/c
$ cd b

Now let’s list the content of b:

alper@brs23-2204:~/a/b$ ls -l

total 4
drwxrwxr-x 2 alper alper 4096 Jul 13 14:20 c

We see only c as expected. But now, let’s use -a option of the ls command.

alper@brs23-2204:~/a/b$ ls -la

total 12
drwxrwxr-x 3 alper alper 4096 Jul 13 14:20 .
drwxrwxr-x 3 alper alper 4096 Jul 13 14:20 ..
drwxrwxr-x 2 alper alper 4096 Jul 13 14:20 c

Now, we see additional 2 entries: . and .. What are those?

The -a stands for all. As a convention, directories and files starting with . are considered hidden on Linux. Most of the configuration files at home directories begin with .. Hidden files aren’t different from our well-known regular files, it is just a convention among Linux distributions. By default, many file utilities like ls command or GUI based file browsers hide those directories and files. Here is the explanation from the ls manual:

-a, --all
       do not ignore entries starting with .

Okay, we know that there are two additional hidden entries in b and they are directories (entries with type d) too, similar to c. But where do they point to? The best way to understand is to examine their inode numbers. Let’s do:

alper@brs23-2204:~/a/b$ ls -lai

total 12
8520988 drwxrwxr-x 3 alper alper 4096 Jul 13 14:20 .
8520987 drwxrwxr-x 3 alper alper 4096 Jul 13 14:20 ..
8520989 drwxrwxr-x 2 alper alper 4096 Jul 13 14:20 c

alper@brs23-2204:~/a$ ls -lai
total 12

8520987 drwxrwxr-x  3 alper alper 4096 Jul 13 14:20 .
3149103 drwxr-x--- 38 alper alper 4096 Jul 13 14:20 ..
8520988 drwxrwxr-x  3 alper alper 4096 Jul 13 14:20 b

alper@brs23-2204:~$ ls -laid a
8520987 drwxrwxr-x 3 alper alper 4096 Jul 13 14:20 a

The numbers on each row are the inode numbers for the corresponding entries. The inode number of directory the a is 8520987 on my system. You might have been noticed that the inode number of . in a and .. in b are also the same. That means that they are hard links to directory a and this is what . and .. are really mean.


Each directory on Linux has two directory entries called . and ... Those are automatically created when we create a new directory. Similarly, they are automatically removed when we remove a directory. They are hard links to existing directories. . is a hard link to its own directory and .. is a hard link to parent, in other words to top, directory.

alper@brs23-2204:~$ cd a/b

alper@brs23-2204:~/a/b$ cd ./././././././
alper@brs23-2204:~/a/b$

For example, if you cd into . repeatedly, you will end up in the same directory.


In order to expand the example, I created 3 directories under c. Now, let’s examine the meaning of hard links for directories.

alper@brs23-2204:~$ find a -type d -exec ls -ld {} + | awk '{print $2, $9}'

3 a
3 a/b
5 a/b/c
2 a/b/c/d
2 a/b/c/e
2 a/b/c/f

Numbers at each row show number of hard links given to the corresponding directory. If you remember, we saw that we can’t create hard links to directories. We are only allowed to create soft links or symlinks to directories. The only possible hard links to directories are . and .. entries which are automatically created by the kernel. Thus, kernel is allowed to create hard links to directories but even the root user can’t create them with ln command.

Let’s try to understand the numbers:

../_images/dot-dot-dot.png

Since a is not the root directory, /, it exists under a not-shown directory.

Hard links are essentially the number of links pointing to that directory. Let’s consider b. b is under a and there is an entry like b-<inode number> in a, this is 1. Now . in b points to b, link count is 2. Lastly, .. in c points to b and this is why hard link count of b is 3. Since each child directory points to its parent directory, c has the highest number of hard link.


You might be wondering that how .. directory of the root directory / is implemented. Since / is the root directory, it doesn’t have a parent. Therefore, /.. points to /. /.., /. and / points to the same directory with inode number 2.

alper@brs23-2204:/$ ls -lid . ..

2 drwxr-xr-x 20 root root 4096 Apr 19  2023 .
2 drwxr-xr-x 20 root root 4096 Apr 19  2023 ..

Are they virtual or real?

As you can see that . and .. are kind of virtual entries. However, most of the file systems store these entries on the disk physically. Directories are list of name - inode pairs. In our example, the only real entry in b is directory c. We have also . and ... Those entries are also stored on the disk along with c like that:

content of b:

.  - inode of b
.. - inode of a
c  - inode of c

If you try to create . and .., you will get an error:

alper@brs23-2204:~/a/b$ mkdir .
mkdir: cannot create directory ‘.’: File exists

alper@brs23-2204:~/a/b$ mkdir ..
mkdir: cannot create directory ‘..’: File exists

According to my research (ChatGPT, not completely sure especially for non-ext file systems), file systems like ext2/3/4, FAT32, Btrfs and ZFS store . and .. entries on the disk physically while exFAT, NTFS don’t. However, if you mount those file systems on Linux you will see . and ... How? Because Virtual File System (VFS) layer creates them virtually. NTFS doesn’t have Linux concepts like rwx or user:group permissions naturally. Those transformations are created by NTFS driver while VFS is responsible for creating unified file system view from the user perspective by creating . and .., if necessary.

But why?

At first glance, we can think that storing . and .. entries on the disk physically is waste of space. As the kernel traverse the file system, it could potentially create them virtually. Although this seems to be technically possible, storing them on the disk isn’t a bad idea at all.

File systems are tree-like data structures stored on disks. Disks are prone to errors due to power losses or internal errors. Storing . and .. on the disk increases success ratio of file recovery tools like fsck. Because those entries are sort of duplicate pointers pointing to current and parent directories. This duplication creates a backup. Even if some part of the disk is corrupted with this duplication, fsck can recreate the data structure.

If the kernel had to create . and .. virtually all the time, we might observe a performance penalty. Also storing them on the disk causes a little bit space inefficiency, they are stored on the disk efficiently as much as possible by the file systems. Having physically . and .. entries on the disk allow fast directory traversal since kernel can read those entries from the disk directly.