0

I'm trying to write a shell in c. Part of it is a handler to catch the SIGTSTP signal and set it to bringing programs in and out of foreground-only mode.

Here are the relevant snippets of code:

//global variables
int global;

//header
void catch_tstp(int);

//main function
int main(int argc, char** argv){
    ...

    // initiate sigaction struct for CTRL-Z action
    struct sigaction ctrlz_act;
    ctrlz_act.sa_handler = catch_tstp;
    ctrlz_act.sa_flags = SA_SIGINFO|SA_RESTART;
    sigfillset(&(ctrlz_act.sa_mask));
    sigaction(SIGTSTP, &ctrlz_act, NULL);
    global = 0;

    ...
}

//handler
void catch_tstp(int sig){
    if(sig == SIGTSTP){
        if(global){
            global=0;
            printf("Entering foreground-only mode (& is now ignored)\n");
        }
        else{
            global=1;
            printf("Exiting foreground-only mode\n");
        }
    }
}

Right now my output looks like this:

: ^ZExiting foreground-only mode             //pressed ctrl+z
             //nothing here, had to press enter again
:            //pressing enter just gives me another :, which is what I want
: ^ZEntering foreground-only mode (& is now ignored)
^ZExiting foreground-only mode

I'm hoping the output can look like this istead:

: ^Z
Entering foreground-only mode (& is now ignored)
:   //":"should show up own automatically on the next line after I press ctrl-z, then enter 
: ^Z
Exiting foreground-only mode
:

Can anyone point me to what I'm doing wrong? Any help would be greatly appreciated. Thank you!

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Aisha Ashwal
  • 83
  • 3
  • 11
  • For one thing, if you want to safely access a non-constant file-scope variable such as `global` from your signal handler, it must be `volatile` and have type `sig_atomic_t`. I suspect your failure to do so is not directly responsible for your problem, however. – John Bollinger Feb 14 '17 at 18:29
  • I revised `global` accordingly, but you were right it did not fix the problem. – Aisha Ashwal Feb 14 '17 at 18:41
  • Also, you should not specify the `SA_SIGINFO` flag to `sigaction()`, because you are using the `sa_handler` member of your `struct sigaction`, not the `sa_sigaction` member. This is a significant problem, but I'll bet removing the flag does not change the observed behavior. – John Bollinger Feb 14 '17 at 18:42
  • I cannot replicate the behavior you describe based on the code you have provided. I presume your program is doing some kind of I/O when you type ^Z, such as attempting to read a command. This is likely relevant to the observed behavior, but also, it is a case wherein the shell should silently consume the signal, since it itself is the foreground process. – John Bollinger Feb 14 '17 at 18:58
  • Do not use the bash tag when implementing your own shell, unless you're implementing it *in bash*; bash is a very specific interpreter that is a large superset of the POSIX sh specification, whereas toy shells like this one are tiny subsets -- they have almost nothing in common. For that matter, the shell tag's wiki makes it clear that its focus is on questions seeking answers in POSIX-compliant languages, not questions about *implementing* shell-like programs. – Charles Duffy Feb 14 '17 at 20:59
  • Sorry I forgot to specify, I AM implementing it in bash. – Aisha Ashwal Feb 15 '17 at 00:30
  • @AishaAshwal, no you are implementing it in C, as the code clearly shows. You are presumably *running* it via `bash`, but that's quite different. There is nothing `bash`-specific in the question. – John Bollinger Feb 15 '17 at 14:21
  • Are you supposed to catch the signal generated by ^Z or are you supposed to take an action when the user types ^Z followed by Enter? It may be best to paste your assignment in verbatim so that we're all clear on what you need to do. – Mark Plotnick Feb 15 '17 at 17:14

1 Answers1

2

You have several problems with your signal handler and how you're setting it up.

