ncurses прерывает системный вызов при изменении размера терминала

У меня проблема с ncurses, и я не смог найти решения в Интернете, поэтому я написал следующую небольшую программу, чтобы продемонстрировать эту проблему.

Вы можете скомпилировать его через:

sudo aptitude install ncurses-dev
g++ -lncurses -o resize resize.cpp

Он отображает целочисленный счетчик, увеличиваемый каждую секунду путем разветвления в процесс таймера, который периодически отправляет один байт родительскому процессу через пару сокетов. Вы можете выйти из него, нажав CTRL+C.

При изменении размера терминала вы должны получить сообщение об ошибке "Прерванный системный вызов". Таким образом, вызов чтения прерывается SIGWINCH при изменении размера. Но как я могу избежать этого? Или часто системный вызов прерывается? Но как бы я обработал прерванный системный вызов, чтобы продолжить увеличивать счетчик, так как дескриптор файла кажется мертвым после прерывания.

Если вы используете неблокирующие сокеты, вы получите вместо этого "Ресурс временно недоступен".

Я использую стабильный Debian Wheezy, поэтому версия ncurses 5.9-10, а версия libstdC++ 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);
    }
}

Спасибо!

С уважением

2 ответа

Ладно, у меня все получилось, используя только входящие функции в обработчиках сигналов. Теперь пара сокетов все еще работает после EINTR или EAGAIN.

Спасибо!

#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;
    }
}

Большинство (если не все) системные вызовы имеют прерванный код ошибки (errno == EINTR), это нормально.

Я бы проверил EINTR на чтение из канала и проигнорировал его, просто прочитал снова.

Я бы не стал вызывать какие-либо функции ncurses в обработчике сигналов, некоторые являются входящими, но я сомневаюсь, что printw есть. Просто сделайте проверку KEY_RESIZE.

Другие вопросы по тегам