Search code examples
cfork

How to terminate waitpid on user input?


I am working on a ncurses based file manager in C. The problem is that some child processes can take some time to complete and till that happens it remains stuck due to waitpid.

I can't use the WNOHANG flag because the next block of code is dependent on the output of the child process.

void getArchivePreview(char *filepath, int maxy, int maxx)
{
    pid_t pid;
    int fd;
    int null_fd;

    // Reallocate `temp_dir` and store path to preview file
    char *preview_path = NULL;
    allocSize = snprintf(NULL, 0, "%s/preview", cache_path);
    preview_path = malloc(allocSize+1);
    if(preview_path == NULL)
    {
        endwin();
        printf("%s\n", "Couldn't allocate memory!");
        exit(1);
    }
    snprintf(preview_path, allocSize+1, "%s/preview", cache_path);

    // Create a child process to run "atool -lq filepath > ~/.cache/cfiles/preview"
    pid = fork();
    if( pid == 0 )
    {
        remove(preview_path);
        fd = open(preview_path, O_CREAT | O_WRONLY, 0755);
        null_fd = open("/dev/null", O_WRONLY);
        // Redirect stdout
        dup2(fd, 1);
        // Redirect errors to /dev/null
        dup2(null_fd, 2);
        execlp("atool", "atool", "-lq", filepath, (char *)0);
        exit(1);
    }
    else
    {
        int status;
        waitpid(pid, &status, 0);
        getTextPreview(preview_path, maxy, maxx);
        free(preview_path);
    }
}

In this case, I would like to carry forward with the rest of the program if the user decides to go to some other file. In what way can I change the architecture of the program?


Solution

  • If I have understood the question correctly then you want to unblock parent on either completion of child or any user input.

    As suggested in this comment, you could handle SIGCHLD and one more signal say SIGUSR1. SIGUSR1 will be raised when you get user input. Following is the example where both SIGCHLD and 'SIGUSR1' is handled. If use inputs any number then it raises SIGUSR1 to parent and parent kill child. Else child will raise SIGCHLD on exit.

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <errno.h>
    
    int raised_signal = -1; 
    
    static void cb_sig(int signal)
    {
            if (signal == SIGUSR1)
                    raised_signal = SIGUSR1;
            else if (signal == SIGCHLD)
                    raised_signal = SIGCHLD;
    }
    
    int main()
    {
            int pid;
            int i, a;
            struct sigaction act;
    
            sigemptyset(&act.sa_mask);
            act.sa_flags = 0;
            act.sa_handler = cb_sig;
    
            if (sigaction(SIGUSR1, &act, NULL) == -1) 
                    printf("unable to handle siguser1\n");
            if (sigaction(SIGCHLD, &act, NULL) == -1) 
                    printf("unable to handle sigchild\n");
    
            pid = fork();
            if (pid == 0) {
                    /* child */
                    for (i = 0; i < 10; i++) {
                            sleep(1);
                            printf("child is working\n");
                    }
                    exit(1);
            } else {
                    /* parent */
                    if (-1 ==  scanf("%d", &a)) {
                            if (errno == EINTR)
                                    printf("scanf interrupted by signal\n");
                    } else {
                            raise(SIGUSR1);
                    }
    
                    if (raised_signal == SIGUSR1) {
                            printf("user terminated\n");
                            kill(pid, SIGINT);
                    } else if (raised_signal == SIGCHLD) {
                            printf("child done working\n");
                    }
    
                    exit(1);
            }
    
            return 0;
    }