As I already noted in comments, a signal handler can safely access a file-scope variable such as global only if that variable is volatile and has type sig_atomic_t. If it attempts to access any other variable then there is a risk that it will not see the most recent value set for that variable, that code outside the scope of the handler will not see the value, if any, that the handler writes to that variable, and that the value of the variable will be corrupted as a result of the signal-handler's writes to it being non-atomic.

A more serious problem, also already noted in comments, is that specifying the SA_SIGINFO flag to sigaction(), as you do, tells it that you have specified the handler via the sa_sigaction member of your struct sigaction, whereas you have actually specified it via the sa_handler member. If you specify the handler via sa_handler then you must omit SA_SIGINFO from the flags, and vice versa. If you specify the the SA_SIGINFO flag inappropriately then receipt of the signal will cause undefined behavior, either by calling your handler with the wrong number of arguments or by trying to call a function via a garbage function pointer.

Furthermore, all functions called by a signal handler must be async-signal-safe. The entry for 'signal' in chapter 7 of the manual contains a list of functions that are safe to call, and printf() is not among them.

But the main issue interfering with your desire that

":"should show up own automatically on the next line after I press ctrl-z

appears to be that you've included SA_RESTART among the sigaction flags. That flag causes (some) system functions that are interrupted by receipt of the signal to be automatically restarted after the signal handler finishes, instead of returning an EINTR error or a partial result to their callers. In your particular case, if your shell receives a SIGSTP while reading a command, restarting the read means that control does not return to the shell for it to print a new prompt. It may very well also mean that characters entered before the ^Z are lost, depending on details of how you read the command.


Additionally, from your program's messaging, it seems like you may have a misconception about the meaning and use of ^Z and the SIGSTP by the terminal interface and the standard shell. These serve a job-control function for jobs executed by the shell; they are not primarily about the mode of the shell itself. There is no need for the shell to maintain a flag or to behave differently after receiving a ^Z (outside the scope of the signal handler itself) -- the needed interactive behavior follows primarily from the fact that the shell can read from the standard input only when it is in the foreground.

When the shell receives a SIGSTP,

  • if it is in the foreground, it should do nothing (and avoid losing unprocessed input).
  • if it is in the background, it should send a SIGSTOP (note the difference) to the foreground process group, and put itself back in the foreground if necessary. It may in this case emit a message and / or prompt.
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Thank you for the detailed explanation. I'm a bit confused about the last part. In my shell, before I added the `^Z` handler, whenever I input `^Z` my shell outputs `[1]+ Stopped`. What I want it to do is, as described in the original post, go into foreground-only mode while displaying "Entering/Exiting foreground-only mode". After fixing the SA_RESTART flag, it's still not doing that, so I'm assuming the problem is in the printf() part, as you pointed out. Any suggestion on how I can fix that? – Aisha Ashwal Feb 15 '17 at 00:00
  • @AishaAshwal, you are mistaken. If your shell has no handler at all for `SIGSTP` then *its parent shell* produces the output you describe when you type ^Z. In any event, I did not suggest that you shouldn't implement a handler. The last part of my answer is about what the handler should *do*. – John Bollinger Feb 15 '17 at 14:20
  • @AishaAshwal, as for your continued difficulties after addressing the points I raised, it would be pointless for me to speculate about code you have not presented. – John Bollinger Feb 15 '17 at 14:26
  • From the OP's [previous question](http://stackoverflow.com/questions/42211242/shell-program-use-ctrl-z-to-bring-program-to-foreground), it appears that their assignment is to take a specific action when ^Z is typed (toggling back and forth to a special mode where subsequent commands are never run in the background), not to perform what has historically been the function of ^Z in a shell. – Mark Plotnick Feb 15 '17 at 17:07
  • Acknowledged, @MarkPlotnick, and that is what I in fact answered in the main body of my answer. My addendum about the behavior of the terminal interface and the *standard* shell may therefore be of limited interest to the OP, but I think they are useful for avoiding confusion for others who may come across this answer. – John Bollinger Feb 15 '17 at 17:54