Reputation: 11
I have a problem with ncurses and couldn't find a solution on the web, so I've written following little program to demonstrate the problem.
You can compile it via:
sudo aptitude install ncurses-dev
g++ -lncurses -o resize resize.cpp
It displays an integer counter incremented every second by forking into a timer process which periodically sends one byte to the parent process via a socketpair. You can quit it by pressing CTRL+C.
When you resize the terminal you should get an error message of 'Interrupted system call'. So the read call gets interrupted by SIGWINCH when resizing. But how can I avoid this? Or is it common that the system call gets interrupted? But how would I handle an interrupted system call in order to proceed incrementing the counter since the file descripter appears to be dead after interruption.
If you use non-blocking sockets, you would get 'Resource temporarily unavailable' instead.
I am using stable debian wheezy, so the ncurses version is 5.9-10 and the libstdc++ version is 4.7.2-5.
#include <ncurses.h>
#include <signal.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <iostream>
//Define a second.
timespec span = {1, 0};
//Handles both, SIGWINCH and SIGINT
void handle(int signal) {
switch (signal) {
case SIGWINCH:
//Reinitialize ncurses to get new size
endwin();
refresh();
printw("Catched SIGWINCH and handled it.\n");
refresh();
break;
case SIGINT:
//Catched CTRL+C and quit
endwin();
exit(0);
break;
}
}
//This registers above signal handler function
void set_handler_for(int signal) {
struct sigaction action;
action.sa_handler = handle;
action.sa_flags = 0;
if (-1 == sigemptyset(&action.sa_mask) or -1 == sigaction(signal, &action, NULL))
throw "Cannot set signal handler";
}
main() {
int fd[2];
//In this try block we fork into the timer process
try {
set_handler_for(SIGINT);
set_handler_for(SIGWINCH);
//Creating a socketpair to communicate between timer and parent process
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd))
throw "Cannot create socketpair";
pid_t pid;
//Doing the fork
if (-1 == (pid = fork()))
throw "Cannot fork process";
if (!pid) {
//We are the timer, so closing the other end of the socketpair
close(fd[0]);
//We send one byte every second to the parent process
while (true) {
char byte;
ssize_t bytes = write(fd[1], &byte, sizeof byte);
if (0 >= bytes)
throw "Cannot write";
nanosleep(&span, 0);
}
//Here the timer process ends
exit(0);
}
//We are the parent process, so closing the other end of the socketpair
close(fd[1]);
}
catch (const char*& what) {
std::cerr << what << std::endl;
exit(1);
}
//Parent process - Initializing ncurses
initscr();
noecho();
curs_set(0);
nodelay(stdscr, TRUE);
//In this try block we read (blocking) the byte from the timer process every second
try {
int tick = 0;
while (true) {
char byte;
ssize_t bytes = read(fd[0], &byte, sizeof byte);
if (0 >= bytes)
throw "Cannot read";
//Clear screen and print increased counter
clear();
mvprintw(0, 0, "Tick: %d - Resize terminal and press CTRL+C to quit.\n", ++tick);
//Catch special key KEY_RESIZE and reinitialize ncurses to get new size (actually not necassary)
int key;
while ((key = getch()) != ERR) {
if (key == KEY_RESIZE) {
endwin();
refresh();
printw("Got KEY_RESIZE and handled it.\n");
}
}
//Update the screen
refresh();
}
}
catch (const char*& what) {
//We got an error - print it but don't quit in order to have time to read it
std::string error(what);
if (errno) {
error.append(": ");
error.append(strerror(errno));
}
error = "Catched exception: "+error+"\n";
printw(error.c_str());
refresh();
//Waiting for CTRL+C to quit
while (true)
nanosleep(&span, 0);
}
}
Thank you!
Regards
Upvotes: 1
Views: 1520
Reputation: 11
Okay, I got it working by only using re-entrant functions within signal handlers. Now the socketpair is still working after an EINTR or EAGAIN.
Thank you!
#include <ncurses.h>
#include <signal.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <iostream>
// Define a second.
timespec base = {1, 0};
// Holds raised SIGINTs.
size_t raised_SIGINT = 0;
// Holds raised SIGWINCHs.
size_t raised_SIGWINCH = 0;
// Handle SIGWINCH
void handle_SIGWINCH(int) {
++raised_SIGWINCH;
}
// Handle SIGINT
void handle_SIGINT(int) {
++raised_SIGINT;
}
// Registers signal handlers.
void assign(int signal, void (*handler)(int)) {
struct sigaction action;
action.sa_handler = handler;
action.sa_flags = 0;
if (-1 == sigemptyset(&action.sa_mask) or -1 == sigaction(signal, &action, NULL))
throw "Cannot set signal handler";
}
// Prints ticks alive and usage information.
inline void print(size_t ticks) {
mvprintw(0, 0, "%ds alive. Resize terminal and press CTRL+C to quit.\n\n", ticks);
}
int main() {
// Holds the two socketpair file descriptors.
int fd[2];
// Fork into the timer process.
try {
// Register both signals.
assign(SIGINT, handle_SIGINT);
assign(SIGWINCH, handle_SIGWINCH);
// Create a socketpair to communicate between timer and parent process.
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd))
throw "Cannot create socketpair";
// Doing the fork.
pid_t pid;
if (-1 == (pid = fork()))
throw "Cannot fork process";
if (!pid) {
// We are the timer, so closing the parent end of the socketpair.
close(fd[0]);
// We send one byte every second to the parent process.
while (true) {
timespec less = base;
int ret;
// Continue sleeping after SIGWINCH but only for the time left.
while (-1 == (ret = nanosleep(&less, &less)) and errno == EINTR and raised_SIGWINCH);
// Maybe quit by user.
if (raised_SIGINT)
return 0;
// If something went wrong, terminate.
if (-1 == ret)
throw "Cannot sleep";
// Repeated writing if interrupted by SIGWINCH.
char byte;
ssize_t bytes;
do {
// Doing the write.
bytes = write(fd[1], &byte, sizeof byte);
}
while (0 >= bytes and (errno == EAGAIN or errno == EINTR) and raised_SIGWINCH);
// Maybe quit by user.
if (raised_SIGINT)
return 0;
// If something went wrong, terminate.
if (0 >= bytes)
throw "Cannot write";
}
// Here the timer process ends.
return 0;
}
// We are the parent process, so closing the timer end of the socketpair.
close(fd[1]);
}
catch (const char*& what) {
// Print fatal error and terminate timer process causing parent process to terminate, too.
std::cerr << what << std::endl;
return 1;
}
// Initializing ncurses for the parent process.
initscr();
// Disable typing.
noecho();
// Disable cursor.
curs_set(0);
// Make reading characters non-blocking.
nodelay(stdscr, TRUE);
// Catch fatal errors.
try {
// Holds ticks alive.
size_t ticks = 0;
// Blockingly read the byte from the timer process awaiking us every second.
while (true) {
// Print ticks alive before incrementing them.
print(ticks++);
// Holds typed keys.
std::string keys;
// Read typed keys.
for (int key = getch(); key != ERR; key = getch())
if (key != KEY_RESIZE)
keys += key;
// Format typed keys string.
if (keys.size())
printw("You've typed: ");
else
keys += "\n";
keys += "\n\n";
// Print typed keys string.
printw(keys.c_str());
// Doing the prints.
refresh();
// Repeated reading if interrupted by SIGWINCH.
ssize_t bytes = 0;
bool again = false;
do {
// Doing the read.
char byte;
bytes = read(fd[0], &byte, sizeof byte);
again = (0 >= bytes and (errno == EAGAIN or errno == EINTR) and raised_SIGWINCH);
// Print how often we got interrupted by SIGWINCH per time base.
if (again) {
// Next two calls are the common way to handle a SIGWINCH.
endwin();
refresh();
// For simpicity clear everything.
clear();
// Re-print ticks.
print(ticks);
// Print the interruption counter.
printw("%dx catched SIGWINCH per time base.\n\n", raised_SIGWINCH);
// Doing the prints.
refresh();
}
}
while (again);
// Reset SIGWINCH raises per time base.
raised_SIGWINCH = 0;
// Maybe quit by user.
if (raised_SIGINT) {
endwin();
return 0;
}
// If something went wrong, terminate.
if (0 >= bytes)
throw "Cannot read";
}
}
catch (const char*& what) {
// We got an error, appending errno if set.
std::string error(what);
if (errno) {
error.append(": ");
error.append(strerror(errno));
}
error = "Catched exception: "+error+"\n";
// Print the fatal error.
printw(error.c_str());
//Doing the print.
refresh();
// Waiting for CTRL+C to quit.
while (true)
nanosleep(&base, 0);
// Quit by user.
endwin();
return 0;
}
}
Upvotes: 0
Reputation: 7792
Most (if not all) system calls have an interrupted error code (errno == EINTR), this is normal.
I would check for EINTR on the read from the pipe and ignore it, just read again.
I wouldn't call any ncurses functions in the signal handler, some are re-entrant but I doubt printw is. Just do the KEY_RESIZE check.
Upvotes: 0