Skip to content
Advertisement

What happens after the execution of a handler function on the SIGCHLD signal in C?

I was wondering what exactly happens after the execution of a handler on a signal in C, more specifically on the SIGCHLD signal.

I’m actually building a shell and I need to do this:

  1. User enters a command with “&” as the last argument
  2. A new process is created with fork
  3. The parent process returns to the main function and wait for a new input from the user
  4. The child process executes in the background
  5. The child process ends and SIGCHLD is triggered
  6. The parent process handles the death of his child

My code do all of these points, but behaves in a strange way at the very end when the handler function returns.

My implementation

Main loop

int
main()
{

    // SIGNALS HANDLING :
    sigaction(SIGTERM, NULL, NULL);
    sigaction(SIGQUIT, NULL, NULL);


    // Current location
    char* path = malloc(PATHMAX);
    int argc;

    char** argv;

    // main loop
    while(1) 
    {
        getcwd(path, PATHMAX);
        printf("%s$ ",path);

        argc = 0;

        // Parsing
        argv = malloc(MAX_INPUT); // user's input is limited to 100000 characters
        getParameters(argv, &argc);


        // First we check if the user wants to execute the task in background
        if ( strcmp(argv[argc-1],"&") == 0 ) {

            // builtin functions can't be executed in background:
            int isExit = (strcmp(argv[0],"exit") == 0) ? 1 : 0;
            int isCd = (strcmp(argv[0],"cd") == 0) ? 1 : 0;

            if(isExit || isCd) {
                printf("Built-in functions can't be executed in background. n");
            } else {
                // Execute the job in background

                // First we delete the last arg
                argv[argc-1] = NULL;
                argc--;

                execBackground(argv,argc);
            }

        } else {

            // Execute built-in functions
            if(!(exeBuiltin(argv, argc))) {
                // Execute jobs if no builtin functions were executed
                exeJob(argv,argc);
            }

        } 


        free(argv);



    }




    return 0;
}

User’s input processing

// max 100000 characters
char input[MAX_INPUT];


// read keyboard inputs
fgets(input,MAX_INPUT,stdin);

Built-in functions (cd and exit)

int exeBuiltin(char** argv, int argc) {
    // execute builtin functions if it exists
    char* builtin[2];
    builtin[0] = "exit";
    builtin[1] = "cd";

    // run through the arguments
    for(int i = 0; i < argc; i++) {
        // exit
        if (strcmp(argv[i],builtin[0]) == 0) {
            exitBuiltin(EXIT_SUCCESS);
        } else if (strcmp(argv[i],builtin[1]) == 0) {
            // cd
            if (argc >= 2) {
                cdBuiltin(argv[i+1]);
                return 1;
            }
        }
    }

    return 0;
}

static void cdBuiltin(char* path) {
    if(chdir(path) == -1) {
        printf("%sn",strerror(errno));
    };
}


static void exitBuiltin(int signal) {
    exit(signal);
}

Link between signal and handler:

struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = child_handler;

sigaction(SIGCHLD, &sa, NULL);

Handler:

static void child_handler(int sig)
{
    int status;
    /* kills process 
     pid is a global variable that is set when I call fork() */
    if(waitpid(pid, &status, 0) != -1) {

        if (status == 0)  // Verify child process terminated without error.  
        {
           // success  
           printf("nBackground job exited with code 0n");
        }

        if (status == 1)      
        {
            // error
            printf("nBackground job exitedn");
        }

    }


    return;
}

The problem

My problem occurs when I call something like:

sudo apt-get update &
cd ../

Then when the sudo command terminates (the handler is called and prints “Background job exited with code 0”), even though the command “cd ../” is already executed it gets executed again.

The ouput looks like:

$ /home/ubuntu/workspace$ sudo apt-get update &
$ /home/ubuntu/workspace$ cd ../
$ /home/ubuntu$
Background job exited with code 0
$ /home$

Additional information:

cd is a builtin function which only does: chdir(path) with a path given, and the path is simply output with a printf("%s$",path) at the beginning of every loop in the main (after a command has been called).

The question

In order to understand what the real problem is, I believe that I should understand what happens at the end of my child_handler function.

If I’m at a certain point in the code with the main process, say waiting on the user input, and the background process dies, child_handler is called and then what? Does it change anything for the main process or is it still a the same point in the code, waiting for an input?

Thanks for you help.

Advertisement

Answer

As @ChrisDodd said, the problem was that a system call (fgets), was interrupted by the death of the background process and caused undefined behaviour.

Therefore, I solved my problem by adding the following flag to my handler:

sa.sa_flags = SA_RESTART;

As the documentation says it will:

Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals.

Thus no more issue with fgets.

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement