views:

88

answers:

3

I have written a program that invokes a system command from inside:

#include <stdlib.h>

int main(void)
{
    while(1)
    {
        system("ls 2>&1 1>/dev/null"); // comment this line out to enable ctrl+break
    }

    return 0;
}

However, when it is running, CTRL+C and CTRL+BREAK no longer work and appear to be ignored.

I am trying to write a program that performs some operations in the background involving the shell, but I also want to be able to break out of the program when the user wants to break.

Is there a way to make it work the way I want? Should I change the architecture to perform some kind of fork / exec?

+2  A: 

According to IEEE Std 1003.1-2008 (POSIX):

  • The system() function shall behave as if a child process were created using fork(), ...

  • The system() function shall ignore the SIGINT and SIGQUIT signals, and shall block the SIGCHLD signal, while waiting for the command to terminate. If this might cause the application to miss a signal that would have killed it, then the application should examine the return value from system() and take whatever action is appropriate to the application if the command terminated due to receipt of a signal.

  • The system() function shall not return until the child process has terminated.

The MYYN
+4  A: 

From the POSIX specification for system():

The system() function ignores the SIGINT and SIGQUIT signals, and blocks the SIGCHLD signal, while waiting for the command to terminate. If this might cause the application to miss a signal that would have killed it, then the application should examine the return value from system() and take whatever action is appropriate to the application if the command terminated due to receipt of a signal.

So, in order to respond properly to signals, you need to examine the return value of system().

system() returns the termination status of the command language interpreter in the format specified by waitpid()

And the docs of waitpid() refer to the docs for wait(), which instruct you to use the following macros to find out why a process exited:

  • WIFEXITED(stat_val)
    Evaluates to a non-zero value if status was returned for a child process that terminated normally.
  • WEXITSTATUS(stat_val)
    If the value of WIFEXITED(stat_val) is non-zero, this macro evaluates to the low-order 8 bits of the status argument that the child process passed to _exit() or exit(), or the value the child process returned from main().
  • WIFSIGNALED(stat_val)
    Evaluates to non-zero value if status was returned for a child process that terminated due to the receipt of a signal that was not caught (see ).
  • WTERMSIG(stat_val)
    If the value of WIFSIGNALED(stat_val) is non-zero, this macro evaluates to the number of the signal that caused the termination of the child process.
  • WIFSTOPPED(stat_val)
    Evaluates to a non-zero value if status was returned for a child process that is currently stopped.
  • WSTOPSIG(stat_val)
    If the value of WIFSTOPPED(stat_val) is non-zero, this macro evaluates to the number of the signal that caused the child process to stop.
  • WIFCONTINUED(stat_val)
    Evaluates to a non-zero value if status was returned for a child process that has continued from a job control stop.

Here is an example of how you would use this information, without having to fork a separate process. Note that you won't actually receive the signal in the parent process, but you can determine the signal sent to the child process:

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

int main(void)
{
    while(1)
    {
        int result = system("ls 2>&1 1>/dev/null");
        if (WIFEXITED(result)) {
          printf("Exited normally with status %d\n", WEXITSTATUS(result));
        } else if (WIFSIGNALED(result)) {
          printf("Exited with signal %d\n", WTERMSIG(result));
          exit(1);
        } else {
          printf("Not sure how we exited.\n");
        }
    }

    return 0;
}

And if you run it, you get:

$ ./sys
Exited normally with status 0
Exited normally with status 0
Exited normally with status 0
Exited normally with status 0
Exited normally with status 0
Exited normally with status 0
^CExited with signal 2
Brian Campbell
@pmg fixed to specifically mention POSIX.
Brian Campbell
Does the system() call exit immediately when SIGINT is issued? I think this is what the OP was having issues with.
San Jacinto
@San Jacinto Yes, the process spawned by `system` receives `SIGNINT` normally, and if it doesn't catch it, then it will be killed, and `system` will return immediately. If you need to be able to handle `SIGINT` yourself even if the child catches it, then you will need to use the extra process like you proposed.
Brian Campbell
+1  A: 

From San Jacinto's comment above:

system() essentially forks, blocks the parent, and ignores certain signals in the child, as per the POSIX spec links. You can bypass this by first creating another process for system() to block. This leaves the original process (the grandparent of the process which the shell is running on) free to accept kill signals.

#include <stdlib.h>
#include <unistd.h>
#include <wait.h>

int main(void)
{
    pid_t pid;

    while(1)
    {
        pid = fork();

        if(pid > 0) // parent
        {
            wait(0);
        }
        else if(pid == 0) // child
        {
            system("ls 2>&1 1>/dev/null");
            return 0;
        }
        else // could not fork
        {
            return 1;
        }
    }

    return 0;
}

On the surface, this appears to do what I need.

Martin
This is a bit more complicated than what I suggested in my answer. You don't need to fork off a separate process; all you need to do is check the return value of `system`. I've updated my answer with an example of how to do this.
Brian Campbell