Skip to content

open · close · read · lseek

Which of the three levels is this bug in?

A descriptor, an open file description and an inode are three different things. Almost every interesting bug here is a confusion between two of them, and no diagram in a man page tells them apart.

Three objects, not one

A number is not a file. When you call open, three things are involved and they are worth naming separately, because everything confusing about descriptors is a confusion between two of them.

The descriptor is the number. It lives in a small table belonging to your process, and it holds almost nothing: a pointer, and one flag saying whether to keep it across exec.

The open file description is what it points at. This is where the cursor lives — the offset your next read starts from — and where O_APPEND and O_NONBLOCK live. It is shared, and that is the whole story.

The inode is the file. Its contents, its permissions, its link count. Two processes with two descriptors on two descriptions of one file are looking at the same bytes through two independent cursors.

The man pages call the middle one an “open file description” and the top one a “file descriptor”. They differ by one word and are not the same object.

three-levels

Opening the same file twice

Two open calls on one path give you two descriptors, two descriptions and one inode. Nothing is shared but the file itself, so each has its own cursor and reading through one does not move the other.

This is what people expect, and it is why the other cases surprise them.

Try this
#include <fcntl.h>
#include <unistd.h>

int main(void) {
  char buf[4];
  int a = open("/work/data", O_RDONLY);
  int b = open("/work/data", O_RDONLY);
  read(a, buf, 4);
  read(b, buf, 4);
  return 0;
}

Watch: The two reads in the trace, and the POS column for each description in the Descriptors pane.

three-levels

Before you run it

Now the same program, but the second descriptor comes from dup2 rather than from a second open.

Predict

After dup2(fd, 7), what does read(fd, buf, 4) followed by read(7, buf, 4) read?

three-levels

`close` on one of two closes nothing

The description survives while any descriptor anywhere still names it. So closing one of a duplicated pair does not close the file, does not flush anything, and does not release the inode — it removes one table entry.

A program that "closed the file" and then finds it still open has usually duplicated it somewhere it forgot about. The commonest place is fork.

Try this
#include <fcntl.h>
#include <unistd.h>

int main(void) {
  char buf[4];
  int fd = open("/work/data", O_RDONLY);
  dup2(fd, 7);
  close(fd);
  return read(7, buf, 4);
}

Watch: That the read succeeds after the close.

three-levels

Trace

Run one of the programs on the left.

Everything you do here stays in this browser.Part of liter8.sh