Memra

A pipe with a name, and a umask interaction

◈ 9 cards

Create and use a FIFO from C, say which half of it survives the processes using it, and explain why ls -l reports size 0 no matter how much you write.

The problem a FIFO solves

A pipe has no name. The only way a second process can obtain one is to inherit it across fork, which means pipes work between related processes and nowhere else. Two programs written by two people, started from two terminals, cannot use a pipe at all — there is nothing for either of them to name.

A FIFO, also called a named pipe, fixes exactly that and nothing else. It is a pipe with a pathname, so any process that can see the pathname and has permission on it can open the channel. Everything else — one direction, kernel buffer, no seeking, read returning 0 at EOF, SIGPIPE on a widowed write — is identical to an ordinary pipe.

Creating one

int mkfifo(const char *path, mode_t mode);

It returns 0 on success and −1 on failure, with EEXIST if something is already at that pathname. path is where the FIFO will appear in the filesystem; mode is the permission bits you are asking for, in the usual octal.

And here is the detail that catches everybody, because it is a callback to Module 4: the mode you ask for is not the mode you get. The kernel filters it through the process’s umask, exactly as it does for any file created by open or creat. With the common umask of 022:

requested  0666   rw-rw-rw-
umask      0022   ----w--w-
result     0644   rw-r--r--

The arithmetic is mode & ~umask. If your reader runs as a different user in the same group and the FIFO comes out 0644, the reader can read it but a second writer in the group cannot write it, and the program fails with EACCES for a reason that has nothing to do with FIFOs. Either set the umask before mkfifo, or chmod the FIFO afterwards.

After creation, ls -l shows the file type letter p in the first column, and the shell command mkfifo does the same job from a script.

Using one

There is no mkfifo-specific I/O. You open the pathname, read or write the descriptor, and close it, exactly like a file:

int fd = open("/tmp/chan", O_WRONLY);
write(fd, "fourteen bytes", 14);
close(fd);

With one behavioural difference that surprises everyone the first time. open on a FIFO blocks until the other end is opened too. Opening for reading blocks until some process opens it for writing, and opening for writing blocks until some process opens it for reading. That is a feature: it is the rendezvous, and it is what lets two unrelated programs start in either order. (Opening with O_NONBLOCK changes it: a read-open returns immediately, and a write-open with no reader fails with ENXIO.)

The two persistences — and the proof

A FIFO is an amalgam of two things with different lifetimes, and separating them is the whole of the exam question:

  • The name — the pathname and its inode — is a filesystem object. It is created by mkfifo and it survives every process that ever touches it. It is filesystem-persistent: still there after a reboot, and it goes away only when someone calls unlink (or runs rm).
  • The buffer — the actual bytes — is kernel memory. It is created when a process first opens the FIFO and destroyed when the last process closes it. It is process-persistent, and it never touches the disk.

The demonstration is simple and worth running. Create a FIFO and an empty regular file. Write fourteen bytes into each. Then look:

$ ls -l chan data
prw-r--r--  1 you you   0 Sep  2 11:04 chan
-rw-r--r--  1 you you  14 Sep  2 11:04 data

The regular file reports 14 bytes. The FIFO reports 0, and du on it does not move either, no matter how much traffic passes through. The conclusion is not that the write failed — the reader received all fourteen bytes — but that the data never went near the disk object. The directory entry is a rendezvous point; the bytes lived in memory the whole time.

Cleaning up

The name outlives your program, so your program must remove it:

unlink("/tmp/chan");

Skip that and you leave a FIFO lying in /tmp forever, and the next run of your program gets EEXIST from mkfifo. Tolerating EEXIST on creation and calling unlink when finished is the pattern to write. The same applies to a PF_LOCAL socket, which is also a name in the filesystem that nothing removes for you.

Half of theFIFOWhere it livesCreated byDestroyed byPersistencethe name(pathname,inode)the filesystemmkfifo()unlink()filesystemthe buffer(the bytes)kernel memorythe firstopen()the lastclose()processls -l always reports size 0: the bytes never reach the disk object.
Two halves, two lifetimes — which is why ls -l can report size 0 for a channel that has just carried fourteen bytes.
NORMAL ~/memra/learn/comp-325/fifos-from-c utf-8 LF