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.