Right now I try to understand the forking/rebinding of stdin/out/err of child processes and to manage the resources (filehandles, sockets) rightly without leaking any resources.
There are some questions left: After I create a socketpair and fork, I have in the parent 5 filedescriptors and in the child (stdin/out/err/socket1/socket2). In the child process, I need to close the "parent" side of the socketpair. I close() stdin/out/err after the fork and dup() the "client end" of the socket three times. After the dup(), do I need to close the "source" of the dup? I guess yes ... but am I right?
When I create in this way (see below) a second child, is the resource handling right? I tried to rely heavily on RAII to not leak any fds, but is it right? Do I miss a big thing?
Bye and thanks in advance!
Georg
EDIT: I fixed an error in rebind_and_exec_child.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <cassert>
// this handle takes a fd, behaves like an int and makes sure the fd is closed again
class fdhandle {
public:
explicit fdhandle(int fd) {
mp_fd = std::shared_ptr<int>(new int, [=](int* pfd) {
close(*pfd);
delete pfd;
});
assert(mp_fd);
*mp_fd = fd;
}
operator int() {
assert(mp_fd);
return *mp_fd;
}
private:
std::shared_ptr<int> mp_fd;
};
void rebind_and_exec_child(fdhandle fd, std::string exe) {
// now close the std fds and connect them to the given fd
close(0); close(1); close(2);
// dup the fd three times and recreate stdin/stdout/stderr with fd as the target
if (dup(fd) != 0 || dup(fd) != 1 || dup(fd) != 2) {
perror("error duplicating socket for stdin/stdout/stderr");
exit(EXIT_FAILURE);
}
// now we can exec the new sub process and talk to it through
// stdin/stdout/stderr
char *arguments[4] = { exe.c_str(), exe.c_str(), "/usr/bin", NULL };
execv(exe.c_str(), arguments);
// this could should never be reached
perror("error: executing the binary");
exit(EXIT_FAILURE);
}
fdhandle fork_connected_child(std::string exe) {
// create the socketpair
int fd[2];
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) {
perror("error, could not create socket pair");
exit(EXIT_FAILURE);
}
fdhandle fdparent(fd[0]); fdhandle fdchild(fd[1]);
// now create the child
pid_t pid = fork();
switch (pid) {
case -1: // could not fork
perror("error forking the child");
exit(EXIT_FAILURE);
break;
case 0: // child
rebind_and_exec_child(fdchild);
break;
default: // parent
return fdparent;
break;
}
}
int main(int argc, const char** argv) {
// create 2 childs
fdhandle fdparent1 = fork_connected_child("/bin/ls");
fdhandle fdparent2 = fork_connected_child("/bin/ls");
}
I guess, I found the solution. For each created socket on the
socketpair()call, I setFD_CLOEXEC. This way, I can be sure that the kernel closes all file descriptors. All other sockets which are handled by my code, will be closed by the fdhandle class call toclose(). The rebinding of the stdin/stdout/stderr, I replaced thedup()fordup2()because it does close and dup atomicly.The hint was this page: http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
This is now my adjusted code:
EDIT: Adjusted structure