1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42#include "libbb.h"
43#include <sys/kd.h>
44
45
46#define ESC "\033"
47
48struct screen_info {
49 unsigned char lines, cols, cursor_x, cursor_y;
50};
51
52#define CHAR(x) (*(uint8_t*)(x))
53#define ATTR(x) (((uint8_t*)(x))[1])
54#define NEXT(x) ((x) += 2)
55#define DATA(x) (*(uint16_t*)(x))
56
57struct globals {
58 char* data;
59 int size;
60 int x, y;
61 int kbd_fd;
62 int ioerror_count;
63 int key_count;
64 int escape_count;
65 int nokeys;
66 int current;
67 int first_line_offset;
68 int last_attr;
69
70 unsigned width;
71 unsigned height;
72 unsigned col;
73 unsigned line;
74 smallint curoff;
75 char attrbuf[sizeof(ESC"[0;1;5;30;40m")];
76
77 struct screen_info remote;
78
79 struct termios term_orig;
80 char vcsa_name[sizeof("/dev/vcsaNN")];
81};
82
83#define G (*ptr_to_globals)
84#define INIT_G() do { \
85 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
86 G.attrbuf[0] = '\033'; \
87 G.attrbuf[1] = '['; \
88 G.width = G.height = UINT_MAX; \
89 G.last_attr--; \
90} while (0)
91
92enum {
93 FLAG_v,
94 FLAG_c,
95 FLAG_s,
96 FLAG_n,
97 FLAG_d,
98 FLAG_f,
99};
100#define FLAG(x) (1 << FLAG_##x)
101#define BW (option_mask32 & FLAG(n))
102
103static void clrscr(void)
104{
105
106 fputs(ESC"[1;1H" ESC"[J", stdout);
107 G.col = G.line = 0;
108}
109
110static void set_cursor(int state)
111{
112 if (G.curoff != state) {
113 G.curoff = state;
114 fputs(ESC"[?25", stdout);
115 bb_putchar("h?l"[1 + state]);
116 }
117}
118
119static void gotoxy(int col, int line)
120{
121 if (G.col != col || G.line != line) {
122 G.col = col;
123 G.line = line;
124 printf(ESC"[%u;%uH", line + 1, col + 1);
125 }
126}
127
128static void cleanup(int code)
129{
130 set_cursor(-1);
131 tcsetattr(G.kbd_fd, TCSANOW, &G.term_orig);
132 if (ENABLE_FEATURE_CLEAN_UP) {
133 close(G.kbd_fd);
134 }
135
136 if (!BW)
137 fputs(ESC"[0m", stdout);
138 bb_putchar('\n');
139 if (code > 1)
140 kill_myself_with_sig(code);
141 exit(code);
142}
143
144static void screen_read_close(void)
145{
146 unsigned i, j;
147 int vcsa_fd;
148 char *data;
149
150
151 vcsa_fd = xopen(G.vcsa_name, O_RDONLY);
152 xread(vcsa_fd, &G.remote, 4);
153 i = G.remote.cols * 2;
154 G.first_line_offset = G.y * i;
155 i *= G.remote.lines;
156 if (G.data == NULL) {
157 G.size = i;
158 G.data = xzalloc(2 * i);
159 }
160 else if (G.size != i) {
161 cleanup(1);
162 }
163 data = G.data + G.current;
164 xread(vcsa_fd, data, G.size);
165 close(vcsa_fd);
166 for (i = 0; i < G.remote.lines; i++) {
167 for (j = 0; j < G.remote.cols; j++, NEXT(data)) {
168 unsigned x = j - G.x;
169 unsigned y = i - G.y;
170
171 if (CHAR(data) < ' ')
172 CHAR(data) = ' ';
173 if (y >= G.height || x >= G.width)
174 DATA(data) = 0;
175 }
176 }
177}
178
179static void screen_char(char *data)
180{
181 if (!BW) {
182 uint8_t attr = ATTR(data);
183
184 uint8_t attr_diff = G.last_attr ^ attr;
185
186 if (attr_diff) {
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211 static const char color[8] = "04261537";
212 const uint8_t fg_mask = 0x07, bold_mask = 0x08;
213 const uint8_t bg_mask = 0x70, blink_mask = 0x80;
214 char *ptr;
215
216 ptr = G.attrbuf + 2;
217
218
219
220
221
222 if (G.last_attr < 0
223 || ((G.last_attr & ~attr) & (bold_mask | blink_mask)) != 0
224 ) {
225 *ptr++ = '0';
226 *ptr++ = ';';
227
228 attr_diff = attr | ~(bold_mask | blink_mask);
229 }
230 G.last_attr = attr;
231 if (attr_diff & bold_mask) {
232 *ptr++ = '1';
233 *ptr++ = ';';
234 }
235 if (attr_diff & blink_mask) {
236 *ptr++ = '5';
237 *ptr++ = ';';
238 }
239 if (attr_diff & fg_mask) {
240 *ptr++ = '3';
241 *ptr++ = color[attr & fg_mask];
242 *ptr++ = ';';
243 }
244 if (attr_diff & bg_mask) {
245 *ptr++ = '4';
246 *ptr++ = color[(attr & bg_mask) >> 4];
247 *ptr++ = ';';
248 }
249 if (ptr != G.attrbuf + 2) {
250 ptr[-1] = 'm';
251 *ptr = '\0';
252 fputs(G.attrbuf, stdout);
253 }
254 }
255 }
256 putchar(CHAR(data));
257 G.col++;
258}
259
260static void screen_dump(void)
261{
262 int linefeed_cnt;
263 int line, col;
264 int linecnt = G.remote.lines - G.y;
265 char *data = G.data + G.current + G.first_line_offset;
266
267 linefeed_cnt = 0;
268 for (line = 0; line < linecnt && line < G.height; line++) {
269 int space_cnt = 0;
270 for (col = 0; col < G.remote.cols; col++, NEXT(data)) {
271 unsigned tty_col = col - G.x;
272
273 if (tty_col >= G.width)
274 continue;
275 space_cnt++;
276 if (BW && CHAR(data) == ' ')
277 continue;
278 while (linefeed_cnt != 0) {
279
280 bb_putchar('\n');
281 linefeed_cnt--;
282 }
283 while (--space_cnt)
284 bb_putchar(' ');
285 screen_char(data);
286 }
287 linefeed_cnt++;
288 }
289}
290
291static void curmove(void)
292{
293 unsigned cx = G.remote.cursor_x - G.x;
294 unsigned cy = G.remote.cursor_y - G.y;
295 int cursor = 1;
296
297 if (cx < G.width && cy < G.height) {
298 gotoxy(cx, cy);
299 cursor = -1;
300 }
301 set_cursor(cursor);
302}
303
304static void create_cdev_if_doesnt_exist(const char* name, dev_t dev)
305{
306 int fd = open(name, O_RDONLY);
307 if (fd != -1)
308 close(fd);
309 else if (errno == ENOENT)
310 mknod(name, S_IFCHR | 0660, dev);
311}
312
313static NOINLINE void start_shell_in_child(const char* tty_name)
314{
315 int pid = xvfork();
316 if (pid == 0) {
317 struct termios termchild;
318 const char *shell = get_shell_name();
319
320 signal(SIGHUP, SIG_IGN);
321
322 setsid();
323
324 close(0);
325 xopen(tty_name, O_RDWR);
326 xdup2(0, 1);
327 xdup2(0, 2);
328 ioctl(0, TIOCSCTTY, 1);
329 tcsetpgrp(0, getpid());
330 tcgetattr(0, &termchild);
331 termchild.c_lflag |= ECHO;
332 termchild.c_oflag |= ONLCR | XTABS;
333 termchild.c_iflag |= ICRNL;
334 termchild.c_iflag &= ~IXOFF;
335 tcsetattr_stdin_TCSANOW(&termchild);
336 execl(shell, shell, "-i", (char *) NULL);
337 bb_simple_perror_msg_and_die(shell);
338 }
339}
340
341int conspy_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
342int conspy_main(int argc UNUSED_PARAM, char **argv)
343{
344 char tty_name[sizeof("/dev/ttyNN")];
345#define keybuf bb_common_bufsiz1
346 struct termios termbuf;
347 unsigned opts;
348 unsigned ttynum;
349 int poll_timeout_ms;
350#if ENABLE_LONG_OPTS
351 static const char getopt_longopts[] ALIGN1 =
352 "viewonly\0" No_argument "v"
353 "createdevice\0" No_argument "c"
354 "session\0" No_argument "s"
355 "nocolors\0" No_argument "n"
356 "dump\0" No_argument "d"
357 "follow\0" No_argument "f"
358 ;
359
360 applet_long_options = getopt_longopts;
361#endif
362 INIT_G();
363 strcpy(G.vcsa_name, "/dev/vcsa");
364
365 opt_complementary = "x+:y+";
366 opts = getopt32(argv, "vcsndfx:y:", &G.x, &G.y);
367 argv += optind;
368 ttynum = 0;
369 if (argv[0]) {
370 ttynum = xatou_range(argv[0], 0, 63);
371 sprintf(G.vcsa_name + sizeof("/dev/vcsa")-1, "%u", ttynum);
372 }
373 sprintf(tty_name, "%s%u", "/dev/tty", ttynum);
374 if (opts & FLAG(c)) {
375 if ((opts & (FLAG(s)|FLAG(v))) != FLAG(v))
376 create_cdev_if_doesnt_exist(tty_name, makedev(4, ttynum));
377 create_cdev_if_doesnt_exist(G.vcsa_name, makedev(7, 128 + ttynum));
378 }
379 if ((opts & FLAG(s)) && ttynum) {
380 start_shell_in_child(tty_name);
381 }
382
383 screen_read_close();
384 if (opts & FLAG(d)) {
385 screen_dump();
386 bb_putchar('\n');
387 return 0;
388 }
389
390 bb_signals(BB_FATAL_SIGS, cleanup);
391
392
393 G.kbd_fd = xopen(CURRENT_TTY, O_RDONLY);
394 tcgetattr(G.kbd_fd, &G.term_orig);
395 termbuf = G.term_orig;
396 termbuf.c_iflag &= ~(BRKINT|INLCR|ICRNL|IXON|IXOFF|IUCLC|IXANY|IMAXBEL);
397
398 termbuf.c_lflag &= ~(ISIG|ICANON|ECHO);
399 termbuf.c_cc[VMIN] = 1;
400 termbuf.c_cc[VTIME] = 0;
401 tcsetattr(G.kbd_fd, TCSANOW, &termbuf);
402
403 poll_timeout_ms = 250;
404 while (1) {
405 struct pollfd pfd;
406 int bytes_read;
407 int i, j;
408 char *data, *old;
409
410
411 i = G.width;
412 j = G.height;
413 get_terminal_width_height(G.kbd_fd, &G.width, &G.height);
414 if (option_mask32 & FLAG(f)) {
415 int nx = G.remote.cursor_x - G.width + 1;
416 int ny = G.remote.cursor_y - G.height + 1;
417
418 if (G.remote.cursor_x < G.x) {
419 G.x = G.remote.cursor_x;
420 i = 0;
421 }
422 if (nx > G.x) {
423 G.x = nx;
424 i = 0;
425 }
426 if (G.remote.cursor_y < G.y) {
427 G.y = G.remote.cursor_y;
428 i = 0;
429 }
430 if (ny > G.y) {
431 G.y = ny;
432 i = 0;
433 }
434 }
435
436
437 old = G.data + G.current;
438 G.current = G.size - G.current;
439 data = G.data + G.current;
440 screen_read_close();
441 if (i != G.width || j != G.height) {
442 clrscr();
443 screen_dump();
444 } else {
445
446 old += G.first_line_offset;
447 data += G.first_line_offset;
448 for (i = G.y; i < G.remote.lines; i++) {
449 char *first = NULL;
450 char *last = last;
451 unsigned iy = i - G.y;
452
453 if (iy >= G.height)
454 break;
455 for (j = 0; j < G.remote.cols; j++, NEXT(old), NEXT(data)) {
456 unsigned jx = j - G.x;
457
458 if (jx < G.width && DATA(data) != DATA(old)) {
459 last = data;
460 if (!first) {
461 first = data;
462 gotoxy(jx, iy);
463 }
464 }
465 }
466 if (first) {
467
468 for (; first <= last; NEXT(first))
469 screen_char(first);
470 }
471 }
472 }
473 curmove();
474
475
476 fflush_all();
477 pfd.fd = G.kbd_fd;
478 pfd.events = POLLIN;
479 bytes_read = 0;
480 switch (poll(&pfd, 1, poll_timeout_ms)) {
481 char *k;
482 case -1:
483 if (errno != EINTR)
484 cleanup(1);
485 break;
486 case 0:
487 if (++G.nokeys >= 4)
488 G.nokeys = G.escape_count = 0;
489 break;
490 default:
491
492 k = keybuf + G.key_count;
493 bytes_read = read(G.kbd_fd, k, sizeof(keybuf) - G.key_count);
494 if (bytes_read < 0)
495 cleanup(1);
496
497
498 for (i = 0; i < bytes_read; i++) {
499 if (k[i] != '\033')
500 G.escape_count = 0;
501 else if (++G.escape_count >= 3)
502 cleanup(0);
503 }
504 }
505 poll_timeout_ms = 250;
506 if (option_mask32 & FLAG(v)) continue;
507
508
509
510
511
512 G.key_count += bytes_read;
513 if (G.escape_count == 0) {
514 int handle, result;
515 long kbd_mode;
516
517 handle = xopen(tty_name, O_WRONLY);
518 result = ioctl(handle, KDGKBMODE, &kbd_mode);
519 if (result >= 0) {
520 char *p = keybuf;
521
522 if (kbd_mode != K_XLATE && kbd_mode != K_UNICODE) {
523 G.key_count = 0;
524 }
525 for (; G.key_count != 0; p++, G.key_count--) {
526 result = ioctl(handle, TIOCSTI, p);
527 if (result < 0) {
528 memmove(keybuf, p, G.key_count);
529 break;
530 }
531
532
533
534 poll_timeout_ms = 20;
535 }
536 }
537
538
539 close(handle);
540
541
542
543 if (result >= 0)
544 G.ioerror_count = 0;
545 else if (errno != EIO || ++G.ioerror_count > 4)
546 cleanup(1);
547 }
548 }
549}
550