Перехват ввода из терминала (Linux)
От: Murr Россия  
Дата: 04.08.03 13:01
Оценка:
В общем, есть некий код, он не совсем рабочий, но вроде идея правильная.

Регистрируем события поступления ввода процессам, читающим с терминала (через DNOTIFY на /dev/* или /dev/pts/*), регистрируем события поступления ввода от драйвера терминала (через FASYNC на терминале). При каждом таком событии прочитываем весь буфер и запихиваем назад через TIOCSTI IOCTL. Чтобы не терять ввод устанавливаем приоритет реального времени (SCHED_FIFO).

Не работает Если кто подскажет где я глючу — обязательно включу в список авторов (если схема принципиально верна)

Вот код:

#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

#define SPY_INTERRUPTED       0
#define SPY_DRIVER_INPUT_RECV 1
#define SPY_USER_INPUT_RECV   2

#define SPY_MAX_BUFFER_SIZE 4096

int spy_reason;
int fd, dfd;
int buffer_size, buffer_tail;

static void usage () {
}

void spy_io_handler (int unused, siginfo_t * si, void * unused2) {
    printf ("handler! fd=%d band=%d\n", si->si_fd, si->si_band);
    spy_reason = (si->si_fd != fd) ? SPY_USER_INPUT_RECV: SPY_DRIVER_INPUT_RECV;
}

int spy_register_hook (int fd) {
    char tname[strlen(ttyname (fd))+1];

    strcpy (tname, ttyname(fd));

    *(strrchr (tname, '/')) = '\0';
    dfd = open (tname, O_RDONLY);

    /* User input registration */
    if (dfd == -1)
        return 0;

    if (fcntl (dfd, F_NOTIFY, DN_ACCESS|DN_MULTISHOT) == -1) {
        close (dfd);
        return 0;
    }

    if (fcntl (dfd, F_SETOWN, getpid ()) == -1) {
        close (dfd);
        return 0;
    }

    if (fcntl (dfd, F_SETSIG, SIGUSR1) == -1) {
        close (dfd);
        return 0;
    }

    /* Driver input registration */
    if (fcntl (fd, F_SETOWN, getpid ()) == -1) {
        close (dfd);
        return 0;
    }

    if (fcntl (fd, F_SETFL, FASYNC|O_NONBLOCK) == -1) {
        close (dfd);
        return 0;
    }

    if (fcntl (fd, F_SETSIG, SIGUSR1) == -1) {
        close (dfd);
        return 0;
    }

//    ioctl (fd, FIONREAD, &buffer_size);
    buffer_tail = buffer_size = 0;

    return 1;
}

int spy_wait_for_event () {
    spy_reason = SPY_INTERRUPTED;
    sigset_t ss;

    sigemptyset (&ss);
    sigaction (SIGUSR1, &(const struct sigaction){.sa_sigaction = spy_io_handler, .sa_mask = ss, .sa_flags = SA_SIGINFO},NULL);

    fprintf (stderr, "spy_wait_for_event entering!\n");
    pause ();
    fprintf (stderr, "spy_wait_for_event exited!\n");

    sigaction (SIGUSR1, &(const struct sigaction){.sa_handler = SIG_IGN, .sa_mask = ss, .sa_flags = 0},NULL);
    return spy_reason;
}

void spy_unread_input (int fd, char * s, int n) {
    int i, c;

    for (i=0; i<n; i++)
        c = s[i], ioctl (fd, TIOCSTI, &c);
}

void spy_output_chars (char * s, int tail, int end) {
    int i;

    for (i=tail; i<end; i++)
        fprintf (stderr, "%x('%c'), ", s[i], s[i]);

}

int main (int argc, char * argv[]) {
    int rc;
    int seek = 0;
    char s[SPY_MAX_BUFFER_SIZE];

    rc = sched_setscheduler (0, SCHED_FIFO,
                        &(const struct sched_param){.sched_priority = 1}) == -1;

    rc |= mlockall (MCL_CURRENT|MCL_FUTURE) == -1;

    if (rc) {
        fprintf (stderr, "ttyspy: failed to set realtime priority\n");
        return 0;
    }

    if (argc != 2) {
        usage  ();
        return 0;
    }

    fd = open (argv[1], O_RDWR);
    if (fd == -1) {
        fprintf (stderr, "ttyspy: failed to open '%s'\n", argv[1]);
        usage   ();
        return  0;
    }

    if (!isatty (fd)) {
        fprintf (stderr, "ttyspy: '%s' is not a tty\n", argv[1]);
        close   (fd);
        usage   ();
        return  0;
    }

    if (!spy_register_hook (fd)) {
        fprintf (stderr, "ttyspy: failed to hook\n");
        close   (fd);
        return  0;
    }

    while (1) {
        switch (spy_wait_for_event ()) {
            case SPY_DRIVER_INPUT_RECV:
                    buffer_size = read (fd, s, SPY_MAX_BUFFER_SIZE);
                    fprintf (stderr, "driver input, got %d chars\n", buffer_size-buffer_tail);

                    if (buffer_size == -1) { // AP: weird, we could not read 0 bytes on driver input!
                        buffer_size = buffer_tail = 0;
                        break;
                    }

                    spy_unread_input (fd, s, buffer_size);
                    spy_output_chars (s, buffer_tail, buffer_size);
                    buffer_tail = buffer_size;
                    fprintf (stderr, "driver input exiting\n");
                    break;
            case SPY_USER_INPUT_RECV:
                    buffer_size = read (fd, s, SPY_MAX_BUFFER_SIZE);
                    fprintf (stderr, "user input, buffer shrinked to %d\n", buffer_size);

                    if (buffer_size == -1) { // AP: that is the most common way ...
                        buffer_size = buffer_tail = 0;
                        break;
                    }

                    spy_unread_input (fd, s, buffer_size);

                    buffer_tail = buffer_size;
                    break;
            default:    return 1;
        }
    }

    close (fd);
    close (dfd);
    return 1;
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.