SIGACTION handler to repeat loading after interruption - linux

Hi stackoverflow family,
I'm doing a uni task to make a linux program to read a password and secure it and "re-read" the password again if the user interrupts it during.
Here's my code for catching handler.
void catch_suspend(int sig_num)
{
printf("\nSuspending execution...\n");
fflush(stdout);
echo_on(YES); // re-enable echo mode
raise(SIGSTOP); // stop self
// we'll get back here when the process is resumed
printf("Resuming execution...\n");
echo_on(NO); // disable echo mode again
printf("Password: "); // reproduce the prompt
fflush(stdout);
}
and here is the main program
int main(int argc, char *argv[])
{
#define MAX_SIZE 30
char user[MAX_SIZE]; // user name supplied by the user
char passwd[MAX_SIZE]; // password supplied by the user
sigset_t sigs;
struct sigaction sa_new;
memset(&sa_new, 0, sizeof(sa_new)); // initialization to zeros
sa_new.sa_handler = catch_suspend; // set handler
sigemptyset(&sa_new.sa_mask); // mask: empty set
sigaddset(&sa_new.sa_mask, SIGINT); // mask: add SIGINT
sigaddset(&sa_new.sa_mask, SIGQUIT); // mask: add SIGQUIT
sa_new.sa_flags = 0; // no flags
printf("Username: "); // prompt the user for a user name
fflush(stdout);
fgets(user, MAX_SIZE, stdin); // wait for input
sigaction(SIGTSTP, &sa_new, &sa_old); // set the handler for SIGTSTP and get old handler setting
printf("Password: "); // prompt the user for a password
fflush(stdout);
echo_on(NO); // set input to no-echo mode
fgets(passwd, MAX_SIZE, stdin); // get the user input
echo_on(YES); // re-enable echo on input
printf("\n"); // the Enter pressed by the user was not echoed
fflush(stdout);
// verify the password (\n is stored, don't compare it)
etc...
return 0;
I should also block all signals except SIGINT and SIGQUIT, not sure if I've done it right with the mask. The problem I've encountered now is this: after interrupting during the password reading, process is stopped. That's ok. But after I use "fg" command to continue, my handler only writes the output and the program ends, but I need it to run fgets(passwd, MAX_SIZE, stdin) (where it was stopped) again.
I guess that I'm probably not setting the old_action correctly, but the manuals I've read didn't really make it more clear to me. Anyone who can lend a helping hand?

Okay, I've finally figured it out and I'm gonna leave it here for anyone having the same issues.
I didn't set the sigaction flag as followed:
sa_new.sa_flags = SA_RESTART;
This flag controls what happens when a signal is delivered during certain primitives (such as open, read or write), and the signal handler returns normally. There are two alternatives: the library function can resume, or it can return failure with error code EINTR.
The choice is controlled by the SA_RESTART flag for the particular kind of signal that was delivered. If the flag is set, returning from a handler resumes the library function. If the flag is clear, returning from a handler makes the function fail

Related

ISSUES with SIGCONT and waitpid() in LINUX

I'll try to keep it simple. I'm currently replicating a shell for LINUX. I use a linked list structure "job_list" to store all background processes. If a background process is terminated, then it is removed from the list. If a background process is suspended, its status within the list is changed from BACKGROUND to STOPPED. If the process is reawakened (through a SIGCONT signal), then the idea is that the process state inside the list should be changed back to BACKGROUND.
My problem is the following: when I send a SIGSTOP signal to a process, section //1 is executed and its change of state is successfully registered in the list. However, when I reawaken that same process using a SIGCONT signal, WIFCONTINUED(status) will return false, but WIFEXITED(status) will always return true. Consequently, section //3 is executed and the process is removed from the list.
What could be wrong? Thanks in advance.
void sigchld_handler (){
block_SIGCHLD();
job *item;
int l_size = list_size(job_list);
int i, new_pid, pid_wait, status, info;
enum status status_res;
for (i = 1; i <= l_size; i++){
item = get_item_bypos(job_list, i);
new_pid = item->pgid;
pid_wait = waitpid(new_pid, &status, WUNTRACED | WNOHANG);
if (WIFSTOPPED(status)){
//1
printf("****SUSPENDED\n");
item->state = STOPPED;
}else if (WIFCONTINUED(status)){
//2
printf("****CONTINUED\n");
item->state = BACKGROUND;
}else if (WIFEXITED(status)){
//3
printf("****EXITED\n");
l_size--;
i--;
delete_job(job_list, item);
}
}
print_job_list(job_list);
unblock_SIGCHLD();
}
You appear to be missing the WCONTINUED value in your call to waitpid.
From the waitpid specification:
pid_t waitpid(pid_t pid, int *stat_loc, int options);
The options argument is constructed from the bitwise-inclusive OR of zero or more of the following flags, defined in the header:
WCONTINUED
The waitpid() function shall report the status of any continued child process specified by pid whose status has not been reported since it continued from a job control stop.

Is it guaranteed that the kill() function will not return until the signal handler has done if a process kills itself?

Imagine the following program written in C:
void handler(int signo) {
write(STDOUT_FILENO, "handler\n", 8);
}
int main() {
signal(SIGUSR1, handler);
kill(getpid(), SIGUSR1);
write(STDOUT_FILENO, "after kill\n", 11);
}
If I run this program in Linux, is it possible the output is as follow:
after kill
handler
I tried many times, and the above result didn't appear.
If the SIGINT signal is delivered to the process after the puts("looping"); statement, then it will print the string "handler1" in the handle1 function. When the kill function returns, the output depends on when the SIGUSR1 signal will be delivered to the process. I think you can use the sigsuspend function to make sure it's what you want. By the way, before kill function returns, at least an unblocked signal is delivered to the process.
The following code outputs what you want and you need to block the SIGUSR1 signal first.
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo) {
write(STDOUT_FILENO, "handler\n", 8);
}
int main() {
sigset_t newmask, oldmask;
sigemptyset( &newmask );
sigaddset( &newmask, SIGUSR1 );
signal(SIGUSR1, handler);
// block the SIGUSR1 signal
sigprocmask ( SIG_BLOCK, &newmask, &oldmask );
kill(getpid(), SIGUSR1);
write(STDOUT_FILENO, "after kill\n", 11);
// reset the signal mask
sigprocmask ( SIG_SETMASK, &oldmask, NULL );
}
puts is not good inside signals handlers. Read signal(7), puts is not async-signal-safe. You should use write(2) inside a signal handler (not puts).
You have edited your question to use write instead of  puts
And if you insist in wrongly using puts you should at least call fflush. But both are wrong inside signal handlers.
(Don't forget that stdoutis buffered)
BTW, it might happen (notably in multi-threaded application, where the signal handler is not running in the same thread that kill(2)-s the signal) that the signal handler is invoked after returning of kill
I'm not sure that you are guaranteed that the signal handler of a single-threaded process is returning before kill, even if I believe that would happen on most Linux kernels. What you should be sure is that the signal handler would eventually be called (but you cannot be sure on when exactly). Also, a kernel is permitted to lose some signals (e.g. if an external thing or process is sending a lot of same signals). So called POSIX real-time signals are the exception, not the norm (see also this)
If you have an event loop (e.g. around poll(2)) and accept to have Linux-specific code
you could consider using signalfd(2) and polling it inside your event loop.

Pending signals not being delivered

I wrote this program to check blocking of SIGQUIT signal. The problem is after restoring to the previous state, the signal is pending but it isn't being delivered.
Here's the main program
sigset_t newmask, oldmask, pendmask;
if(signal(SIGQUIT,sig_quit)==SIG_ERR) //Register signal handler
printf("\nerror\n");
sigemptyset(&newmask);
sigaddset(&newmask,SIGQUIT);
sigprocmask(SIG_BLOCK,&newmask,&oldmask); //Save previous value and block SIGQUIT
sleep(5);
sigpending(&pendmask);
if(sigismember(&pendmask,SIGQUIT)){ //Check if SIGQUIT is pending
printf("\nSIGQUIT pending..\n");
fflush(stdout);
}
sigprocmask(SIG_BLOCK,&oldmask,NULL); //Restore old value of sigset
printf("\nSIGQUIT unblocked\n");
fflush(stdout);
sleep(5); //Sleep again to check if everything is A-okay!
I can understand that in the first sleep the signal is blocked. But even after unblocking, in the second sleep function, the program isn't responding to SIGQUIT.
What could be the possible problem here?
P.S I'm using CentOs
Here's the handler function
void sig_quit(int i)
{
printf("\nSIGQUIT caught\n");
fflush(stdout);
signal(SIGQUIT,SIG_DFL);
}
That's because you aren't unblocking SIGQUIT when you think you are:
sigprocmask(SIG_BLOCK,&oldmask,NULL); //Restore old value of sigset
With SIG_BLOCK, sigprocmask is adding oldmask to the currently blocked signals.
Try instead:
sigprocmask(SIG_SETMASK,&oldmask,NULL); //Restore old value of sigset
Furthermore, in sig_quit you are using printf which is not an async-signal-safe function. You can use, for example, write instead.

SIGINT signal re-install in linux

I am writing a program dealing with Linux signals. To be more specific, I want to re-install signal SIGINT in child process, only to find that it doesn't work.
Here is a simpler version of my code:
void handler(int sig){
//do something
exit(0);
}
void handler2(int sig){
//do something
exit(0);
}
int main(){
signal(SIGINT, handler);
if ((pid = fork()) == 0) {
signal(SIGINT, handler2); // re-install signal SIGINT
// do something that takes some time
printf("In child process:\n");
execve("foo", argv, environ); // foo is a executable in local dir
exit(0);
}else{
int status;
waitpid(pid, &status, 0); // block itself waiting for child procee to exit
}
return 0;
}
When shell is printing "In child process:", I press ctrl+c. I find that function handler is executed without problem, but handler2 is never executed.
Could you help me with this bug in my code?
Update:
I want the child process to receive SIGINT signal during foo running process, is that possible?
It is not a bug - calling execve has replaced the running binary image. The function handler2() (and any other function of your binary) is no longer mapped in the program memory having been replaced by the image of "foo" and therefore all signal settings are replaced to a default.
If you wish the signal handler to be active during "foo" run, you have to:
make sure the handler function is mapped into the memory of foo
a signal handler is registered after "foo" starts.
One way to do this is to create a shared library that contains the signal handler and an init function that is defined as a constructor that registers said signal handler and force it into the "foo" memory by manipulating the environment under which you execve foo (the environ variable) to include
LD_PRELOAD=/path/to/shared_library.so
#gby's anwser has given comprehensive background knowlegde. I am here to give another solution without shared library.
Every time child process stops or terminates, parent process will receive SIGCHLD. You can handler this SIGCHLD signal to know if child process was terminated by SIGINT. In your handler:
pid_t pid = waitpid(pid_t pid,int * status,int options)
You can get status of child process through waitpid function.
if(WIFSIGNALED(status) && (pid == child_pid)){
if(WTERMSIG(status) == SIGINT){
// now you know your foo has received SIGINT.
// do whatever you like.
}
}

Explicitly invoke SIG_DFL/SIG_IGN handlers on Linux

I've blocked, and then waited for a signal via the following code:
sigset_t set;
sigfillset(&set); // all signals
sigprocmask(SIG_SETMASK, &set, NULL); // block all signals
siginfo_t info;
int signum = sigwaitinfo(&set, &info); // wait for next signal
struct sigaction act;
sigaction(signum, NULL, &act); // get the current handler for the signal
act.sa_handler(signum); // invoke it
The last line generates a segmentation fault, as the handler is set to SIG_DFL (defined as 0). How can I manually invoke the default handler if it's set to SIG_DFL or SIG_IGN? Also note that SIG_IGN is defined as 1.
As you discovered you cannot invoke SIG_DFL and SIG_IGN per se. However, you can more-or-less mimic their behavior.
Briefly, imitating normal signal disposition would be:
quite easy for user-defined sa_handlers
easy enough for SIG_IGN, with the caveat that you'd need to waitpid() in the case of CHLD
straightforward but unpleasant for SIG_DFL, re-raising to let the kernel do its magic.
Does this do what you want?
#include <signal.h>
#include <stdlib.h>
/* Manually dispose of a signal, mimicking the behavior of current
* signal dispositions as best we can. We won't cause EINTR, for
* instance.
*
* FIXME: save and restore errno around the SIG_DFL logic and
* SIG_IGN/CHLD logic.
*/
void dispatch_signal(const int signo) {
int stop = 0;
sigset_t oset;
struct sigaction curact;
sigaction(signo, NULL, &curact);
/* SIG_IGN => noop or soak up child term/stop signals (for CHLD) */
if (SIG_IGN == curact.sa_handler) {
if (SIGCHLD == signo) {
int status;
while (waitpid(-1, &status, WNOHANG|WUNTRACED) > 0) {;}
}
return;
}
/* user defined => invoke it */
if (SIG_DFL != curact.sa_handler) {
curact.sa_handler(signo);
return;
}
/* SIG_DFL => let kernel handle it (mostly).
*
* We handle noop signals ourselves -- "Ign" and "Cont", which we
* can never intercept while stopped.
*/
if (SIGURG == signo || SIGWINCH == signo || SIGCONT == signo) return;
/* Unblock CONT if this is a "Stop" signal, so that we may later be
* woken up.
*/
stop = (SIGTSTP == signo || SIGTTIN == signo || SIGTTOU == signo);
if (stop) {
sigset_t sig_cont;
sigemptyset(&sig_cont);
sigaddset(&sig_cont, SIGCONT);
sigprocmask(SIG_UNBLOCK, &sig_cont, &oset);
}
/* Re-raise, letting the kernel do the work:
* - Set exit codes and corefiles for "Term" and "Core"
* - Halt us and signal WUNTRACED'ing parents for "Stop"
* - Do the right thing if we forgot to handle any special
* signals or signals yet to be introduced
*/
kill(getpid(), signo);
/* Re-block CONT, if needed */
if (stop) sigprocmask(SIG_SETMASK, &oset, NULL);
}
UPDATE
(in response to OP's excellent questions)
1: does this slot in after the sigwaitinfo?
Yes. Something like:
... block signals ...
signo = sigwaitinfo(&set, &info);
dispatch_signal(signo);
2: Why not raise those signals handled by SIG_IGN, they'll be ignored anyway
It's slightly more efficient to noop in userspace than by three syscalls (re-raise, unmask, re-mask). Moreover, CHLD has special semantics when SIG_IGNored.
3: Why treat SIGCHLD specially?
Originally (check answer edits) I didn't -- re-raised it in the SIG_IGN case,
because IGNored CHLD signals tell the kernel to automatically reap children.
However, I changed it because "natural" CHLD signals carry information about
the terminated process (at least PID, status, and real UID).
User-generated CHLD signals don't carry the same semantics, and, in my testing,
IGNoring them doesn't cause 2.6 to autoreap queued zombies whose SIGCHLD
was "missed." So, I do it myself.
4: Why are "stop" related signals unblocking CONT. Will not invoking the default handler for CONT unstop the process?
If we're stopped (not executing) and CONT is blocked, we will never receive the
signal to wake us up!
5: Why not call raise instead of the kill line you've given?
Personal preference; raise() would work, too.
I see 2 mistakes in your code :
1) You should reverse the last two lines like this :
act.sa_handler(signum);
sigaction(signum, NULL, &act);
2) You must pass a function handler to the fiedl sa_handler instead of a int. The prototype of the function shoudl look like this :
/**
*some where in you code
*/
void handler (int signal){ /*your code*/}
/**
*
*/
act.sa_handler = handler;
If you want the default handler to be invoked, you should set the field sa_handler to SIG_DFL and it should work.
I'm not aware of the way to do it.
Only suggestion I have is to look into the man 7 signal and perform manually the action according the table you see there. Ign is nothing. Core is call to abort(). Term is _exit().
Of course you can also set signal handler back to SIG_DFL and then kill(getpid(),THE_SIG) (or its equivalent raise(THE_SIG)). (I personally do not like raise because on some systems it might produce some messages on stderr.)

Resources