In this chapter, we conclude our discussion of file-related topics by looking at directories and links. After an overview of their implementation, we describe the system calls used to create and remove directories and links. We then look at library functions that allow a program to scan the contents of a single directory and to walk through (i.e., examine each file in) a directory tree.
Each process has two directory-related attributes: a root directory, which determines the point from which absolute pathnames are interpreted, and a current working directory, which determines the point from which relative pathnames are interpreted. We look at the system calls that allow a process to change both of these attributes.
We finish the chapter with a discussion of library functions that are used to resolve pathnames and to parse them into directory and filename components.
A directory is stored in the file system in a similar way to a regular file. Two things distinguish a directory from a regular file:
A directory is marked with a different file type in its i-node entry (I-nodes).
A directory is a file with a special organization. Essentially, it is a table consisting of filenames and i-node numbers.
On most native Linux file systems, filenames can be up to 255 characters long. The relationship between directories and i-nodes is illustrated in Figure 18-1, which shows the partial contents of the file system i-node table and relevant directory files that are maintained for an example file (/etc/passwd
).
Although a process can open a directory, it can’t use read() to read the contents of a directory. To retrieve the contents of a directory, a process must instead use the system calls and library functions discussed later in this chapter. (On some UNIX implementations, it is possible to perform a read() on a directory, but this is not portable.) Nor can a process directly change a directory’s contents with write(); it can only indirectly (i.e., request the kernel to) change the contents using system calls such as open() (to create a new file), link(), mkdir(), symlink(), unlink(), and rmdir(). (All of these system calls are described later in this chapter, except open(), which was described in Section 4.3.)
The i-node table is numbered starting at 1, rather than 0, because 0 in the i-node field of a directory entry indicates that the entry is unused. I-node 1 is used to record bad blocks in the file system. The root directory (/) of a file system is always stored in i-node entry 2 (as shown in Figure 18-1), so that the kernel knows where to start when resolving a pathname.
If we review the list of information stored in a file i-node (I-nodes), we see that the i-node doesn’t contain a filename; it is only the mapping within a directory list that defines the name of a file. This has a useful consequence: we can create multiple names—in the same or in different directories—each of which refers to the same i-node. These multiple names are known as links, or sometimes as hard links to distinguish them from symbolic links, which we discuss shortly.
All native Linux and UNIX file systems support hard links. However, many non-UNIX file systems (e.g., Microsoft’s VFAT) do not. (Microsoft’s NTFS file system does support hard links.)
From the shell, we can create new hard links to an existing file using the ln command, as shown in the following shell session log:
$echo -n 'It is good to collect things,' > abc
$ ls -li abc 122232 -rw-r--r-- 1 mtk users 29 Jun 15 17:07 abc $ln abc xyz
$echo ' but it is better to go on walks.' >> xyz
$cat abc
It is good to collect things, but it is better to go on walks. $ls -li abc xyz
122232 -rw-r--r-- 2 mtk users 63 Jun 15 17:07 abc 122232 -rw-r--r-- 2 mtk users 63 Jun 15 17:07 xyz
The i-node numbers displayed (as the first column) by ls -li confirm what was already clear from the output of the cat command: the names abc
and xyz
refer to the same i-node entry, and hence to the same file. In the third field displayed by ls -li, we can see the link count for the i-node. After the ln abc xyz command, the link count of the i-node referred to by abc
has risen to 2, since there are now two names referring to the file. (The same link count is displayed for the file xyz
, since it refers to the same i-node.)
If one of these filenames is removed, the other name, and the file itself, continue to exist:
$rm abc
$ls -li xyz
122232 -rw-r--r-- 1 mtk users 63 Jun 15 17:07 xyz
The i-node entry and data blocks for the file are removed (deallocated) only when the i-node’s link count falls to 0—that is, when all of the names for the file have been removed. To summarize: the rm command removes a filename from a directory list, decrements the link count of the corresponding i-node by 1, and, if the link count thereby falls to 0, deallocates the i-node and the data blocks to which it refers.
All of the names (links) for a file are equivalent—none of the names (e.g., the first) has priority over any of the others. As we saw in the above example, after the first name associated with the file was removed, the physical file continued to exist, but it was then accessible only by the other name.
A question often asked in online forums is “How can I find the filename associated with the file descriptor X in my program?” The short answer is that we can’t— at least not portably and unambiguously—since a file descriptor refers to an i-node, and multiple filenames (or even, as described in Creating and Removing (Hard) Links: link() and unlink(), none at all) may refer to this i-node.
On Linux, we can see which files a process currently has open by using readdir() (Reading Directories: opendir() and readdir()) to scan the contents of the Linux-specific /proc/
PID/fd
directory, which contains symbolic links for each of the file descriptors currently opened by the process. The lsof(1) and fuser(1) tools, which have been ported to many UNIX systems, can also be useful in this regard.
Hard links have two limitations, both of which can be circumvented by the use of symbolic links:
Because directory entries (hard links) refer to files using just an i-node number, and i-node numbers are unique only within a file system, a hard link must reside on the same file system as the file to which it refers.
A hard link can’t be made to a directory. This prevents the creation of circular links, which would confuse many system programs.
Early UNIX implementations permitted the superuser to create hard links to directories. This was necessary because these implementations did not provide a mkdir() system call. Instead, a directory was created using mknod(), and then links for the .
and ..
entries were created ([Vahalia, 1996]). Although this feature is no longer needed, some modern UNIX implementations retain it for backward compatibility.
An effect similar to hard links on directories can be achieved using bind mounts (Bind Mounts).