4

I'm creating a shell program, and I want Ctrl+C to kill foreground processes but not background processes for example; &sleep 50 is a background process, and I want it to be so that if I were to use Ctrl+C it would kill any foreground processes, and leave the background unaffected. But for the life of me can't figure out what to do, any help is greatly appreciated :D

int main(void) {
  Command cmd;
  int n, forSignals;
  char **cmds;
  char **pipedCmds;
  signal(SIGINT, INThandler);
  while (!done) {
    char *line;
    line = readline("> ");
    if (!line) {
      done = 1;
    } else {
      stripwhite(line);
      if(*line) {
        add_history(line);
        n = parse(line, &cmd);
        PrintCommand(n, &cmd);
        cmds = getCmds(cmd.pgm);
        pipedCmds = getPipedCmds(cmd.pgm);
        executionDecider(line, cmds, pipedCmds, cmd);
      }
    }
    if(line) {
      free(line);
    }
  }
  return 0;
}

void  INThandler(int sig)
{
  signal(sig, SIG_IGN);
  kill(SIGINT, 0);
  printf("\n");
}

P.S. There is of course the rest of the code on actually executing programs, let me know if it's necessary to be shown, but I believe, this is a good minimally reproducible example.

EDIT: Quite important, don't know how I forgot :/ but I need it to not create a zombie process by doing this, it shouldn't leave ANY zombies behind.

EDIT: Please find linked a URL leading to a code dump of the full project. It may make more sense there: https://codedump.io/share/d8hrj40JdEqL/1/lshc---c-shell-program

Misha Akopov
  • 12,241
  • 27
  • 68
  • 82
Alex
  • 394
  • 1
  • 4
  • 15
  • I may be missing something, but I think we need to see more here. The UNIX tty _already_ turns a Ctrl-C into a SIGINT directed at the foreground process group. You don’t need to do anything except [manage job control process groups](https://stackoverflow.com/a/10779506/132382) correctly. – pilcrow Dec 21 '19 at 15:26
  • Can you add `Command` and the other missing functions? – S.S. Anne Dec 30 '19 at 23:41

3 Answers3

2

By default STDIN of all background process is disabled. You can check with sudo ls -al /proc/<"pid">/fd. In that case any of the input can not be transferred from terminal to background process. So even after ctrl+c it will not get terminated. And only the foreground process will get terminated.

++

As per man page int kill(pid_t pid, int sig); Here you are doing kill(SIGINT,0); As per your code, second argument 0 means Hangup signal (SIGHUP) which comes only when shell is get terminated. It terminates all process running in that shell. Update the kill function calling.

++ You have mentioned "&sleep 50" this is not valid in linux.So which OS are you using.

UpendraG
  • 29
  • 3
2

I think your core question is how to interpose on the standard signal semantics of a process group. By default all the processes that you fork() remain within the process group you belong to. A system call, setpgid(), can create a new process group, divorcing group signal semantics for the new processes.

Certain signals are delivered to process groups. Notifications from the tty driver are broadcast to the process group which the session leader of the tty currently belongs to. Simple, right :-?

So, what you want to do is use setpgid() in the child processes you start to create a new process group. Any processes they subsequently start will inherit their new process group; so they have no way back to the original, root, process group [ I think, the ground is uneven here ].

I've written a sample program which creates a little set of processes which just hang around sleeping until they are sent a SIG_TERM signal. Each of these processes are placed in their own process group ( waste of resources, but much smaller code ). When you hit Control-C on this program running, it announces its SIG_INT, then delivers a SIG_TERM to all the processes it started. When you hit Control-D, the main process exits.

You can tell this program works if you input Control-C, Control-D, you should find with ps/pgrep that no residual children exist, and the output screen display the correctly configured (NPROC) number of processes receiving a SIG_TERM (15).

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#ifndef NPROC
#define NPROC 7
#endif
static int children[NPROC];
static int nproc;

static void SigPrint(char *act, int signo) {
    char buf[100];
    int n;
    n = sprintf(buf, "[%d]:(%d) -> %s\n", getpid(), signo, act);
    write(1, buf, n);
}

static void SigDie(int signo) {
    SigPrint("Die", signo);
    _exit(1);
}

static void SigDisp(int signo) {
    SigPrint("Dispatch", signo);
    int i;
    for (i = 0; i < nproc; i++) {
        if (kill(children[i], SIGTERM) < 0) {
            perror("kill");
        }
    }
}

int main(void) {
    signal(SIGINT, SigDisp);
    signal(SIGTERM, SigDie);
    for (nproc = 0; nproc < NPROC; nproc++) {
        children[nproc] = fork();
        if (children[nproc] == 0) {
            signal(SIGINT, SIG_DFL);
            if (setpgid(0, 0) < 0) {
                perror("setpgid");
                _exit(1);
            }
            while (sleep(5)) {
            }
            _exit(0);
        } else if (children[nproc] < 0) {
            perror("fork");
            kill(getpid(), SIGINT);
            perror("kill");
            _exit(1); /*Just in case... */
        }
    }
    int c;
    while ((c = getchar()) != EOF) {
        putchar(c);
    }
    return 0;
}

When you run this, you should get output something like:

^C[15141]:(2) -> Dispatch
[15142]:(15) -> Die
[15143]:(15) -> Die
[15146]:(15) -> Die
[15144]:(15) -> Die
[15145]:(15) -> Die
[15147]:(15) -> Die
[15148]:(15) -> Die

with different values for the [pid] field.

This program shows how to divorce child processes from the notifications of the parents. It could be much better, the model here is quite rich.

mevets
  • 10,070
  • 1
  • 21
  • 33
  • Note that `sprintf()` is not guaranteed to be [async-signal-safe by POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03). It's [not safe on Linux](http://man7.org/linux/man-pages/man7/signal-safety.7.html) where it's [merely MT-safe](http://man7.org/linux/man-pages/man3/sprintf.3.html), but it is [async-signal-safe on Solaris](https://docs.oracle.com/cd/E36784_01/html/E36874/sprintf-3c.html), for example. – Andrew Henle Jan 06 '20 at 11:03
  • Thanks, that is stunning. Locale ruins everything. – mevets Jan 06 '20 at 17:33
0

I assume that you send your Ctrl-C via kill -2 so that you can easily interrupt a background process.

You can easily find out if your process is running fore- or background by checking the terminal foreground process group:

#include <unistd.h>

int isForeground() 
{
    pid_t pid = tcgetpgrp(STDIN_FILENO);
    if(pid == -1 /* piped */ || pid == getpgrp() /* foreground */)
        return 1;
    /* otherwise background */
    return 0;
}

Use that function to decide whether or not to register your signal handler with the signal() function

if (isForeground())
    // register my custom signal handler
    if (SIG_ERR == signal(SIGINT, mySignalHandler))
        printf("unable to catch SIGINT!\n");

or inside your signal handler function

void handler(int sig)
{
    // background processes shall be left unaffected
    if (0 == isForeground())
        return;

    // otherwise process the signal accordingly, e.g. cleaning things up
    // and using exit() to terminate
    ...
}

This works on my Linux system. Please mind that the semantics of signal() may vary across different Unix systems (BSD, SysV) which could affect the portability of this code.

Twonky
  • 796
  • 13
  • 31