Why does Linux use getdents() on directories instead of read()?

Viewed 7408

I was skimming through K&R C and I noticed that to read the entries in a directories, they used:

while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf))
    /* code */

Where dirbuf was a system-specific directory structure, and dp->fd a valid file descriptor. On my system, dirbuf would have been a struct linux_dirent. Note that a struct linux_dirent has a flexible array member for the entry name, but let us assume, for the sake of simplicity, that it doesn't. (Dealing with the flexible array member in this scenario would only require a little extra boilerplate code).

Linux, however, doesn't support this construct. When using read() to try reading directory entries as above, read() returns -1 and errno is set to EISDIR.

Instead, Linux dedicates a system call specifcally for reading directories, namely the getdents() system call. However, I've noticed that it works in pretty much the same way as above.

while (syscall(SYS_getdents, fd, &dirbuf, sizeof(dirbuf)) != -1)
    /* code */

What was the rational behind this? There seems to be little/no benefit compared to using read() as done in K&R.

3 Answers

Your suspicion is correct: It would make more sense to have the read system call work on directories and return some standardized data, rather than have the separate getdents system call. getdents is superfluous and reduces the uniformity of the interface. The other answers assert that "read" as an interface would be inferior in some way to "getdents". They are incorrect. As you can observe, the arguments and return value of "read" and "getdents" are identical; just "read" only works on non-directories and "getdents" only works on directories. "getdents" could easily be folded into "read" to get a single uniform syscall.

The reason this is not the case is historical. Originally, "read" worked on directories, but returned the actual raw directory entry in the filesystem. This was complex to parse, so the getdirents call was added in addition to read, to provide a filesystem-independent view of directory entries. Eventually, "read" on directories was turned off. "read" on directories just as well could have been made to behave identically to getdirents instead of being turned off. It just wasn't, possibly because it seemed duplicative.

In Linux, in particular, "read" has returned an error when reading directories for so long that it's almost certain that some program is relying on this behavior. So, backwards compatibility demands that "read" on Linux will never work on directories.

Related