4

I've just read this:

What does the FD_CLOEXEC fcntl() flag do?

and I understand what FD_CLOEXEC does, but not why it's important. Why not just close all the relevant file descriptors before exec()ing something? And why is it so critical to set FD_CLOEXEC that there's a special O_CLOEXEC open mode flag you can set (on Linux 2.6.something and later), only to avoid a bit of inter-thread synchronization?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • Because (for example) when writing the exec part of the code you may not know every file descriptor currently in use? – Jean-Baptiste Yunès Jul 29 '17 at 11:19
  • @Jean-BaptisteYunès: So the OS provides these facilities just because processes can't be bothered to control their threads? I dunno, sounds unconvincing somehow. – einpoklum Jul 29 '17 at 11:28
  • This has nothing to do with threading only process management. File descriptors are inherited through exec, you may not want it when opening a file, so use `FD_CLOEXEC` not to put the burden on who writes the execing part of the code. – Jean-Baptiste Yunès Jul 29 '17 at 11:31
  • @Jean-BaptisteYunès: So just close all the file descriptors you don't trust to keep open. – einpoklum Jul 29 '17 at 12:07
  • Again, why do you think you always know which descriptors are allocated at the point you tried to exec? – Jean-Baptiste Yunès Jul 29 '17 at 12:11
  • @Jean-BaptisteYunès: Can't I just check? At worst, you could do look at `/proc/self/fd`. – einpoklum Jul 29 '17 at 12:22
  • `/proc` is not supported on every *nix flavor. And checking is not always possible, given a descriptor you didn't opened by yourself, how can you determine that it should be closed or not? – Jean-Baptiste Yunès Jul 29 '17 at 12:23
  • @Jean-BaptisteYunès: We're talking about Linux here... ; also - if you're going to `exec()` something - everything you don't specifically want to keep open can be closed. Remember, I'm asking for a _compelling_ use case here. Being clueless as a process is not very compelling. – einpoklum Jul 29 '17 at 12:26
  • Here's a use case. gdb lets you add extensions using python, and in python you can use the `subprocess` package to call external Unix commands, or that package may be called implicitly by other packages without your knowing it. To prevent file descriptor leakage, gdb carefully sets `CLOEXEC` on all the fd's it uses internally, so the extension writer doesn't have to worry. – Mark Plotnick Jul 29 '17 at 14:29
  • Another example: `popen` doesn't give you the chance to close any fd's. – Mark Plotnick Jul 29 '17 at 15:35
  • @MarkPlotnick: But the `fork()` is already bad enough... you can do whatever crazy s**t you want with your python code. The `popen` example I can sort of buy, though you could always manipulate the command string so it closes the file descriptors you don't want opened. – einpoklum Jul 29 '17 at 16:49
  • @MarkPlotnick: So, are you saying O_CLOEXEC affects forking, not just exec'ing? – einpoklum Jul 29 '17 at 17:20
  • If you don't use the CLOEXEC open flag and instead use regular open and then fcntl to set CLOEXEC, there is an effect - it's that race condition you alluded to. If another thread calls fork (or another function that calls fork, such as popen, system, etc.) between the open and fcntl, you might get an fd in the child that hasn't had CLOEXEC applied to it. You can surround the open-fcntl sequence with a lock, and have other threads lock before fork, system, popen, etc., but system can run (and thus hold that lock) for a long time. – Mark Plotnick Jul 29 '17 at 18:21
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/150491/discussion-between-einpoklum-and-mark-plotnick). – einpoklum Jul 29 '17 at 18:27
  • The main motivation of FD_CLOEXEC is expensive IPC and not threading, although there are some threading use cases. – Jay-Pi May 26 '22 at 21:00

2 Answers2

0

I have a use case, albeit one from a toy program (actually a homework assignment).

The assignment was to write a program that would take two arguments, which are the names of executables. It would start them up, using fork and exec (or e.g. execve), with the stdout of the first program writing to one end of a pipe that was read as stdin of the second program.

Example usage: ./mypipeprogram ./program1 ./program2 should give results identical(ish) to ./program1 | ./program2.

I wanted my program to be as close to bulletproof as I could get it, and that meant handling all the errors that I could, as gracefully as I could.

You'll have to forgive me, as I don't think I have the source anymore, so this is from memory.

One of the kinds of errors I had to try to handle was the case where one of the programs listed couldn't be exec'd, but the other could. In that case, the process that couldn't exec could kill the other process, so it wouldn't hang forever, or anything like that.

In hindsight, probably not the best idea, because closing the pipe should have worked fine.

Still, going one way (I think it was the first process, the output one), I could catch this, and instead kill the other process before it even tried to exec the other program.

What I did was open another pipe, and fnctl it with FD_CLOEXEC, relying on POSIX saying that "the file shall be closed upon successful execution of one of the exec functions."

Then the second process would prepare to exec, read from that pipe, and when it was closed, it would know the first process successfully exec'd, so it could go ahead. If the first process's exec failed, the pipe would still be open, so the second process would still be blocked on the read, and the first process could kill it.

TL;DR

I used FD_CLOEXEC on a pipe to allow one process to wait for a second process to successfully exec, or get killed by the second process if the exec failed.

0

Assume parent process P has a certain amount of files it shares with child process C1 (call it S1) and another amount of files it shares with child process C2 (call it S2). This requires to NOT use CLOEXEC for those files with the open call or whatever other call is used.

After the parent process opened the shared files S1, it calls fork/vfork/clone with the corresponding flags to inherit file descriptors. The same happens for the shared files S2. However, C2 now has also all file descriptors of C1.

Subsequent child processes C3...Cn would all inherit the file handles of previous processes, which is a problem if one works with many files and they are not closed shortly after.

One could now

    1. close those open file descriptors in the child process, which requires to iterate through all file descriptors and close the unknown ones by the child process
    1. let the Kernel deal with that logic and save potentially many slow system calls

The only exception, where file descriptor inheritance is the expected behavior are standard streams, because the user wants to handle child process output of errors or status reports.

Jay-Pi
  • 343
  • 3
  • 13