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:
- User enters a command with “&” as the last argument
- A new process is created with fork
- The parent process returns to the main function and wait for a new input from the user
- The child process executes in the background
- The child process ends and SIGCHLD is triggered
- 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.