4

I have a TCP server application which occasionally needs to reconfigure the bound ports by closing them and then opening them at a later time.

The application also needs to execute an external binary with communication to it. This is currently done using a popen() call. The external binary run time can span the period when the network ports need to be reconfigured.

The problem is, when the main application closes a port it is taken on by the 'forked' process which popen has created to run the binary.

That makes sense (it's discussed at What happens when a tcp server binds and forks before doing an accept ? Which process would handle the client requests? ), but is undesirable, as the main application is then unable to reopen the port.

Is this where FD_CLOEXEC O_CLOEXEC as available in popen(3) can be used? The application needs the pipe that popen(3) offers as stdin to the binary which is executed, is that filehandle left open when CLOEXEC closes the others.

Is there a better way to run the binary, which won't result in a forked process which will hold on to a closed port?

There is another, possibly related question at How to fork process without inheriting handles?

Community
  • 1
  • 1
rolinger
  • 665
  • 7
  • 17

2 Answers2

7

No, you cannot start another program and get back from it without fork(2) followed by some execve(2) code (which is what popen, posix_spawn, and system are doing). You'll better avoid popen or system and code the pipe+fork+execve explicitly yourself (since you know which file descriptors should be kept and which to close(2), generally after the fork and before the execve in the child process...), see this.

(every process & program, except /sbin/init and some hotplug things, is started with fork + execve; and your shell is constantly using fork + execve for most commands, except the builtin ones like cd)

the fork(2) call could be implemented by clone(2).

Read some good book like the Advanced Linux Programming book freely available online here. See also syscalls(2). Use (at least for debugging and to understand things) strace(1) and your debugger (gdb). Study the source code of popen and of system in your free software libc (GNU libc or musl-libc), and the source code of your shell.

You could nearly mimic execve(2) by a tricky sequence of mmap(2) (and related munmap) calls, but not quite (in particular w.r.t. close-on-exec etc...). You'll probably need also to call the obsolete setcontext(3) (or write the equivalent assembler code).

You might consider communicating with a specialized shell-like server-like program doing the fork & execve (see my execicar.c for example and inspiration). Maybe you'll find daemon(3) useful.

A better alternative might be to embed some interpreter (like lua or guile) in your application and/or to dlopen(3) some plugin. The disadvantage is that a bug (in the interpreted script or the plugin) affects your entire server.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Thanks, but is it possible to execute the binary in another way, or at least stop the fork'ed process from taking over the network ports when the parent process closes them? – rolinger Nov 20 '15 at 12:35
  • 3
    Code the `pipe`+`fork`+`execve` yourself, and place appropriately calls to `close` – Basile Starynkevitch Nov 20 '15 at 12:37
  • 1
    I never deeply thought about it but it seems counter-intuitive to me that an executable cannot be started "from disk" (because that's what the outward appearance of e.g. issuing a command in a shell is). Sure, a new process probably must get an environment and other settings from somewhere and it would be handy (mandatory?) to have a parent. But all that could afaics as easily be accomplished by copying the information. Does a modern MS Windows follow the same "execute is fork" paradigm? – Peter - Reinstate Monica Nov 20 '15 at 12:43
  • I don't know Windows, but the separation between creating a process (with `fork`) and starting an executable (with `execve`) is a central idea to Unix & Posix and it is a very good idea (because *you* can code some stuff in between!) – Basile Starynkevitch Nov 20 '15 at 12:46
  • 2
    @PeterA.Schneider, Windows is different. It does not have any idea of fork() at all. *Nix is all fork, Win* is no fork - instead they have CreateProcessEx. I strongly prefer fork. – SergeyA Nov 20 '15 at 14:17
0

You can definitely use the close-on-exec flag to avoid newly started processes inheriting open files or sockets (and there's no other way I'm aware of). If you open a file with open(2) you can set the flag at that moment by adding O_CLOEXEC to the file creation flags. If the file is already open you can set it using fcntl() (and if you opened the file using fopen(3) you can get the file descriptor required for this via fileno(3)). For a socket you can also set the flag when opening it with socket(2) by setting SOCK_CLOEXEC. No file with this flag set will be inherited by a process spawned by your program, be it directly via fork + exec or any other "disguise" of that combination, like system(3) or popen(3).