3

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?

John Leaf
  • 63
  • 1
  • 7
  • 1
    Maybe consider using a `sigaction` handler for `SIGCHLD` instead? That way you could wait for either the child exiting (indicated by the signal handler), or input. – Hasturkun Jul 07 '19 at 07:16
  • How can it wait for both of them at the same time? If i understand correctly it will either wait for the child or wait for input – John Leaf Jul 07 '19 at 07:53
  • @Hasturkun, If I understand correctly you are saying, handle `SIGCHLD` and say `SIGUSR1` using `sigaction`, use `raise` to signal `SIGUSR1` on user input. – Shubham Jul 07 '19 at 08:15
  • @JohnLeaf: If you're using `select` (or `poll`), you can use a `socketpair`, `eventfd` or `signalfd` (with the first two getting written by the signal handler) to wait on both input and child termination. Other similar options exist. (some depending on if your program is multithreaded or not) – Hasturkun Jul 07 '19 at 09:01
  • Do two forkings? – alk Jul 07 '19 at 16:42

1 Answers1

2

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;
}
Shubham
  • 628
  • 1
  • 9
  • 19
  • Hi, thanks for your reply. Your code worked fine for me as is, but when I tried to implement in my code (as shown [here](https://pst.moe/paste/lyuvml)), the program just hangs and exits with the error `User defined signal 1` on any input. Any reason what could be going wrong here? – John Leaf Jul 09 '19 at 08:59
  • Also I have one more question so I thought I'd make a separate comment. Can you explain why `if (-1 == scanf("%d", &a))` doesn't block? won't `scanf` wait for input? – John Leaf Jul 09 '19 at 09:00
  • `scanf` returns -1 on failure and `errno` is set from [one of the error code](https://linux.die.net/man/3/scanf) (check "Return Value" section). When `SIGCHLD` is raised then `scanf` is interrupted setting `errno` to `EINTR`. – Shubham Jul 09 '19 at 11:31
  • [Here](https://pst.moe/paste/lyuvml) I don't see a signal handler definition. You have not specified signals to listen for. I am unsure whether `wgetch` will be interrupted by a signal and use `fork` after initializing signals. – Shubham Jul 09 '19 at 11:50
  • Thanks for the explanation! I checked [this](https://linux.die.net/man/3/wgetch) manpage and it says that `wgetch` will get interrupted by a signal. As for the `cb_sig` and `raised_signal` definitions, I have them declared, just didn't include them in the paste. You can view the full code [here](https://pst.moe/paste/ggxrzc). It's long but you can control-f the relevant parts (`cb_sig`,`raised_signal`,`getPDFPreview`) – John Leaf Jul 09 '19 at 13:11
  • 1
    I don't see `sigaction(SIGCHLD, &act, NULL)`, `sigaction(SIGUSR1, &act, NULL)` being called in your code. Also please [remove the signal handler](https://stackoverflow.com/questions/9302464/how-do-i-remove-a-signal-handler) if you do not want to use it in future. – Shubham Jul 09 '19 at 15:05
  • can't believe I missed that. Thanks! – John Leaf Jul 10 '19 at 03:56
  • 1
    Hi, I was reading more about signal handling and I read that you cant modify global variables from signal handlers unless they are of the type `volatile sig_atomic_t` but, in my code I am changing `int raised_signal` from the signal handler. why is this working? – John Leaf Jul 13 '19 at 05:37
  • 1
    Yes, If you try to update the global variable from signal and main process there is a chance of race condition. In this case we are updating `raised_signal` from signal handler and main process is just reading the variable. So this will not have two separate copies of `raised_signal` at a given time. As far as atomic variables are considered, it guarantees that the variable will be updated in a single instruction that way we do not have multiple copies of it. This is just an example, you could further read "Signals" chapter from "Linux System Programming by Robert Love" – Shubham Jul 15 '19 at 03:01