Reputation: 53
I'm in the first chapter of Advanced Programming in the UNIX Environment 2nd ed. and implemented the program in fig 1.10 using sigaction()
instead of signal()
(and )
as the prompt instead of %
).
#include<stdio.h>
#include<errno.h>
#include<signal.h>
#include<stdarg.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<unistd.h>
#define MAXLINE 1024
static void mysigint(int signo);
struct sigaction mysigaction = {
mysigint,
0,
0,
0,
0
};
static void err_ret(const char *fmt, ...);
int main(void) {
char buf[MAXLINE];
pid_t pid;
int status;
if (sigaction(SIGINT, &mysigaction, NULL) < 0) {
err_ret("Signal error");
exit(1);
}
// signal(SIGINT, mysigint);
printf(") ");
while (fgets(buf, MAXLINE, stdin) != NULL) {
if (buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = 0;
if ((pid = fork()) < 0) {
perror("Fork error");
exit(1);
} else if (pid == 0) { // Child
execlp(buf, buf, (char *)0);
err_ret("Couldn't execute: %s",buf);
exit(127);
}
if ((pid = waitpid(pid, &status, 0)) < 0) { // Parent
perror("Waitpid error");
exit(1);
}
printf(") ");
}
if (errno)
perror("Shell exit error");
else
printf("Exiting shell...\n");
exit(0);
}
static void err_ret(const char *fmt, ...) {
va_list ap;
char buf[MAXLINE];
va_start(ap, fmt);
vsnprintf(buf, MAXLINE, fmt, ap);
perror(buf);
}
static void mysigint(int signo) {
printf("Interrupt signal caught\n) ");
}
This implements a primitive shell that, when receiving SIGINT
, is supposed to print a string then continue. The book uses signal()
, but the man page says to use sigaction()
instead and the book is pretty old so I went with sigaction()
at first.
When sending a SIGINT
to the program when its handler is installed by sigaction()
, the handler is run but the while loop is still exited. The perror()
output seems to suggest the SIGINT
is interrupting the read()
inside fgets()
?
$ ./a.out
) ^CInterrupt signal caught
Shell exit error: Interrupted system call
) $
Furthermore the final )
in the handler's printf()
is delayed being printed until after the while loop exits.
So I tried replacing the call to sigaction()
with one to signal()
. With mysigint
installed with signal()
, this happens:
$ ./a.out
) ^CInterrupt signal caught
<empty line, which I then type RETURN while on>
Couldn't execute: : No such file or directory
) ) )
So the while loop isn't exited, but the right-paren in the handler is still delayed being printed, and when it is three are printed instead of one. I think that one is being stored in a buffer and a second at the bottom of the while loop before I press return, and then those two are printed plus the third prompt after pressing enter and executing an empty command. But I'm not sure.
Finally I went back to sigaction()
but passed SIG_IGN
instead of mysigint
. It behaved about as I expected:
$ ./a.out
) ^C
I also tried testing the sigaction()
and signal()
variants in gdb but they both behaved differently there.
My question is: how do I implement a SIGINT
handler that behaves as the book wants? That is, printing "Interrupt signal caught" and then immediately printing a new shell prompt.
In case it helps I'm on Linux Mint 20.
Upvotes: 1
Views: 596
Reputation: 1224
The perror() output seems to suggest the SIGINT is interrupting the read() inside fgets()
If read system call is being executed, and a signal comes, the read would return with an error and errno is set to EINTR. This is true for all system calls, or at least, the system calls that block. You can change fgets to read and check for errors given by read. If read returns -1 and errno is EINTR, you can print the string, "Interrupt signal caught\n) ".
Upvotes: 1