0

As most developers will be aware processes define three file descriptors which we know more commonly as stdin, stdout and stderr.

From what I can tell the fd for each of these are statically defined as 0, 1 and 2 respectively. This is explicitly stated in the POSIX standard: http://pubs.opengroup.org/onlinepubs/009695399/functions/stdin.html

Now lets say I have a group of processes which require a 4th. For example a child process created with fork() exec() requiring a handle to a control socket created with socketpair() before the fork(). For this example the purpose of the 4th handle (socket) is to provide a link between parent and child process. Now the question comes... how does the child know which FD is the control socket? Is there any reason why I can't use a static number for this (eg: #define CONTROL_SOCKET 3) as long as I dup2(new_socket,CONTROL_SOCKET) between the fork() and the exec(). I'd then just be able to foo = write(CONTROL_SOCKET, bar, baz) inside the child. Lets take it as read that any other FDs open my app are expected to close on exec so my logic is that dup2() won't close anything that won't be closed anyway by exec().

I know that there are several possible work-arounds to avoid doing this (eg: passing the FD in an environment variable or program arguments) and there are a number of examples on the web showing how to do so. What I don't understand is what is being worked around? What's the problem with statically defining an FD that's being avoided? It feels at first glance to be avoiding a problem which doesn't exist. Do some systems use FDs other than 0, 1 and 2 and might I be overwriting something important by dup2(<some fd>,3) before an exec?

Note: This question is really a question about writing code which is portable between existing POSIX operating systems.

Philip Couling
  • 13,581
  • 5
  • 53
  • 85
  • 2
    A better way would be to pass the FD# as an argument or in an environment variable, so that this question doesn't arise. – Barmar Feb 11 '16 at 00:36
  • I think I've read that some shells may open other descriptors. But I don't think application programs should care about them, so it wouldn't be a problem if your code overwrote them. – Barmar Feb 11 '16 at 00:37
  • No, there isn't. As far as the kernel is concerned, it's just a convention. It's possible to start a process without 0, 1 or 2 either, but nobody does that. – user253751 Feb 11 '16 at 00:49
  • @immibis: Actually the C standard requires three streams. How they are implemented is not part of the standard. – too honest for this site Feb 11 '16 at 01:07
  • 2
    @Olaf The Linux kernel does not care about this strange "C standard" thing. You can `close(0); close(1); close(2); execl(...);` just fine. – user253751 Feb 11 '16 at 01:14
  • @immibis: Not sure what your point is. If you leave the standard, you cannot expect standard behaviour anymore. But the question does not even specify a specific OS and there are very well different implementations. However, it is not just convention, but **standard**. – too honest for this site Feb 11 '16 at 01:18
  • @Olaf Nobody asked about standard behaviour. – user253751 Feb 11 '16 at 01:48
  • 1
    @Olaf perhaps I was incorrect in flagging this as "C". Its not really a C question at all and the C standard can not help here because the C standard only lays out the requirements for the C *language* to work correctly. Standards covering OS behaviour are NOT included in the C standard. So Immubus is correct. While there are *nix standards such as POSIX they do not completely cover common (portable) behaviour. There are a lot of conventions between operating systems which are not actually in any standard. – Philip Couling Feb 11 '16 at 10:30
  • 1
    @Olaf actually looking at the POSIX standard, it IS defined and standard: http://pubs.opengroup.org/onlinepubs/009695399/functions/stdin.html 0,1 and 2 are the fixed values on POSIX systems. – Philip Couling Feb 11 '16 at 10:42
  • @couling: But it is not C standard. YOu did not even mention an OS. As you now added the POSIX tag all is fine! Note that we have to work with the information given in a question. Everything beyond that is just speculation. – too honest for this site Feb 11 '16 at 11:04
  • Did you consider using `fdopen`? – Basile Starynkevitch Feb 11 '16 at 11:07
  • If you have somehow a file descriptor (I don't know how, that is your business), you get a `FILE*` – Basile Starynkevitch Feb 11 '16 at 12:09
  • Yes, since `exec` is overwriting the entire virtual address space of the process. But you could pass the file descriptor number as an argument to the `exec`-ed program, and use `fdopen` *inside* that program – Basile Starynkevitch Feb 11 '16 at 12:16

3 Answers3

2

There is no widespread use of file descriptors other than 0, 1, 2. I seem to remember a few tracing systems using a fixed file descriptor out of that range (though not usually 3), but they were very much the exception (and I don't remember noticing it this millennium).

On the whole, if the first process in the set is able to ensure that the descriptor was not in use when it was started, it can safely pass the descriptor on to its child processes. If you find the descriptor already in use, you could open a different descriptor to the control socket, and then use fstat() to compare the two descriptors. If they're for the same device, you can probably go ahead using the inherited descriptor 3; if not, you need to dup2() and close() the control socket.

If your processes plan to simply open the control socket themselves, there's no particular need to worry about which file descriptor is used.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

The reason you probably don't want to do that is that unlike with 0, 1, and 2, you're not guaranteed that 3 will be open, unless you always control how the process is started.

There's nothing wrong with having a fixed descriptor number for some channel, though. You definitely will not be overwriting anything with dup2(<some fd>, 3). It'll only overshadow <some fd> for your process and it's children, but 3 won't be affected in the parent process at all.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 1
    Yes I always control how the process is started... Frankly I'd prefer not to `exec()` at all and simply fork, but other requirements (including a need to use Pthreads) force me to clean up the process using `exec()`. – Philip Couling Feb 11 '16 at 10:26
-1

You don't need to make it so complex.

I.e. do not use dup2() with a target descriptor that you do not know for certain is closed (e.g. you know because you just closed it), or that you do not want to implicitly close in doing so.

Just pass new_socket itself, as the string representing its integer value, on either the command line or in an environment variable, as suggested in the comments.

See this answer: https://stackoverflow.com/a/21596854/816536 but ignore the second variant of "P1.c" -- that's the losing way.

Community
  • 1
  • 1
Greg A. Woods
  • 2,663
  • 29
  • 26
  • I'm not suggesting "work-arounds". I'm suggesting better methods that make your intentions well known and are clear and concise. Making assumptions is always far more error prone than being explicit, regardless of wether it brings platform dependence or not. It's not really "extra work" to be precise and explicit in one's intentions -- rather it is a very good habit to learn. Your future self will thank you! – Greg A. Woods Feb 11 '16 at 20:57