0

The problem: I need to print the kill signal received by a process,

For example:

If I send a *kill -15 1245* where 1245 is the pid of my process, my program should print something like "Process killed by signal 15", but even If I send a *kill -15* to a process, the WIFSIGNALED macro returns false and obviously WTERMSIG returns 0.

The system: I'm on Linux Mint 18.3, an Ubuntu based distro, and I tested my program in other Ubuntu distros and still does not working, BUT in Fedora and OpenSUSE it works well. Any idea?

The code:

//Libraries
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>

//Macros
#define MAX_LIMIT 50

//Function where i create a child process and execute a shell over it.
void run(char comando[])
{
    int status;
    pid_t pid;
    if((pid = fork()) == 0)
        execlp("sh", "sh", "-c", comando, NULL);
    pid = waitpid(pid, &status, 0);
    //The problem begins here, the WIFEXITED returns *true* even is the process was killed by a signal.
    if(WIFEXITED(status))
        printf("Process ended with status %d\n", 
WEXITSTATUS(status));
    //Is here when i need to print the signal, but WIFSIGNALED returns *false* even if a signal was sended by the *kill* command.
    else if(WIFSIGNALED(status))
        printf("Process killed by signal %d\n", 
WTERMSIG(status));
    else if(WIFSTOPPED(status))
        printf("Process stopped by signal %d\n", 
WSTOPSIG(status));
    else if(WIFCONTINUED(status))
        printf("Process continued...\n");
} 
//Function that simulates a shell by repeating prompt.
void shell()
{
    run("clear");
    printf("\t\t\t\t\tMINI_SHELL\n");
    char comando[MAX_LIMIT];
    do
    {
        printf("$> ");
        fgets(comando, MAX_LIMIT, stdin);
        char *cp = strchr(comando,'\n'); if (cp != NULL) *cp =  0;
        if(strcmp(comando, "ext") != 0)
            run(comando);
    } while(strcmp(comando, "ext") != 0);
}

int main(int argc, char *argv[])
{
    shell();
    return 0;
}
  • You may have a race condition. That is, the command terminates normally _before_ you're able to send it a kill signal. If you do (e.g. `date`) as the command, you won't have time to do `ps` to find the pid to do `kill `. You may want to test with `sleep 60` as the command, so you'll have enough time. Of course, for testing, you could do `kill(pid,SIGINT);` between the `fork` and the `waitpid` to guarantee that the signal is sent. You can check the return of `kill` (and `errno`) – Craig Estey Sep 13 '18 at 23:32
  • Also, note that [as you already realize] `fgets` will return a buffer with `\n` at the end. This will probably cause the command to abort immediately (hence, no time for `kill` to take effect--you'll always get `WIFEXITED` as true). After the `fgets`, add: `char *cp = strchr(comando,'\n'); if (cp != NULL) *cp = 0;` and remove the `\n` from your `strcmp` calls. – Craig Estey Sep 13 '18 at 23:53
  • You are missing a couple of parenthesis in `if(pid = fork() == 0)`. The `pid` you watch is not the `pid` of a child. – user58697 Sep 14 '18 at 01:26
  • @user58697 You were right, I've already fix those parenthesis, but the problem stills unsolved. Thanks for the observation. – Rafael Gallardo Sep 14 '18 at 02:22
  • @CraigEstey If I use `kill(pid, SIGINT)` between the `fork` and the `waitpid` the process report that was killed by signal 2, but if I send a SIGINT signal from other terminal is doesn't works, besides I add `char *cp = strchr(comando,'\n'); if (cp != NULL) *cp = 0;` after the `fgets` and remove my `\n`. The process had enough time to be finded by the `ps` because I run an infinite cicle. I think the system is losing the signal, but I don't know why the program works well in Fedora and not in Ubuntu. – Rafael Gallardo Sep 14 '18 at 02:34
  • I confirm your reported behavior under Fedora [which is what I have]. Try `SIGTERM`. Your subprogram may need to examine the default signal mask and handler it is given (e.g. `sigprocmask` and `signal` or `sigaction`) to get `SIG_DFL` behavior. It _may_ be the `sh` command. Under Fedora, `sh` is `bash`, but under Ubuntu, it may be something else (also look at shell versions). So, try using `bash` in your `execlp` – Craig Estey Sep 14 '18 at 03:33
  • @CraigEstey It's done, you solve it, i just replace the call for `sh` with a call for `bash` and now everything runs like expected. Thank you very much. I'm novice in stackoverflow, any way to accept your answer? I think it can be useful for other users, I'm not the only one with this problem in my class. – Rafael Gallardo Sep 14 '18 at 04:18

2 Answers2

1

It all comes down to the difference between the default shell on debian-based distros (/bin/dash) and on redhat-based ones (/bin/bash).

When you're calling

execlp("sh", "sh", "-c", comando, NULL);

with a commando like "cat" or "echo 1; cat", if sh is /bin/dash (as on debian), the shell will call waitpid() itself for the status of cat before exiting; if sh is /bin/bash, it will just exec through to the last command in the script.

Try typing a command like echo pid=$$; cat in your mini-shell, then kill the pid printed by the echo, not the pid of cat, and you will get the expected 'Process killed by signal ...'

0

Note: This is prefaced by the top comments.

Because fgets leaves a \n at the end, this should be removed before being passed to execlp. Otherwise, the command will likely fail [quickly] (with a WIFEXITED status) due to not finding a command that has the newline at the end. To strip this, after the fgets, add:

char *cp = strchr(comando, '\n');
if (cp != NULL)
    *cp = 0;

Under Fedora, /bin/sh is a symlink to /bin/bash. Under Ubuntu, sh may be symlinked to something else. There may differences in how the shells do interpretation and how they set the default signal mask before invoking the target command.

Since Fedora works correctly, to get consistency, replace sh in your execlp with bash. This should make both distributions behave in a similar manner.

Craig Estey
  • 30,627
  • 4
  • 24
  • 48