Reputation: 107
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:
My code do all of these points, but behaves in a strange way at the very end when the handler function returns.
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("%s\n",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 0\n");
}
if (status == 1)
{
// error
printf("\nBackground job exited\n");
}
}
return;
}
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).
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.
Upvotes: 1
Views: 2001
Reputation: 107
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.
Upvotes: 0
Reputation: 126508
One definite problem is that you are calling printf
in your signal handler, and printf
is not async-safe, so if the SIGCHLD happens while in any library function (such as fgets
waiting to read a line of input perhaps?), you get undefined behavior. Perhaps corrupting the stdio file buffers and causing it to act as if the input was duplicated.
You need to very careful how you write the code in your signal handler, making sure it cannot call (directly or indirectly) and non-async-safe library function, and ensuring that all the code is itself async-safe with respect to any side effects in other parts of your program.
Upvotes: 1