I am writing a simple shell program in c, and need to handle ctrl-c.
If a foreground process is running, I need to terminate it and continue the main shell loop. If not, I need to do nothing but print that the signal was caught.
Below is my code, based on this thread: Catch Ctrl-C in C
void inthandler(int dummy){
signal(dummy, SIG_IGN);
printf("ctrl-c caught\n");
}
and I call signal() right before entering my main loop
int main(int argc, char*argv[]){
signal(SIGINT, inthandler)
while(true){
//main loop
}
}
As of now, I am able to intercept ctrl-c and print my intended message, but any further input results in a segfault.
How can I return to execution of my main loop after I enter inthandler?
Use sigaction(), not signal(), except when setting the disposition to SIG_DFL or SIG_IGN. While signal() is specified in C, and sigaction() POSIX.1, you'll need POSIX to do anything meaningful with signals anyway.
Only use async-signal safe functions in signal handlers. Instead of standard I/O (as declared in <stdio.h>), you can use POSIX low-level I/O (read(), write()) to the underlying file descriptors. You do need to avoid using standard I/O to streams that use those same underlying descriptors, though, or the output may be garbled due to buffering in standard I/O.
If you change the signal disposition in the signal handler to ignored, only the first signal (of each caught type) will be caught.
Consider the following example program:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Helper function: Write a string to a descriptor, keeping errno unchanged.
Returns 0 if success, errno error code if an error occurs. */
static inline int wrs(const int fd, const char *s)
{
/* Invalid descriptor? */
if (fd == -1)
return EBADF;
/* Anything to actually write? */
if (s && *s) {
const int saved_errno = errno;
const char *z = s;
ssize_t n;
int err = 0;
/* Use a loop to find end of s, since strlen() is not async-signal safe. */
while (*z)
z++;
/* Write the string, ignoring EINTR, and allowing short writes. */
while (s < z) {
n = write(fd, s, (size_t)(z - s));
if (n > 0)
s += n;
else
if (n != -1) {
/* This should never occur, so it's an I/O error. */
err = EIO;
break;
} else
if (errno != EINTR) {
/* An actual error occurred. */
err = errno;
break;
}
}
errno = saved_errno;
return err;
} else {
/* Nothing to write. NULL s is silently ignored without error. */
return 0;
}
}
/* Signal handler. Just outputs a line to standard error. */
void catcher(int signum)
{
switch (signum) {
case SIGINT:
wrs(STDERR_FILENO, "Caught INT signal.\n");
return;
default:
wrs(STDERR_FILENO, "Caught a signal.\n");
return;
}
}
/* Helper function to install the signal handler. */
int install_catcher(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = catcher;
act.sa_flags = SA_RESTART; /* Do not interrupt "slow" functions */
if (sigaction(signum, &act, NULL) == -1)
return -1; /* Failed */
return 0;
}
/* Helper function to remove embedded NUL characters and CRs and LFs,
as well as leading and trailing whitespace. Assumes data[size] is writable. */
size_t clean(char *data, size_t size)
{
char *const end = data + size;
char *src = data;
char *dst = data;
/* Skip leading ASCII whitespace. */
while (src < end && (*src == '\t' || *src == '\n' || *src == '\v' ||
*src == '\f' || *src == '\r' || *src == ' '))
src++;
/* Copy all but NUL, CR, and LF chars. */
while (src < end)
if (*src != '\0' && *src != '\n' && *src != '\r')
*(dst++) = *(src++);
else
src++;
/* Backtrack trailing ASCII whitespace. */
while (dst > data && (dst[-1] == '\t' || dst[-1] == '\n' || dst[-1] == '\v' ||
dst[-1] == '\n' || dst[-1] == '\r' || dst[-1] == ' '))
dst--;
/* Mark end of string. */
*dst = '\0';
/* Return new length. */
return (size_t)(dst - data);
}
int main(void)
{
char *line = NULL;
size_t size = 0;
ssize_t len;
if (install_catcher(SIGINT)) {
fprintf(stderr, "Cannot install signal handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("Type Ctrl+D, 'exit', or 'quit' on an empty line to exit.\n");
while (1) {
len = getline(&line, &size, stdin);
if (len < 0)
break;
clean(line, len);
printf("Read %zd chars: %s\n", len, line);
if (!strcmp(line, "exit") || !strcmp(line, "quit"))
break;
}
return EXIT_SUCCESS;
}
On most POSIXy systems, Ctrl+C in a pseudoterminal also clears the current line buffer, so pressing it in the middle of interactively supplying a line will discard the line (the data not sent to the process).
Note that the ^C you normally see in pseudoterminals when you press Ctrl+C is a terminal feature, controlled by the ECHO termios setting. That setting also controls whether keypresses in general are echoed on the terminal.
If you add signal(SIGINT, SIG_IGN) to catcher(), just after the wrs() line, only the first Ctrl+C will print "Caught INT signal"; any following Ctrl+C will just discard the the incomplete line.
So, if you type, say, FooCtrl+CBarEnter, you'll see either
Foo^CCaught INT signal.
Bar
Read 4 chars: Bar
or
Foo^CBar
Read 4 chars: Bar
depending on whether the INT signal generated by Ctrl+C is caught by the handler, or ignored, at that point.
To exit, type exit or quit at the start of a line, or immediately after a Ctrl+C.
There are no segmentation faults here, so if your code does generate one, it must be due to a bug in your program.
How can I return to execution of my main loop after I enter inthandler?
Signal delivery interrupts the execution for the duration of executing the signal handler; then, the interrupted code continues executing. So, the strictly correct answer to that question is by returning from the signal handler.
If the signal handler is installed with the SA_RESTART flag set, then the interrupted code should continue as if nothing had happened. If the signal handler is installed without that flag, then interrupting "slow" system calls may return an EINTR error.
The reason errno must be kept unchanged in a signal handler -- and this is a bug many, many programmers overlook -- is that if an operation in the signal handler changes errno, and the signal handler gets invoked right after a system or C library call failed with an error code, the errno seen by the code will be the wrong one! Granted, this is a rare case (the time window where this can occur is tiny for each system or C library call), but it is a real bug that can occur. When it does occur, it is the kind of Heisenbug that causes developers to rip out their hair, run naked in circles, and generally go more crazy than they already are.
Also note that stderr is only used in the code path where installing the signal handler fails, because I wanted to be sure not to mix I/O and POSIX low-level I/O to standard error.
Related
I have a writer process which outputs its status at regular intervals as a readable chunck of wchar_t.
I would need to ensure the following properties:
When there's and update, the readers shouldn't read partial/corrupted data
The file should be volatile in memory so that when the writer quits, the file is gone
The file content size is variable
Multiple readers could read the file in parallel, doesn't matter if the content is synced, as long as is non partial for each client
If using truncate and then write, clients should only read the full file and not observe such partial operations
How could I implement such /procfs-like file, outside /procfs filesystem?
I was thinking to use classic c Linux file APIs and create something under /dev/shm by default, but I find it hard to implement effectively point 1 and 5 most of all.
How could I expose such file?
Typical solution is to create a new file in the same directory, then rename (hardlink) it over the old one.
This way, processes see either an old one or a new one, never a mix; and it only depends on the moment when they open the file.
The Linux kernel takes care of the caching, so if the file is accessed often, it will be in RAM (page cache). The writer must, however, remember to delete the file when it exits.
A better approach is to use fcntl()-based advisory record locks (typically over the entire file, i.e. .l_whence = SEEK_SET, .l_start = 0, .l_len = 0).
The writer will grab a write/exclusive lock before truncating and rewriting the contents, and readers a read/shared lock before reading the contents.
This requires cooperation, however, and the writer must be prepared to not be able to lock (or grabbing the lock may take undefined amount of time).
A Linux-only scheme would be to use atomic replacement (via rename/hardlinking), and file leases.
(When the writer process has an exclusive lease on an open file, it gets a signal whenever another process wants to open that same file (inode, not file name). It has at least a few seconds to downgrade or release the lease, at which point the opener gets access to the contents.)
Basically, the writer process creates an empty status file, and obtains exclusive lease on it. Whenever the writer receives a signal that a reader wants to access the status file, it writes the current status to the file, releases the lease, creates a new empty file in the same directory (same mount suffices) as the status file, obtains an exclusive lease on that one, and renames/hardlinks it over the status file.
If the status file contents do not change all the time, only periodically, then the writer process creates an empty status file, and obtains exclusive lease on it. Whenever the writer receives a signal that a reader wants to access the (empty) status file, it writes the current status to the file, and releases the lease. Then, when the writer process' status is updated, and there is no lease yet, it creates a new empty file in the status file directory, takes an exclusive lease on it, and renames/hardlinks over the status file.
This way, the status file is always updated just before a reader opens it, and only then. If there are multiple readers at the same time, they can open the status file without interruption when the writer releases the lease.
It is important to note that the status information should be collected in a single structure or similar, so that writing it out to the status file is efficient. Leases are automatically broken if not released soon enough (but there are a few seconds at least to react), and the lease is on the inode – file contents – not the file name, so we still need the atomic replacement.
Here's a crude example implementation:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdarg.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define LEASE_SIGNAL (SIGRTMIN+0)
static pthread_mutex_t status_lock = PTHREAD_MUTEX_INITIALIZER;
static int status_changed = 0;
static size_t status_len = 0;
static char *status = NULL;
static pthread_t status_thread;
static char *status_newpath = NULL;
static char *status_path = NULL;
static int status_fd = -1;
static int status_errno = 0;
char *join2(const char *src1, const char *src2)
{
const size_t len1 = (src1) ? strlen(src1) : 0;
const size_t len2 = (src2) ? strlen(src2) : 0;
char *dst;
dst = malloc(len1 + len2 + 1);
if (!dst) {
errno = ENOMEM;
return NULL;
}
if (len1 > 0)
memcpy(dst, src1, len1);
if (len2 > 0)
memcpy(dst+len1, src2, len2);
dst[len1+len2] = '\0';
return dst;
}
static void *status_worker(void *payload __attribute__((unused)))
{
siginfo_t info;
sigset_t mask;
int err, num;
/* This thread blocks all signals except LEASE_SIGNAL. */
sigfillset(&mask);
sigdelset(&mask, LEASE_SIGNAL);
err = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (err)
return (void *)(intptr_t)err;
/* Mask for LEASE_SIGNAL. */
sigemptyset(&mask);
sigaddset(&mask, LEASE_SIGNAL);
/* This thread can be canceled at any cancellation point. */
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
while (1) {
num = sigwaitinfo(&mask, &info);
if (num == -1 && errno != EINTR)
return (void *)(intptr_t)errno;
/* Ignore all but the lease signals related to the status file. */
if (num != LEASE_SIGNAL || info.si_signo != LEASE_SIGNAL || info.si_fd != status_fd)
continue;
/* We can be canceled at this point safely. */
pthread_testcancel();
/* Block cancelability for a sec, so that we maintain the mutex correctly. */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_mutex_lock(&status_lock);
status_changed = 0;
/* Write the new status to the file. */
if (status && status_len > 0) {
const char *ptr = status;
const char *const end = status + status_len;
ssize_t n;
while (ptr < end) {
n = write(status_fd, ptr, (size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (n != -1) {
if (!status_errno)
status_errno = EIO;
break;
} else
if (errno != EINTR) {
if (!status_errno)
status_errno = errno;
break;
}
}
}
/* Close and release lease. */
close(status_fd);
status_fd = -1;
/* After we release the mutex, we can be safely canceled again. */
pthread_mutex_unlock(&status_lock);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
}
}
static int start_status_worker(void)
{
sigset_t mask;
int result;
pthread_attr_t attrs;
/* This thread should block LEASE_SIGNAL signals. */
sigemptyset(&mask);
sigaddset(&mask, LEASE_SIGNAL);
result = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (result)
return errno = result;
/* Create the worker thread. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 2*PTHREAD_STACK_MIN);
result = pthread_create(&status_thread, &attrs, status_worker, NULL);
pthread_attr_destroy(&attrs);
/* Ready. */
return 0;
}
int set_status(const char *format, ...)
{
va_list args;
char *new_status = NULL;
int len;
if (!format)
return errno = EINVAL;
va_start(args, format);
len = vasprintf(&new_status, format, args);
va_end(args);
if (len < 0)
return errno = EINVAL;
pthread_mutex_lock(&status_lock);
free(status);
status = new_status;
status_len = len;
status_changed++;
/* Do we already have a status file prepared? */
if (status_fd != -1 || !status_newpath) {
pthread_mutex_unlock(&status_lock);
return 0;
}
/* Prepare the status file. */
do {
status_fd = open(status_newpath, O_WRONLY | O_CREAT | O_CLOEXEC, 0666);
} while (status_fd == -1 && errno == EINTR);
if (status_fd == -1) {
pthread_mutex_unlock(&status_lock);
return 0;
}
/* In case of failure, do cleanup. */
do {
/* Set lease signal. */
if (fcntl(status_fd, F_SETSIG, LEASE_SIGNAL) == -1)
break;
/* Get exclusive lease on the status file. */
if (fcntl(status_fd, F_SETLEASE, F_WRLCK) == -1)
break;
/* Replace status file with the new, leased one. */
if (rename(status_newpath, status_path) == -1)
break;
/* Success. */
pthread_mutex_unlock(&status_lock);
return 0;
} while (0);
if (status_fd != -1) {
close(status_fd);
status_fd = -1;
}
unlink(status_newpath);
pthread_mutex_unlock(&status_lock);
return 0;
}
int main(int argc, char *argv[])
{
char *line = NULL;
size_t size = 0;
ssize_t len;
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *argv0 = (argc > 0 && argv[0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
fprintf(stderr, " %s STATUS-FILE\n", argv0);
fprintf(stderr, "\n");
fprintf(stderr, "This program maintains a pseudofile-like status file,\n");
fprintf(stderr, "using the contents from standard input.\n");
fprintf(stderr, "Supply an empty line to exit.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
status_path = join2(argv[1], "");
status_newpath = join2(argv[1], ".new");
unlink(status_path);
unlink(status_newpath);
if (start_status_worker()) {
fprintf(stderr, "Cannot start status worker thread: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (set_status("Empty\n")) {
fprintf(stderr, "Cannot create initial empty status: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
while (1) {
len = getline(&line, &size, stdin);
if (len < 1)
break;
line[strcspn(line, "\n")] = '\0';
if (line[0] == '\0')
break;
set_status("%s\n", line);
}
pthread_cancel(status_thread);
pthread_join(status_thread, NULL);
if (status_fd != -1)
close(status_fd);
unlink(status_path);
unlink(status_newpath);
return EXIT_SUCCESS;
}
Save the above as server.c, then compile using e.g.
gcc -Wall -Wextra -O2 server.c -lpthread -o server
This implements a status server, storing each line from standard input to the status file if necessary. Supply an empty line to exit. For example, to use the file status in the current directory, just run
./server status
Then, if you use another terminal window to examine the directory, you see it has a file named status (with typically zero size). But, cat status shows you its contents; just like procfs/sysfs pseudofiles.
Note that the status file is only updated if necessary, and only for the first reader/accessor after status changes. This keeps writer/server overhead and I/O low, even if the status changes very often.
The above example program uses a worker thread to catch the lease-break signals. This is because pthread mutexes cannot be locked or released safely in a signal handler (pthread_mutex_lock() etc. are not async-signal safe). The worker thread maintains its cancelability, so that it won't be canceled when it holds the mutex; if canceled during that time, it will be canceled after it releases the mutex. It is careful that way.
Also, the temporary replacement file is not random, it is just the status file name with .new appended at end. Anywhere on the same mount would work fine.
As long as other threads also block the lease break signal, this works fine in multithreaded programs, too. (If you create other threads after the worker thread, they'll inherit the correct signal mask from the main thread; start_status_worker() sets the signal mask for the calling thread.)
I do trust the approach in the program, but there may be bugs (and perhaps even thinkos) in this implementation. If you find any, please comment or edit.
Probably a silly question, with read and other functions you can specify the number of bytes you want to read, however when reading from stdin I find that I can only type 1024 characters in the prompt, if I type the 1025 character, it's not written and if I want the line to be read (pressing ENTER key) I need to remove the 1024 character in order to leave space for '\n' I suppose. This occurs only in my c program not the shell so what's causing this restriction?
#include <unistd.h>
#include <stdio.h>
int main() {
char buf[2048];
int c;
c = read(fileno(stdin), &buf, sizeof(buf));
printf("%s\n", buf);
return 0;
}
Transferring select comments to form an answer.
General diagnosis
This is a property of the terminal driver on your system, rather than of the program or the C library. Modern shells such as Bash don't read a single line; they read characters as they become available using non-canonical input. See also Canonical vs non-canonical terminal input.
Barmar noted:
Note that read() doesn't add a null terminator to the input that it reads, but printf() expects a null-terminated string.
Instead of adding a null terminator, you could tell printf() how many characters to print:
printf("%.*s\n", c, buf);
That is, however, tangential to the question of how to get a long line of input.
If you use an open source o/s, you can modify the terminal driver source code and recompile your kernel to allow you to type more than 1 KiB on a single line, but anything much short of that isn't going to work. The terminal driver imposes a limit; you have to change the terminal driver to change that limit. If you're on Linux, you can poke around the /proc file system to see if there's a dynamic configuration parameter you can change (so you don't have to recompile the kernel, but you do have to alter the settings of the terminal driver); I've not heard of that being possible.
The limit can be a nuisance if you copy'n'paste more than 1 KiB of text with no newlines in it from a browser and want to paste it into a file on your system. Use a program such as Vim to manage it — it puts the terminal into a non-canonical mode and therefore doesn't run into the limit.
Using POSIX termios to slurp input from a terminal
If you want a program to read from a terminal without the line lengths (but also with line editing such as erase or kill processing), then you could consider this program — slurp:
/*
#(#)File: $RCSfile: slurp.c,v $
#(#)Version: $Revision: 1.3 $
#(#)Last changed: $Date: 2018/10/28 17:14:24 $
#(#)Purpose: Put terminal into non-canonical mode to slurp input
#(#)Author: J Leffler
*/
/*TABSTOP=4*/
#include "posixver.h"
#include "stderr.h"
#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
static const char optstr[] = "a:ho:V";
static const char usestr[] = "[-hV][-a output | -o output]";
static const char hlpstr[] =
" -a output Append to named file (creating it if necessary)\n"
" -h Print this help message and exit\n"
" -o output Output to named file (truncating it if it exists)\n"
" -V Print version information and exit\n"
;
static struct termios saved = { 0 };
static bool sigint_enabled = false;
static bool sigquit_enabled = false;
static bool slurping = false;
static void reset_termios(void);
static void set_non_canonical(void);
static void sig_handler(int signum);
static void set_signal_handling(void);
static void slurp(int ofd, const char *filename);
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
extern const char jlss_id_slurp_c[];
const char jlss_id_slurp_c[] = "#(#)$Id: slurp.c,v 1.3 2018/10/28 17:14:24 jonathanleffler Exp $";
#endif /* lint */
int main(int argc, char **argv)
{
const char *filename = "standard output";
int ofd = STDOUT_FILENO;
int oflag = 0;
err_setarg0(argv[0]);
int opt;
while ((opt = getopt(argc, argv, optstr)) != -1)
{
switch (opt)
{
case 'h':
err_help(usestr, hlpstr);
/*NOTREACHED*/
case 'o':
case 'a':
if (ofd != STDOUT_FILENO)
{
err_remark("the -a and -o flags are mutually exclusive\n");
err_usage(usestr);
}
oflag = (opt == 'o') ? O_TRUNC : O_APPEND;
if ((ofd = open(optarg, O_WRONLY | O_CREAT | oflag, 0644)) < 0)
err_syserr("failed to open file %s for writing: ", optarg);
filename = optarg;
break;
case 'V':
err_version("PROG", &"#(#)$Revision: 1.3 $ ($Date: 2018/10/28 17:14:24 $)"[4]);
/*NOTREACHED*/
default:
err_usage(usestr);
/*NOTREACHED*/
}
}
if (optind != argc)
{
err_remark("unexpected file name options (first is '%s')\n", argv[optind]);
err_usage(usestr);
}
set_non_canonical();
if (slurping)
set_signal_handling();
slurp(ofd, filename);
return 0;
}
static void reset_termios(void)
{
tcsetattr(STDIN_FILENO, 0, &saved);
}
static void set_non_canonical(void)
{
if (tcgetattr(STDIN_FILENO, &saved) == 0)
{
struct termios modified = saved;
atexit(reset_termios);
/*
** On macOS 10.14 (at least), if you don't reset ISIG, the
** signal characters are not transferred to the program, so
** you can't detect those signals. With ICANON reset, they
** don't generate the signal either. The code does not try
** to handle the suspend (^Z) key specially, nor any other
** keys than EOF, INTR, QUIT.
*/
modified.c_lflag &= ~(ICANON | ISIG);
modified.c_cc[VMIN] = 1;
modified.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &modified);
slurping = true;
}
}
static void sig_handler(int signum)
{
reset_termios();
_exit(128 + signum);
}
/* Almost worth a data structure and a loop, but not quite */
static void set_signal_handling(void)
{
/* Simulate SIGINT and SIGQUIT */
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
{
(void)signal(SIGINT, sig_handler);
sigint_enabled = true;
}
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
{
(void)signal(SIGQUIT, sig_handler);
sigquit_enabled = true;
}
/* Have program terminate when sent normal signals */
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
(void)signal(SIGHUP, sig_handler);
if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
(void)signal(SIGTERM, sig_handler);
if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
(void)signal(SIGPIPE, sig_handler);
}
static void slurp(int ofd, const char *filename)
{
char buffer[4096];
int nbytes;
while ((nbytes = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0)
{
/* Simulate EOF and interrupt and quit signals */
if (nbytes == 1 && slurping)
{
if (buffer[0] == saved.c_cc[VEOF])
break;
if (sigint_enabled && buffer[0] == saved.c_cc[VINTR])
exit(128 + SIGINT);
if (sigquit_enabled && buffer[0] == saved.c_cc[VQUIT])
exit(128 + SIGQUIT);
}
if (write(ofd, buffer, nbytes) != nbytes)
err_syserr("failed to write %d bytes to %s: ", nbytes, filename);
}
}
The library code used is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c, stderr.h and posixver.h in the libsoq sub-directory.
This deals with most of the traps for the unwary. It does its best to reset the terminal back to the initial ('known good') state when it exits. It does simulate EOF, interrupt and quit keyboard signals, but it does not simulate regular terminal processing such as erase or kill.
It doesn't make sense to use this when the standard input is not a terminal, but the code should handle that OK too (it simply does normal reads). You can send the output to standard output (default) or to a file (-o file to create or truncate a file, -a file to append or create a file).
I am working on some example code in a C text called "Headfirst C". I wrote a practice application that demonstrates signal handling and after finishing the chapter decided to play around with it a bit. I'm an engineer used to working in LabVIEW (high degree of concurrency and intuitive event handling functionality) so I am interested in using alarms and signal handlers to generate periodic events. My question is this:
In the following sample code, what is the proper method or best practices for discarding a variable amount of user input that sits in stdin? I have written this little app as a demo and a 3 second alarm triggers an annoying "howdy!" message to interrupt an fgets call. The thing I have noticed, however, is that if the user is interrupted in the middle of typing, when he eventually does press enter any text entered (having been interrupted or not) will be echoed back. I want to discard anything that was interrupted before the user pressed enter.
//Sample Program - Signal Handling & Alarms
//Header Includes
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
//Function Declarations
//Handler Functions
void diediedie(int sig);
void howdy(int sig);
//Handler Register Function
int catchSignal(int signum, void(*handler)(int));
//Variable declarations
//Declare interrupted flags
static int interrupted = 0;
//Program entrypoint
int main() {
//Register interrupt hander, catch errors
if(catchSignal(SIGINT, diediedie) == -1) {
fprintf(stderr, "Could not register interrupt handler");
exit(2);
}
//Register alarm handler, catch errors
if(catchSignal(SIGALRM, howdy) == -1) {
fprintf(stderr, "Could not register alarm handler");
exit(2);
}
//Create initial alarm trigger
alarm(3);
//Do something stupid while waiting for signals
char name[30];
printf("Enter your name: ");
//Keep waiting for user input even if interrupted by alarm signal
while(1) {
fgets(name, 30, stdin);
if(interrupted) {
// reset interrupted flag
interrupted = 0;
// ***** ADD CODE TO DISCARD INTERRUPTED INPUT HERE ******
}
else {
//echo user input and break out
printf("Hello %s\n", name);
break;
}
}
//end program
return 0;
}
//interrupt handler definition
void diediedie(int sig) {
//write some stuff, exit program
puts("Goodbye world!");
exit(1);
}
//alarm handler definition
void howdy(int sig) {
//set interrupted flag
interrupted = 1;
//write some annoying message
puts("howdy!");
//set another alarm trigger
alarm(3);
//**** COULD STDIN BE FLUSHED HERE? ****
}
//signal handler registration function definition
int catchSignal(int signum, void(*handler)(int)) {
//register handler
struct sigaction action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
return sigaction(signum, &action, NULL);
}
Is the proper place to do this purge within the alarm handler? Note the comments indicating my thoughts for proper code location.
I have considered the following:
while(getchar() != EOF) {}
I am also wondering, what happens while fgets is waiting for user input and SIGALRM is raised? Is that function terminated? I observe that if I don't include the while loop to check the interrupted flag and respond appropriately, the program will finish the fgets, dump some garbage on the screen (I assume the current state of stdin?) and end the program.
Thanks for any advice!
In Unix, a signal handler occurs out of band with your code. If the signal occurs in the middle of a blocking system call, the system call will exit with errno set to EINTR. But I believe that fgets() is handling this interruption for you and continuing on without returning control to you.
If you are using a Unix based OS and typing in input from the command line, what's really going on here is that you are reading data from your terminal in cooked mode. Your program doesn't get any data from the TTY until return is pressed. You need to set the terminal into 'raw' mode. Here is an example of how to do that integrated with your code:
//Sample Program - Signal Handling & Alarms
//Header Includes
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
//Function Declarations
//Handler Functions
void diediedie(int sig);
void howdy(int sig);
//Handler Register Function
int catchSignal(int signum, void(*handler)(int));
//Variable declarations
//Declare interrupted sa_flags
static int interrupted = 0;
static struct termios save_termios;
//Program entrypoint
int main() {
struct termios buf;
int fd = 1;
// This is derived from from Stevens, "Advanced Programming in the UNIX Environment"
if (tcgetattr(fd, &save_termios) < 0) /* get the original state */
return -1;
buf = save_termios;
buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
/* echo off, canonical mode off, extended input
processing off, signal chars off */
buf.c_iflag |= BRKINT | ICRNL;
/* SIGINT on BREAK, CR-toNL on */
buf.c_cflag &= ~(CSIZE | PARENB);
/* clear size bits, parity checking off */
buf.c_cflag |= CS8;
/* set 8 bits/char */
buf.c_oflag &= ~(OPOST);
/* output processing off */
buf.c_cc[VMIN] = 1; /* 1 byte at a time */
buf.c_cc[VTIME] = 0; /* no timer on input */
if (tcsetattr(fd, TCSAFLUSH, &buf) < 0)
return -1;
//Register interrupt hander, catch errors
if(catchSignal(SIGINT, diediedie) == -1) {
fprintf(stderr, "Could not register interrupt handler");
exit(2);
}
//Register alarm handler, catch errors
if(catchSignal(SIGALRM, howdy) == -1) {
fprintf(stderr, "Could not register alarm handler");
exit(2);
}
//Create initial alarm trigger
alarm(3);
//Do something stupid while waiting for signals
char name[30];
printf("Enter your name: ");
//Keep waiting for user input even if interrupted by alarm signal
char nextchar = 0;
char *p;
p = name;
while(nextchar != '\n') {
nextchar = fgetc(stdin);
if (interrupted) {
// reset interrupted flag
interrupted = 0;
//Discard interrupted input by reseting 'p' to the start of the buffer
p = name;
*p = 0;
continue;
}
if (nextchar == '\n') {
*p = 0;
fputc('\r', stdout);
fputc('\n', stdout);
break;
}
// You'll have to handle some characters manually to emulate what the
// terminal does, or you could filter them out using a function like isprint()
//
if (nextchar == 127) {
// *** handle backspace
if (p > name) {
p--;
}
// TODO: To handle this right you'll have to backup the cursor on the screen
} else {
*p = nextchar;
p++;
}
fputc(nextchar, stdout);
// Handle buffer overflow
if (p-name == sizeof(name) - 1) {
*p = 0;
break;
}
}
// echo user input
printf("Input is: %s\r\n", name);
tcsetattr(1, TCSAFLUSH, &save_termios);
}
//interrupt handler definition
void diediedie(int sig) {
//write some stuff, exit program
puts("Goodbye world!");
tcsetattr(1, TCSAFLUSH, &save_termios);
exit(1);
}
//alarm handler definition
void howdy(int sig) {
//set interrupted flag
interrupted = 1;
//write some annoying message
puts("howdy!");
//set another alarm trigger
alarm(3);
}
// signal handler registration function definition
int catchSignal(int signum, void(*handler)(int)) {
//register handler
struct sigaction action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
return sigaction(signum, &action, NULL);
}
Note that you need to save the original terminal settings and restore them before the program exits! If you get into trouble, you might break the terminal settings. Use reset or stty sane from the command line to restore sane terminal settings. For more information on the termios datastructure, see the man pages.
You can also use libraries like ncurses to handle raw input.
To do this right, you'd need to put the terminal into "raw" mode, in which every keystroke is returned immediately to the application, instead of being accumulated with interpretation of line-editing characters ("cooked" mode, which is the default).
Of course, if you don't let the kernel handle line-editing (backspace, for example), then you need to do it yourself, which is quite a lot of work. Interpreting backspace and whatever other editing commands you're interested in isn't that hard, but maintaining the correct appearance on the terminal is a pain.
See man termios for a lot more information.
The code I have right now sends a prompt out to stdout, then reads a line from stdin. Receiving SIGINT at any point interrupts execution and exits the program. I am unsure where I should trap SIGINT, and I know that I cannot start a new prompt when the signal is received with my current code. Is there a proper way to accomplish that (ultimate goal would be for it to act like most shells (SIGINT cancels the current prompt and starts a new one))?
This code will run on Linux, but the less platform independent, the better.
get_line reads a line from stdin into a buffer and generates a char[], which is assigned to line.
split_args takes a line and transforms it into an array of char[], splitting on whitespace.
is_command_valid determines if the user typed a known internal command. External programs cannot be executed.
static int run_interactive(char *user)
{
int done = 0;
do
{
char *line, **args;
int (*fn)(char *, char **);
fprintf(stderr, "gitorium (%s)> ", user);
get_line(&line);
if (line[0] == '\0')
{
free(line);
break;
}
split_args(&args, line);
if (!strcmp(args[0], "quit") || !strcmp(args[0], "exit") ||
!strcmp(args[0], "logout") || !strcmp(args[0], "bye"))
done = 1;
else if (NULL == args[0] ||
(!strcmp("help", args[0]) && NULL == args[1]))
interactive_help();
else if ((fn = is_command_valid(args)) != NULL)
(*fn)(user, args);
else
error("The command does not exist.");
free(line);
free(args);
}
while (!done);
return 0;
}
Here are the two most important helper functions
static int split_args(char ***args, char *str)
{
char **res = NULL, *p = strtok(str, " ");
int n_spaces = 0, i = 0;
while (p)
{
res = realloc(res, sizeof (char*) * ++n_spaces);
if (res == NULL)
return GITORIUM_MEM_ALLOC;
res[n_spaces-1] = p;
i++;
p = strtok(NULL, " ");
}
res = realloc(res, sizeof(char*) * (n_spaces+1));
if (res == NULL)
return GITORIUM_MEM_ALLOC;
res[n_spaces] = 0;
*args = res;
return i;
}
static int get_line(char **linep)
{
char *line = malloc(LINE_BUFFER_SIZE);
int len = LINE_BUFFER_SIZE, c;
*linep = line;
if(line == NULL)
return GITORIUM_MEM_ALLOC;
for(;;)
{
c = fgetc(stdin);
if(c == EOF || c == '\n')
break;
if(--len == 0)
{
char *linen = realloc(*linep, sizeof *linep + LINE_BUFFER_SIZE);
if(linen == NULL)
return GITORIUM_MEM_ALLOC;
len = LINE_BUFFER_SIZE;
line = linen + (line - *linep);
*linep = linen;
}
*line++ = c;
}
*line = 0;
return 0;
}
If I understand you correctly, you want to know how to handle the signal as well as what to do once you get it.
The way you establish a signal handler is with sigaction(). You didn't state the platform you're on so I'm assuming Linux, although sigaction() is defined by the POSIX standards and should be available on most other platforms.
There are various ways you can do this. One way is to establish a signal handler which simply sets a global variable to 1, denoting that the signal was caught. Then in your getline() function you establish a check to see if SIGINT was caught and if it was then return NULL and allow run_interactive() to run again.
Here's how you would catch the signal:
#include <signal.h>
static int sigint_caught = 0;
static void sigint_handler(int sig) {
sigint_caught = 1;
}
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; // or SA_RESTART if you want to automatically restart system calls interrupted by the signal
sa.sa_handler = sigint_handler;
if (sigaction(SIGINT, &sa, NULL) == -1) {
printf("could not establish handler\n");
exit(-1); // or something
}
And then perhaps in getline(), in the infinite loop, you would establish the check to see if SIGINT has been caught:
for (;;) {
if (sigint_caught) {
return NULL;
}
// ...
And then in your run_interactive() call you can check the return value with the check to see if SIGINT was caught:
// ...
get_line(&line);
if (line == NULL && sigint_caught) {
sigint_caught = 0; // allow it to be caught again
free(line);
continue; // or something; basically go to the next iteration of this loop
} else if (line[0] == '\0') {
free(line);
break;
} else {
// rest of code
Didn't test it so I can't guarantee it'll work, since your question is pretty broad (having to look through more of your code etc.), but hopefully it gives you enough of an idea as to what you can do you in your situation. This is perhaps a pretty naive solution but it might meet your needs. For something more robust perhaps look into the source code for popular shells like bash or zsh.
For example, one thing that can happen is that fgetc() might block since there is no new data in stdin, and that might be when the signal is sent. fgetc() would be interrupted and errno would be EINTR, so you could add a check for this in getline():
c = fgetc(stdin);
// make sure to #include <errno.h>
if (errno == EINTR && sigint_caught)
return NULL;
This would only happen if you don't set sa_flags to SA_RESTART. If you do, then fgetc should automatically restart and continue blocking until new input is received, which may or may not be what you want.
I would like to stuff an 'A' character back into stdin using ungetc on receipt of SIGUSR1. Imagine that I have a good reason for doing this.
When calling foo(), the blocking read in stdin is not interrupted by the ungetc call on receipt of the signal. While I didn't expect this to work as is, I wonder if there is a way to achieve this - does anyone have suggestions?
void handler (int sig)
{
ungetc ('A', stdin);
}
void foo ()
{
signal (SIGUSR1, handler);
while ((key = fgetc (stdin)) != EOF)
{
...
}
}
Rather than try to get ungetc() to unblock a blocking fgetc() call via a signal, perhaps you could try not having fgetc() block to begin with and wait for activity on stdin using select().
By default, the line discipline for a terminal device may work in canonical mode. In this mode, the terminal driver doesn't present the buffer to userspace until the newline is seen (Enter key is pressed).
To accomplish what you want, you can set the terminal into raw (non-canonical) mode by using tcsetattr() to manipulate the termios structure. This should case the blocking call to fgetc() to immediately return the character inserted with ungetc().
void handler(int sig) {
/* I know I shouldn't do this in a signal handler,
* but this is modeled after the OP's code.
*/
ungetc('A', stdin);
}
void wait_for_stdin() {
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(fileno(stdin),&fdset);
select(1, &fdset, NULL, NULL, NULL);
}
void foo () {
int key;
struct termios terminal_settings;
signal(SIGUSR1, handler);
/* set the terminal to raw mode */
tcgetattr(fileno(stdin), &terminal_settings);
terminal_settings.c_lflag &= ~(ECHO|ICANON);
terminal_settings.c_cc[VTIME] = 0;
terminal_settings.c_cc[VMIN] = 0;
tcsetattr(fileno(stdin), TCSANOW, &terminal_settings);
for (;;) {
wait_for_stdin();
key = fgetc(stdin);
/* terminate loop on Ctrl-D */
if (key == 0x04) {
break;
}
if (key != EOF) {
printf("%c\n", key);
}
}
}
NOTE: This code omits error checking for simplicity.
Clearing the ECHO and ICANON flags respectively disables echoing of characters as they are typed and causes read requests to be satisfied directly from the input queue. Setting the values of VTIME and VMIN to zero in the c_cc array causes the read request (fgetc()) to return immediately rather than block; effectively polling stdin. This causes key to get set to EOF so another method for terminating the loop is necessary. Unnecessary polling of stdin is reduced by waiting for activity on stdin using select().
Executing the program, sending a SIGUSR1 signal, and typing
t e s t results in the following output1:
A
t
e
s
t
1) tested on Linux
It is not entirely clear what your goal is, but is this what you are looking for?
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int handle = 0;
void handler (int sig) {
handle = 1;
}
void foo () {
int key;
signal (SIGUSR1, handler);
while ((key = fgetc (stdin)) != EOF) {
printf("%c\n",key);
if (handle) {
handle = 0;
ungetc('A',stdin);
}
}
}
int main(void) {
printf("PID: %d\n",getpid());
foo();
}
It produces this output:
PID: 8902
test (typed on stdin)
t
A
e
s
t
FILE*s are not async safe.
You cannot operate on a FILE* in a signal handler while someone else also uses that same FILE*. functions you can all in a signal handler is stated here:
http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html . (It might be
different on a windows machine, but still any FILE* are not safe there either.
This is essentially the same as #Jamie's answer, slightly changed to support your desire to process the A before the t, but it's too hard to type code into a comment box, so I've posted this answer separately.
int insert_an_A = 0;
void handler(int sig) { insert_an_A = 1; }
void process_char(char c) { ... }
int main(int argc, char **argv) {
int c;
/* skip signal setup */
while ((c = fgetc(stdin)) != EOF) {
if (insert_an_A) {
process_char('A');
insert_an_A = 0;
}
process_char(c);
}
}
If you want to process an handler received during the call to fgetc that returns EOF, you should also check insert_an_A after exiting the while loop.
Note also that in general the best practice for signal handlers is to set a global variable and return from the handler. Elsewhere in your program, look for that variable changing and react appropriately.