简体   繁体   中英

linux: fork/socketpair/close and multiple child process

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 set FD_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 to close() . The rebinding of the stdin/stdout/stderr, I replaced the dup() for dup2() because it does close and dup atomicly.

The hint was this page: http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html

File descriptors open in the calling process image shall remain open in the new process image, except for those whose close-on-exec flag FD_CLOEXEC is set. For those file descriptors that remain open, all attributes of the open file description remain unchanged. For any file descriptor that is closed for this reason, file locks are removed as a result of the close as described in close() . Locks that are not removed by closing of file descriptors remain unchanged.

This is now my adjusted code:

EDIT: Adjusted structure

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <memory>
#include <cassert>
#include <iostream>

// this handle takes a fd, behaves like an int and makes sure the fd is closed again
class fdhandle {
public:
    fdhandle()  {}
    explicit fdhandle(int fd) {
        mp_fd = std::shared_ptr<int>(new int, [=](int* pfd) {
            close(*pfd);
            delete pfd;
        });
        assert(mp_fd);
        *mp_fd = fd;

        // set FD_CLOEXEC on fd
        int flags;
        flags = fcntl(fd, F_GETFD);
        if (-1 == flags) {
            perror("error, could not get flags from filedescriptor");
            exit(EXIT_FAILURE);
        }
        flags |= FD_CLOEXEC;
        if (fcntl(fd, F_SETFD, flags) == -1) {
            perror("error, could not set FD_CLOEXEC");
            exit(EXIT_FAILURE);
        }
    }
    operator int() {
        assert(mp_fd);
        return *mp_fd;
    }

    void show_fd_status() {
        if (!mp_fd)
            return;

        int fd = *mp_fd;

        using namespace std;
        char buf[256];
        int fd_flags = fcntl(fd, F_GETFD);
        if (fd_flags == -1)
            return;
        int fl_flags = fcntl(fd, F_GETFL);
        if (fl_flags == -1)
            return;
        char path[256];
        sprintf(path, "/proc/self/fd/%d", fd);
        memset(&buf[0], 0, 256);
        ssize_t s = readlink(path, &buf[0], 256);
        if (s == -1) {
            cerr << " (" << path << "): " << "not available";
            return;
        }
        cerr << fd << " (" << buf << "): ";
        // file status
        if (fd_flags & FD_CLOEXEC)  cerr << "cloexec ";
        if (fl_flags & O_APPEND)  cerr << "append ";
        if (fl_flags & O_NONBLOCK)  cerr << "nonblock ";

        // acc mode   
        if (fl_flags & O_RDONLY)  cerr << "read-only ";
        if (fl_flags & O_RDWR)  cerr << "read-write ";
        if (fl_flags & O_WRONLY)  cerr << "write-only ";
        if (fl_flags & O_DSYNC)  cerr << "dsync ";
        if (fl_flags & O_RSYNC)  cerr << "rsync ";
        if (fl_flags & O_SYNC)  cerr << "sync ";

        struct flock fl;
        fl.l_type = F_WRLCK;
        fl.l_whence = 0;
        fl.l_start = 0;
        fl.l_len = 0;
        fcntl(fd, F_GETLK, &fl);
        if (fl.l_type != F_UNLCK)
        {
            if (fl.l_type == F_WRLCK)
                cerr << "write-locked";
            else
                cerr << "read-locked";
            cerr << "(pid:" << fl.l_pid << ") ";
        }
    }
private:
    std::shared_ptr<int>    mp_fd;
};

struct child
{
    pid_t       pid;
    fdhandle    fd;
};

void rebind_and_exec_child(fdhandle fd, std::string exe) {
    // unset FD_CLOEXEC 
    int flags, oflags;
    flags = oflags = fcntl(fd, F_GETFD);
    if (-1 == flags) {
        perror("error, could not get flags from filedescriptor");
        exit(EXIT_FAILURE);
    }
    flags &= ~FD_CLOEXEC;
    if (fcntl(fd, F_SETFD, flags) == -1) {
        perror("error, could not unset FD_CLOEXEC");
        exit(EXIT_FAILURE);
    }

    // close and rebind the stdin/stdout/stderr
    // dup the fd three times and recreate stdin/stdout/stderr with fd as the target
    if (dup2(fd, STDIN_FILENO) != 0 || dup2(fd, STDOUT_FILENO) != 1 || dup2(fd, STDERR_FILENO) != 2) {
        perror("error duplicating socket for stdin/stdout/stderr");
        exit(EXIT_FAILURE);
    }

    // restore the old flags
    if (fcntl(fd, F_SETFD, oflags) == -1) {
        perror("error, could not set FD_CLOEXEC");
        exit(EXIT_FAILURE);
    }

    // now we can exec the new sub process and talk to it through 
    // stdin/stdout/stderr
    char path[256];
    char argv[256];
    sprintf(path,"%s",exe.c_str());
    sprintf(argv,"%d",30);
    execlp(path, path, argv, 0);

    // this should never be reached
    perror("error: executing the binary");
    exit(EXIT_FAILURE);
}

child 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, exe);
        break;

    default:    // parent
        std::cout << "forked " << exe << std::endl;
        return child { pid, fdparent };
        break;
    }       
}

int main(int argc, const char** argv) {
    // setup the signal handler prior to forking
    sleep(20);

    // create 2 childs
    {
        child child1 = fork_connected_child("/usr/bin/sleep");
        child child2 = fork_connected_child("/usr/bin/sleep");

        int status;
        waitpid(child1.pid, &status, 0);
        waitpid(child2.pid, &status, 0);
    }

    sleep(20);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM