This code snippet opens /dev/console
. The resulting file descriptor is the lowest-numbered file descriptor that isn't already open. If that number is at most 2, the loop is executed again. If that number is 3 or above, the descriptor is closed and the loop stops.
When the loop finishes, file descriptors 0 to 2 (stdin, stdout and stderr) are guaranteed to be open. Either they were open before, and may be connected to any file, or they've just been opened, and they're connected to /dev/console
.
The choice of /dev/console
is strange. I would have expected /dev/tty
, which is always the controlling terminal associated with the process group of the calling process. This is one of the few files that the POSIX standard requires to exist. /dev/console
is the system console, which is where syslog messages sent to the console go; it isn't useful for a shell to care about this.
So here we do have to pass the file name twice in the function.
They are not quite the same thing as you notice by observing that one of them is used as the argv[0]
value. This doesn't have to be the same as the basename of the executable; many/most things ignore it and you can put whatever you want in there.
The first one is the actual path to the executable, for which there is an obvious necessity. The second one is passed to the process ostensibly as the name used to invoke it, but, e.g.:
execl("/bin/ls", "banana", "-l", NULL);
Will work fine, presuming /bin/ls
is the correct path.
Some applications do, however, make use of argv[0]
. Usually these have one or more symlinks in $PATH
; this is common with compression utilities (sometimes they use shell wrappers instead). If you have xz
installed, stat $(which xzcat)
shows it's a link to xz
, and man xzcat
is the same as man xz
which explains "xzcat is equivalent to xz --decompress --stdout". The way xz can tell how it was invoked is by checking argv[0]
, making these equivalent:
execl("/bin/xz", "xzcat", "somefile.xz", NULL);
execl("/bin/xz", "xz", "--decompress", "--stdout", "somefile.xz", NULL);
Best Answer
There's a flag you can set on a file descriptor (upon
open()
: O_CLOEXEC or later withfcntl()
: FD_CLOEXEC) if you don't want that fd to be passed to executed commands.That's what you should do for your internal file descriptors if you're going to execute commands.
In shells, that's what
ksh93
does when you doexec 3< some-file
for instance. For other shells or fds opened with{ cmd1; cmd2; } 3< file
, you'd need to close by hand if you don't wantcmd1
orcmd2
to access that fd:{cmd1 3<&-; cmd2; } 3< file
. That's good practice but not always followed as it's usually not critical if you don't do it.Now, whether the feature is useful. Yes, several commands rely on it.
A few commands take a file descriptor as argument that is meant to have been opened by a caller. A few examples that come to mind:
xterm
with its-S
optionqemu
for various thingsflock
(to lock a file on the caller's fd)test
command aka[
for it's-t
option (OK thetest
utility is built in most Bourne-like shells nowadays, but there still is atest
command that can be executed).dialog
needs file descriptors for input from the user, output and error to the user and input and output to the caller, so you can use extra fds for that.gpg
oropenssl
to which you can specify a file descriptor to communicate the passphrase or other information.There are a number of helper utilities (for instance, the need to execute could be to run a part of a command as a different user or group using a setuid/setgid executable) that rely on that.
Process substitution relies on it:
In,
diff <(cmd1) <(cmd2)
, 2 file descriptors (to pipes) are passed todiff
and diff accesses them by opening them via the special /dev/fd/n passed as argument.For shells that don't have process substitution, you do the same by hand with things like: