source: MondoRescue/branches/3.3/mindi-busybox/miscutils/conspy.c@ 3647

Last change on this file since 3647 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

  • Property svn:eol-style set to native
File size: 13.8 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * A text-mode VNC like program for Linux virtual terminals.
4 *
5 * pascal.bellard@ads-lu.com
6 *
7 * Based on Russell Stuart's conspy.c
8 * http://ace-host.stuart.id.au/russell/files/conspy.c
9 *
10 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 */
12
13//applet:IF_CONSPY(APPLET(conspy, BB_DIR_BIN, BB_SUID_DROP))
14
15//kbuild:lib-$(CONFIG_CONSPY) += conspy.o
16
17//config:config CONSPY
18//config: bool "conspy"
19//config: default y
20//config: select PLATFORM_LINUX
21//config: help
22//config: A text-mode VNC like program for Linux virtual terminals.
23//config: example: conspy NUM shared access to console num
24//config: or conspy -nd NUM screenshot of console num
25//config: or conspy -cs NUM poor man's GNU screen like
26
27//usage:#define conspy_trivial_usage
28//usage: "[-vcsndfFQ] [-x COL] [-y LINE] [CONSOLE_NO]"
29//usage:#define conspy_full_usage "\n\n"
30//usage: "A text-mode VNC like program for Linux virtual consoles."
31//usage: "\nTo exit, quickly press ESC 3 times."
32//usage: "\n"
33//usage: "\n -v Don't send keystrokes to the console"
34//usage: "\n -c Create missing /dev/{tty,vcsa}N"
35//usage: "\n -s Open a SHELL session"
36//usage: "\n -n Black & white"
37//usage: "\n -d Dump console to stdout"
38//usage: "\n -f Follow cursor"
39//usage: "\n -F Assume console is on a framebuffer device"
40//usage: "\n -Q Disable exit on ESC-ESC-ESC"
41//usage: "\n -x COL Starting column"
42//usage: "\n -y LINE Starting line"
43
44#include "libbb.h"
45#include "common_bufsiz.h"
46#include <sys/kd.h>
47
48#define ESC "\033"
49#define CURSOR_ON -1
50#define CURSOR_OFF 1
51
52#define DEV_TTY "/dev/tty"
53#define DEV_VCSA "/dev/vcsa"
54
55struct screen_info {
56 unsigned char lines, cols, cursor_x, cursor_y;
57};
58
59#define CHAR(x) (*(uint8_t*)(x))
60#define ATTR(x) (((uint8_t*)(x))[1])
61#define NEXT(x) ((x) += 2)
62#define DATA(x) (*(uint16_t*)(x))
63
64struct globals {
65 char* data;
66 int size;
67 int x, y;
68 int kbd_fd;
69 int ioerror_count;
70 int key_count;
71 int escape_count;
72 int nokeys;
73 int current;
74 int first_line_offset;
75 int last_attr;
76 // cached local tty parameters
77 unsigned width;
78 unsigned height;
79 unsigned col;
80 unsigned line;
81 smallint curoff; // unknown:0 cursor on:-1 cursor off:1
82 char attrbuf[sizeof("0;1;5;30;40m")];
83 // remote console
84 struct screen_info remote;
85 // saved local tty terminfo
86 struct termios term_orig;
87 char vcsa_name[sizeof(DEV_VCSA "NN")];
88};
89
90#define G (*ptr_to_globals)
91#define INIT_G() do { \
92 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
93 G.width = G.height = UINT_MAX; \
94 G.last_attr--; \
95} while (0)
96
97enum {
98 FLAG_v, // view only
99 FLAG_c, // create device if need
100 FLAG_Q, // never exit
101 FLAG_s, // session
102 FLAG_n, // no colors
103 FLAG_d, // dump screen
104 FLAG_f, // follow cursor
105 FLAG_F, // framebuffer
106};
107#define FLAG(x) (1 << FLAG_##x)
108#define BW (option_mask32 & FLAG(n))
109
110static void putcsi(const char *s)
111{
112 fputs(ESC"[", stdout);
113 fputs(s, stdout);
114}
115
116static void clrscr(void)
117{
118 // Home, clear till end of screen
119 putcsi("1;1H" ESC"[J");
120 G.col = G.line = 0;
121}
122
123static void set_cursor(int state)
124{
125 if (G.curoff != state) {
126 G.curoff = state;
127 putcsi("?25");
128 bb_putchar("h?l"[1 + state]);
129 }
130}
131
132static void gotoxy(int col, int line)
133{
134 if (G.col != col || G.line != line) {
135 G.col = col;
136 G.line = line;
137 printf(ESC"[%u;%uH", line + 1, col + 1);
138 }
139}
140
141static void cleanup(int code) NORETURN;
142static void cleanup(int code)
143{
144 set_cursor(CURSOR_ON);
145 tcsetattr(G.kbd_fd, TCSANOW, &G.term_orig);
146 if (ENABLE_FEATURE_CLEAN_UP) {
147 close(G.kbd_fd);
148 }
149 // Reset attributes
150 if (!BW)
151 putcsi("0m");
152 bb_putchar('\n');
153 if (code > EXIT_FAILURE)
154 kill_myself_with_sig(code);
155 exit(code);
156}
157
158static void screen_read_close(void)
159{
160 unsigned i, j;
161 int vcsa_fd;
162 char *data;
163
164 // Close & re-open vcsa in case they have swapped virtual consoles
165 vcsa_fd = xopen(G.vcsa_name, O_RDONLY);
166 xread(vcsa_fd, &G.remote, 4);
167 i = G.remote.cols * 2;
168 G.first_line_offset = G.y * i;
169 i *= G.remote.lines;
170 if (G.data == NULL) {
171 G.size = i;
172 G.data = xzalloc(2 * i);
173 }
174 if (G.size != i) {
175 cleanup(EXIT_FAILURE);
176 }
177 data = G.data + G.current;
178 xread(vcsa_fd, data, G.size);
179 close(vcsa_fd);
180 for (i = 0; i < G.remote.lines; i++) {
181 for (j = 0; j < G.remote.cols; j++, NEXT(data)) {
182 unsigned x = j - G.x; // if will catch j < G.x too
183 unsigned y = i - G.y; // if will catch i < G.y too
184
185 if (y >= G.height || x >= G.width)
186 DATA(data) = 0;
187 else {
188 uint8_t ch = CHAR(data);
189 if (ch < ' ')
190 CHAR(data) = ch | 0x40;
191 else if (ch > 0x7e)
192 CHAR(data) = '?';
193 }
194 }
195 }
196}
197
198static void screen_char(char *data)
199{
200 if (!BW) {
201 uint8_t attr_diff;
202 uint8_t attr = ATTR(data);
203
204 if (option_mask32 & FLAG(F)) {
205 attr >>= 1;
206 }
207 attr_diff = G.last_attr ^ attr;
208 if (attr_diff) {
209// Attribute layout for VGA compatible text videobuffer:
210// blinking text
211// |red bkgd
212// ||green bkgd
213// |||blue bkgd
214// vvvv
215// 00000000 <- lsb bit on the right
216// bold text / text 8th bit
217// red text
218// green text
219// blue text
220// TODO: apparently framebuffer-based console uses different layout
221// (bug? attempt to get 8th text bit in better position?)
222// red bkgd
223// |green bkgd
224// ||blue bkgd
225// vvv
226// 00000000 <- lsb bit on the right
227// bold text
228// red text
229// green text
230// blue text
231// text 8th bit
232 // converting RGB color bit triad to BGR:
233 static const char color[8] = "04261537";
234 const uint8_t fg_mask = 0x07, bold_mask = 0x08;
235 const uint8_t bg_mask = 0x70, blink_mask = 0x80;
236 char *ptr;
237
238 ptr = G.attrbuf;
239
240 // (G.last_attr & ~attr) has 1 only where
241 // G.last_attr has 1 but attr has 0.
242 // Here we check whether we have transition
243 // bold->non-bold or blink->non-blink:
244 if (G.last_attr < 0 // initial value
245 || ((G.last_attr & ~attr) & (bold_mask | blink_mask)) != 0
246 ) {
247 *ptr++ = '0'; // "reset all attrs"
248 *ptr++ = ';';
249 // must set fg & bg, maybe need to set bold or blink:
250 attr_diff = attr | ~(bold_mask | blink_mask);
251 }
252 G.last_attr = attr;
253 if (attr_diff & bold_mask) {
254 *ptr++ = '1';
255 *ptr++ = ';';
256 }
257 if (attr_diff & blink_mask) {
258 *ptr++ = '5';
259 *ptr++ = ';';
260 }
261 if (attr_diff & fg_mask) {
262 *ptr++ = '3';
263 *ptr++ = color[attr & fg_mask];
264 *ptr++ = ';';
265 }
266 if (attr_diff & bg_mask) {
267 *ptr++ = '4';
268 *ptr++ = color[(attr & bg_mask) >> 4];
269 ptr++; // last attribute
270 }
271 if (ptr != G.attrbuf) {
272 ptr[-1] = 'm';
273 *ptr = '\0';
274 putcsi(G.attrbuf);
275 }
276 }
277 }
278 putchar(CHAR(data));
279 G.col++;
280}
281
282static void screen_dump(void)
283{
284 int linefeed_cnt;
285 int line, col;
286 int linecnt = G.remote.lines - G.y;
287 char *data = G.data + G.current + G.first_line_offset;
288
289 linefeed_cnt = 0;
290 for (line = 0; line < linecnt && line < G.height; line++) {
291 int space_cnt = 0;
292 for (col = 0; col < G.remote.cols; col++, NEXT(data)) {
293 unsigned tty_col = col - G.x; // if will catch col < G.x too
294
295 if (tty_col >= G.width)
296 continue;
297 space_cnt++;
298 if (BW && CHAR(data) == ' ')
299 continue;
300 while (linefeed_cnt != 0) {
301 //bb_putchar('\r'); - tty driver does it for us
302 bb_putchar('\n');
303 linefeed_cnt--;
304 }
305 while (--space_cnt)
306 bb_putchar(' ');
307 screen_char(data);
308 }
309 linefeed_cnt++;
310 }
311}
312
313static void curmove(void)
314{
315 unsigned cx = G.remote.cursor_x - G.x;
316 unsigned cy = G.remote.cursor_y - G.y;
317 int cursor = CURSOR_OFF;
318
319 if (cx < G.width && cy < G.height) {
320 gotoxy(cx, cy);
321 cursor = CURSOR_ON;
322 }
323 set_cursor(cursor);
324}
325
326static void create_cdev_if_doesnt_exist(const char* name, dev_t dev)
327{
328 int fd = open(name, O_RDONLY);
329 if (fd != -1)
330 close(fd);
331 else if (errno == ENOENT)
332 mknod(name, S_IFCHR | 0660, dev);
333}
334
335static NOINLINE void start_shell_in_child(const char* tty_name)
336{
337 int pid = xvfork();
338 if (pid == 0) {
339 struct termios termchild;
340 const char *shell = get_shell_name();
341
342 signal(SIGHUP, SIG_IGN);
343 // set tty as a controlling tty
344 setsid();
345 // make tty to be input, output, error
346 close(0);
347 xopen(tty_name, O_RDWR); // uses fd 0
348 xdup2(0, 1);
349 xdup2(0, 2);
350 ioctl(0, TIOCSCTTY, 1);
351 tcsetpgrp(0, getpid());
352 tcgetattr(0, &termchild);
353 termchild.c_lflag |= ECHO;
354 termchild.c_oflag |= ONLCR | XTABS;
355 termchild.c_iflag |= ICRNL;
356 termchild.c_iflag &= ~IXOFF;
357 tcsetattr_stdin_TCSANOW(&termchild);
358 execl(shell, shell, "-i", (char *) NULL);
359 bb_simple_perror_msg_and_die(shell);
360 }
361}
362
363int conspy_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
364int conspy_main(int argc UNUSED_PARAM, char **argv)
365{
366 char tty_name[sizeof(DEV_TTY "NN")];
367 struct termios termbuf;
368 unsigned opts;
369 unsigned ttynum;
370 int poll_timeout_ms;
371#if ENABLE_LONG_OPTS
372 static const char getopt_longopts[] ALIGN1 =
373 "viewonly\0" No_argument "v"
374 "createdevice\0" No_argument "c"
375 "neverquit\0" No_argument "Q"
376 "session\0" No_argument "s"
377 "nocolors\0" No_argument "n"
378 "dump\0" No_argument "d"
379 "follow\0" No_argument "f"
380 "framebuffer\0" No_argument "F"
381 ;
382
383 applet_long_options = getopt_longopts;
384#endif
385#define keybuf bb_common_bufsiz1
386 setup_common_bufsiz();
387
388 INIT_G();
389 strcpy(G.vcsa_name, DEV_VCSA);
390
391 opt_complementary = "x+:y+"; // numeric params
392 opts = getopt32(argv, "vcQsndfFx:y:", &G.x, &G.y);
393 argv += optind;
394 ttynum = 0;
395 if (argv[0]) {
396 ttynum = xatou_range(argv[0], 0, 63);
397 sprintf(G.vcsa_name + sizeof(DEV_VCSA)-1, "%u", ttynum);
398 }
399 sprintf(tty_name, "%s%u", DEV_TTY, ttynum);
400 if (opts & FLAG(c)) {
401 if ((opts & (FLAG(s)|FLAG(v))) != FLAG(v))
402 create_cdev_if_doesnt_exist(tty_name, makedev(4, ttynum));
403 create_cdev_if_doesnt_exist(G.vcsa_name, makedev(7, 128 + ttynum));
404 }
405 if ((opts & FLAG(s)) && ttynum) {
406 start_shell_in_child(tty_name);
407 }
408
409 screen_read_close();
410 if (opts & FLAG(d)) {
411 screen_dump();
412 bb_putchar('\n');
413 return 0;
414 }
415
416 bb_signals(BB_FATAL_SIGS, cleanup);
417
418 // All characters must be passed through to us unaltered
419 G.kbd_fd = xopen(CURRENT_TTY, O_RDONLY);
420 tcgetattr(G.kbd_fd, &G.term_orig);
421 termbuf = G.term_orig;
422 termbuf.c_iflag &= ~(BRKINT|INLCR|ICRNL|IXON|IXOFF|IUCLC|IXANY|IMAXBEL);
423 //termbuf.c_oflag &= ~(OPOST); - no, we still want \n -> \r\n
424 termbuf.c_lflag &= ~(ISIG|ICANON|ECHO);
425 termbuf.c_cc[VMIN] = 1;
426 termbuf.c_cc[VTIME] = 0;
427 tcsetattr(G.kbd_fd, TCSANOW, &termbuf);
428
429 poll_timeout_ms = 250;
430 while (1) {
431 struct pollfd pfd;
432 int bytes_read;
433 int i, j;
434 char *data, *old;
435
436 // in the first loop G.width = G.height = 0: refresh
437 i = G.width;
438 j = G.height;
439 get_terminal_width_height(G.kbd_fd, &G.width, &G.height);
440 if (option_mask32 & FLAG(f)) {
441 int nx = G.remote.cursor_x - G.width + 1;
442 int ny = G.remote.cursor_y - G.height + 1;
443
444 if (G.remote.cursor_x < G.x) {
445 G.x = G.remote.cursor_x;
446 i = 0; // force refresh
447 }
448 if (nx > G.x) {
449 G.x = nx;
450 i = 0; // force refresh
451 }
452 if (G.remote.cursor_y < G.y) {
453 G.y = G.remote.cursor_y;
454 i = 0; // force refresh
455 }
456 if (ny > G.y) {
457 G.y = ny;
458 i = 0; // force refresh
459 }
460 }
461
462 // Scan console data and redraw our tty where needed
463 old = G.data + G.current;
464 G.current = G.size - G.current;
465 data = G.data + G.current;
466 screen_read_close();
467 if (i != G.width || j != G.height) {
468 clrscr();
469 screen_dump();
470 } else {
471 // For each remote line
472 old += G.first_line_offset;
473 data += G.first_line_offset;
474 for (i = G.y; i < G.remote.lines; i++) {
475 char *first = NULL; // first char which needs updating
476 char *last = last; // last char which needs updating
477 unsigned iy = i - G.y;
478
479 if (iy >= G.height)
480 break;
481 for (j = 0; j < G.remote.cols; j++, NEXT(old), NEXT(data)) {
482 unsigned jx = j - G.x; // if will catch j >= G.x too
483
484 if (jx < G.width && DATA(data) != DATA(old)) {
485 last = data;
486 if (!first) {
487 first = data;
488 gotoxy(jx, iy);
489 }
490 }
491 }
492 if (first) {
493 // Rewrite updated data on the local screen
494 for (; first <= last; NEXT(first))
495 screen_char(first);
496 }
497 }
498 }
499 curmove();
500
501 // Wait for local user keypresses
502 fflush_all();
503 pfd.fd = G.kbd_fd;
504 pfd.events = POLLIN;
505 bytes_read = 0;
506 switch (poll(&pfd, 1, poll_timeout_ms)) {
507 char *k;
508 case -1:
509 if (errno != EINTR)
510 goto abort;
511 break;
512 case 0:
513 if (++G.nokeys >= 4)
514 G.nokeys = G.escape_count = 0;
515 break;
516 default:
517 // Read the keys pressed
518 k = keybuf + G.key_count;
519 bytes_read = read(G.kbd_fd, k, COMMON_BUFSIZE - G.key_count);
520 if (bytes_read < 0)
521 goto abort;
522
523 // Do exit processing
524 if (!(option_mask32 & FLAG(Q))) {
525 for (i = 0; i < bytes_read; i++) {
526 if (k[i] != '\033')
527 G.escape_count = -1;
528 if (++G.escape_count >= 3)
529 cleanup(EXIT_SUCCESS);
530 }
531 }
532 }
533 poll_timeout_ms = 250;
534 if (option_mask32 & FLAG(v)) continue;
535
536 // Insert all keys pressed into the virtual console's input
537 // buffer. Don't do this if the virtual console is in scan
538 // code mode - giving ASCII characters to a program expecting
539 // scan codes will confuse it.
540 G.key_count += bytes_read;
541 if (G.escape_count == 0) {
542 int handle, result;
543 long kbd_mode;
544
545 handle = xopen(tty_name, O_WRONLY);
546 result = ioctl(handle, KDGKBMODE, &kbd_mode);
547 if (result >= 0) {
548 char *p = keybuf;
549
550 G.ioerror_count = 0;
551 if (kbd_mode != K_XLATE && kbd_mode != K_UNICODE) {
552 G.key_count = 0; // scan code mode
553 }
554 for (; G.key_count != 0; p++, G.key_count--) {
555 result = ioctl(handle, TIOCSTI, p);
556 if (result < 0) {
557 memmove(keybuf, p, G.key_count);
558 break;
559 }
560 // If there is an application on console which reacts
561 // to keypresses, we need to make our first sleep
562 // shorter to quickly redraw whatever it printed there.
563 poll_timeout_ms = 20;
564 }
565 }
566 // We sometimes get spurious IO errors on the TTY
567 // as programs close and re-open it
568 else if (errno != EIO || ++G.ioerror_count > 4) {
569 if (ENABLE_FEATURE_CLEAN_UP)
570 close(handle);
571 goto abort;
572 }
573 // Close & re-open tty in case they have
574 // swapped virtual consoles
575 close(handle);
576 }
577 } /* while (1) */
578 abort:
579 cleanup(EXIT_FAILURE);
580}
Note: See TracBrowser for help on using the repository browser.