Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does closing file descriptors after fork affect the child process?

I want to run programs in linux by a button click an therefore I wrote a function execute:

void execute(const char* program_call, const char* param )
{
    pid_t child = vfork();

    if(child == 0) // child process
    {
        int child_pid = getpid();

        char *args[2]; // arguments for exec
        args[0] = (char*)program_call; // first argument is program_call
        args[1] = (char*)param;

        // close all opened file descriptors:
        const char* prefix = "/proc/";
        const char* suffix = "/fd/";
        char child_proc_dir[16]; 
        sprintf(child_proc_dir,"%s%d%s",prefix,child_pid, suffix);

        DIR *dir;
        struct dirent *ent;

        if ((dir = opendir (child_proc_dir)) != NULL) {
            // get files and directories within directory
            while ((ent = readdir (dir)) != NULL) {
                // convert file name to int
                char* end;
                int fd = strtol(ent->d_name, &end, 32);
                if (!*end) // valid file descriptor
                {
                    close(fd); // close file descriptor
                    // or set the flag FD_CLOEXEC
                    //fcntl( fd, F_SETFD, FD_CLOEXEC );
                }
            }
            closedir (dir);
        } 
        else 
        {
            cerr<< "can not open directory: " << child_proc_dir <<endl;
        }
        // replace the child process with exec*-function
            execv(program_call,args);
            _exit(2);
        }
    else if (child == -1) // fork error
    {
        if (errno == EAGAIN)
        {
            cerr<<“To much processes"<<endl;
        }
        else if (errno == ENOMEM)
        {
            cerr<<“Not enough space available."<<endl;
        }
    }
    else // parent process
    {
        usleep(50); // give some time 
        if ( errno == EACCES)
        {
            cerr<<“Permission denied or process file not executable."<<endl;
        }
        else if ( errno == ENOENT)
        {
            cerr<<"\n Invalid path or file."<<endl;
        }
        int child_status;
        if ( waitpid(child, &child_status, WNOHANG | WUNTRACED) < 0) // waitpid failed
        {
            cerr<<"Error - Execution failed"<<endl;
        }
        else if ( WIFEXITED( child_status ) &&  WEXITSTATUS( child_status ) != 0)   
        {
            cerr<<“Child process error - Execution failed"<<endl;
        }
    }
}

There are two problems:

  1. Closing the file descriptors causes some problems, for example Thunderbird crashes or VLC runs without sound. More exactly: closing of stdout(1) and stderr(2) causes these problems. As I understand, closing file descriptor before exec only prevents them from been duplicated (there is no need to send informations from child process to parent process). Why does this affect the child process? Replacing close() by setting the flag FD_CLOEXEC doesn't change anything. Also setting the FD_CLOEXEC flag before fork doesn't solve the problem. Is there a better way to prevent inheritance of file descriptors?

  2. The return value of waitpid is often 0, even if the program call fails, I think because there are two (asynchrone) processes. usleep(50) solves this problem for my needs, but I hope there are better solutions for this problem.

I'm using vfork, but the same problems occur by using fork.

like image 340
Lexa Avatar asked Dec 15 '14 11:12

Lexa


People also ask

What happens to file descriptors after fork?

When a fork() is performed, the child receives duplicates of all of the parent's file descriptors. These duplicates are made in the manner of dup(), which means that corresponding descriptors in the parent and the child refer to the same open file description.

What happens when you close a file descriptor?

close() closes a file descriptor, so that it no longer refers to any file and may be reused. Any record locks (see fcntl(2)) held on the file it was associated with, and owned by the process, are removed (regardless of the file descriptor that was used to obtain the lock).

Does child process inherit file descriptors?

With many of the process-creation functions, the child inherits the file descriptors of the parent. For example, if the parent had file descriptor 5 in use for a particular file when the parent creates the child, the child will also have file descriptor 5 in use for that same file.

What happens within a parent process when a child closes a file descriptor inherited across a fork?

within a parent process when a child closes a file descriptor inherited across a fork. In other words, does the file remain open in the parent, or is it closed there? It stays open in the parent.


2 Answers

First problem: There is no way to prevent inheritance of file descriptors except you close them yourself or set FD_CLOEXEC, check this

Second problem: You got The return value of waitpid is often 0, because you sepecfied WNOHANG in waitpid.

waitpid(): on success, returns the process ID of the child whose state has changed; 
if WNOHANG was specified  and  one  or  more  child(ren) specified by pid exist, 
but have not yet changed state, then 0 is returned.  On error, -1 is returned.
like image 76
D3Hunter Avatar answered Sep 28 '22 12:09

D3Hunter


First, in 2014, never use vfork but simply fork(2). (Since vfork(2) is obsolete since POSIX 2001 and removed in POSIX 2008).

Then, the simplest way to close most of file descriptors is just

for (int fd=3; fd<256; fd++) (void) close(fd);

(hint: if a fd is invalid, close(fd) would fail and we ignore the failure; and you start from 3 to keep open 0==stdin, 1==stdout, 2==stderr; so in principle all the close above would fail).

However, well behaved and well-written programs should not need such a loop on closing (so it is a crude way to overcome previous bugs).

Of course, if you know that some file descriptor other than stdin, stdout, stderr is valid and needed to the child program_call (which is unlikely) you'll need to explicitly skip it.

and then use FD_CLOEXEC as much as possible.

It is unlikely that your program would have a lot of file descriptors without you knowing them.

Maybe you want daemon(3) or (as commented by vality) posix_spawn.

If you need to explicitly close STDIN_FILENO (i.e. 0), or STDOUT_FILENO (i.e. 1), or STDERR_FILENO (i.e. 2) you'll better open("/dev/null",... and dup2 them after - before calling exec, because most programs expect them to exist.

like image 32
Basile Starynkevitch Avatar answered Sep 28 '22 11:09

Basile Starynkevitch