diff options
author | Pascal Bellard <pascal.bellard@ads-lu.com> | 2010-06-21 02:17:29 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2010-06-21 02:17:29 +0200 |
commit | a8b594fda7cbfcc2aadf9779ee40b264eeec86b2 (patch) | |
tree | 09be4efc8b552668b1db3542549559c1f804fe53 | |
parent | d2b738a6d27ceab834c35382100948a2fdd55b44 (diff) | |
download | busybox-w32-a8b594fda7cbfcc2aadf9779ee40b264eeec86b2.tar.gz busybox-w32-a8b594fda7cbfcc2aadf9779ee40b264eeec86b2.tar.bz2 busybox-w32-a8b594fda7cbfcc2aadf9779ee40b264eeec86b2.zip |
conspy: new applet
Signed-off-by: Pascal Bellard <pascal.bellard@ads-lu.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | miscutils/conspy.c | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/miscutils/conspy.c b/miscutils/conspy.c new file mode 100644 index 000000000..7ba7959a2 --- /dev/null +++ b/miscutils/conspy.c | |||
@@ -0,0 +1,472 @@ | |||
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 tarball for details. | ||
11 | * | ||
12 | * example : conspy num shared access to console num | ||
13 | * or conspy -d num screenshot of console num | ||
14 | * or conspy -cs num poor man's GNU screen like | ||
15 | */ | ||
16 | |||
17 | //applet:IF_CONSPY(APPLET(conspy, _BB_DIR_BIN, _BB_SUID_DROP)) | ||
18 | |||
19 | //kbuild:lib-$(CONFIG_CONSPY) += conspy.o | ||
20 | |||
21 | //config:config CONSPY | ||
22 | //config: bool "conspy" | ||
23 | //config: default n | ||
24 | //config: help | ||
25 | //config: A text-mode VNC like program for Linux virtual terminals. | ||
26 | //config: example : conspy num shared access to console num | ||
27 | //config: or conspy -d num screenshot of console num | ||
28 | //config: or conspy -cs num poor man's GNU screen like | ||
29 | |||
30 | //usage:#define conspy_trivial_usage | ||
31 | //usage: "[-vcsndf] [-x ROW] [-y LINE] [CONSOLE_NO]" | ||
32 | //usage:#define conspy_full_usage "\n\n" | ||
33 | //usage: "A text-mode VNC like program for Linux virtual consoles." | ||
34 | //usage: "\nTo exit, quickly press ESC 3 times." | ||
35 | //usage: "\n" | ||
36 | //usage: "\nOptions:" | ||
37 | //usage: "\n -v Don't send keystrokes to the console" | ||
38 | //usage: "\n -c Create missing devices in /dev" | ||
39 | //usage: "\n -s Open a SHELL session" | ||
40 | //usage: "\n -n Black & white" | ||
41 | //usage: "\n -d Dump console to stdout" | ||
42 | //usage: "\n -f Follow cursor" | ||
43 | //usage: "\n -x ROW Starting row" | ||
44 | //usage: "\n -y LINE Starting line" | ||
45 | |||
46 | #include "libbb.h" | ||
47 | #include <sys/kd.h> | ||
48 | |||
49 | struct screen_info { | ||
50 | unsigned char lines, rows, cursor_x, cursor_y; | ||
51 | }; | ||
52 | |||
53 | #define CHAR(x) ((x)[0]) | ||
54 | #define ATTR(x) ((x)[1]) | ||
55 | #define NEXT(x) ((x)+=2) | ||
56 | #define DATA(x) (* (short *) (x)) | ||
57 | |||
58 | struct globals { | ||
59 | char* data; | ||
60 | int size; | ||
61 | int x, y; | ||
62 | int kbd_fd; | ||
63 | unsigned width; | ||
64 | unsigned height; | ||
65 | char mask; | ||
66 | char last_attr; | ||
67 | struct screen_info info; | ||
68 | struct termios term_orig; | ||
69 | }; | ||
70 | |||
71 | #define G (*ptr_to_globals) | ||
72 | #define INIT_G() do { \ | ||
73 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | ||
74 | } while (0) | ||
75 | |||
76 | static void screen_read_close(int fd, char *data) | ||
77 | { | ||
78 | unsigned i, j; | ||
79 | |||
80 | xread(fd, data, G.size); | ||
81 | G.last_attr = 0; | ||
82 | for (i = 0; i < G.info.lines; i++) { | ||
83 | for (j = 0; j < G.info.rows; j++, NEXT(data)) { | ||
84 | unsigned x = j - G.x; // if will catch j < G.x too | ||
85 | unsigned y = i - G.y; // if will catch i < G.y too | ||
86 | |||
87 | if (CHAR(data) < ' ') | ||
88 | CHAR(data) = ' '; | ||
89 | if (y >= G.height || x >= G.width) | ||
90 | DATA(data) = 0; | ||
91 | } | ||
92 | } | ||
93 | close(fd); | ||
94 | } | ||
95 | |||
96 | static void screen_char(char *data) | ||
97 | { | ||
98 | if (((G.last_attr - ATTR(data)) & G.mask) != 0) { | ||
99 | // BLGCRMOW | ||
100 | static const char color[8] = "04261537"; | ||
101 | |||
102 | printf("\033[%c;4%c;3%cm", | ||
103 | (ATTR(data) & 8) ? '1' // bold | ||
104 | : '0', // defaults | ||
105 | color[(ATTR(data) >> 4) & 7], color[ATTR(data) & 7]); | ||
106 | G.last_attr = ATTR(data); | ||
107 | } | ||
108 | bb_putchar(CHAR(data)); | ||
109 | } | ||
110 | |||
111 | #define clrscr() printf("\033[1;1H" "\033[0J") | ||
112 | #define curoff() printf("\033[?25l") | ||
113 | |||
114 | static void curon(void) | ||
115 | { | ||
116 | printf("\033[?25h"); | ||
117 | } | ||
118 | |||
119 | static void gotoxy(int row, int line) | ||
120 | { | ||
121 | printf("\033[%u;%uH", line + 1, row + 1); | ||
122 | } | ||
123 | |||
124 | static void screen_dump(char *data) | ||
125 | { | ||
126 | int space, linefeed, line, row; | ||
127 | int linecnt = G.info.lines - G.y; | ||
128 | |||
129 | data += 2 * G.y * G.info.rows; | ||
130 | for (linefeed = line = 0; line < linecnt && line < G.height; line++) { | ||
131 | for (space = row = 0; row < G.info.rows; row++, NEXT(data)) { | ||
132 | unsigned tty_row = row - G.x; // if will catch row < G.x too | ||
133 | |||
134 | if (tty_row >= G.width) | ||
135 | continue; | ||
136 | space++; | ||
137 | if (((G.last_attr - ATTR(data)) & G.mask) && CHAR(data) == ' ') | ||
138 | continue; | ||
139 | while (linefeed != 0) { | ||
140 | bb_putchar('\r'); | ||
141 | bb_putchar('\n'); | ||
142 | linefeed--; | ||
143 | } | ||
144 | while (--space) | ||
145 | bb_putchar(' '); | ||
146 | screen_char(data); | ||
147 | } | ||
148 | linefeed++; | ||
149 | } | ||
150 | } | ||
151 | |||
152 | static void curmove(void) | ||
153 | { | ||
154 | unsigned cx = G.info.cursor_x - G.x; | ||
155 | unsigned cy = G.info.cursor_y - G.y; | ||
156 | |||
157 | if (cx >= G.width || cy >= G.height) { | ||
158 | curoff(); | ||
159 | } else { | ||
160 | curon(); | ||
161 | gotoxy(cx, cy); | ||
162 | } | ||
163 | fflush_all(); | ||
164 | } | ||
165 | |||
166 | static void cleanup(int code) | ||
167 | { | ||
168 | curon(); | ||
169 | fflush_all(); | ||
170 | tcsetattr(G.kbd_fd, TCSANOW, &G.term_orig); | ||
171 | if (ENABLE_FEATURE_CLEAN_UP) { | ||
172 | free(ptr_to_globals); | ||
173 | close(G.kbd_fd); | ||
174 | } | ||
175 | // Reset attributes | ||
176 | if (G.mask != 0) | ||
177 | printf("\033[0m"); | ||
178 | bb_putchar('\n'); | ||
179 | if (code > 1) | ||
180 | kill_myself_with_sig(code); // does not return | ||
181 | exit(code); | ||
182 | } | ||
183 | |||
184 | static void get_initial_data(const char* vcsa_name) | ||
185 | { | ||
186 | int size; | ||
187 | int fd = xopen(vcsa_name, O_RDONLY); | ||
188 | xread(fd, &G.info, 4); | ||
189 | G.size = size = G.info.rows * G.info.lines * 2; | ||
190 | G.width = G.height = UINT_MAX; | ||
191 | G.data = xzalloc(2 * size); | ||
192 | screen_read_close(fd, G.data); | ||
193 | } | ||
194 | |||
195 | static void create_cdev_if_doesnt_exist(const char* name, dev_t dev) | ||
196 | { | ||
197 | int fd = open(name, O_RDONLY); | ||
198 | if (fd != -1) | ||
199 | close(fd); | ||
200 | else if (errno == ENOENT) | ||
201 | mknod(name, S_IFCHR | 0660, dev); | ||
202 | } | ||
203 | |||
204 | static NOINLINE void start_shell_in_child(const char* tty_name) | ||
205 | { | ||
206 | int pid = vfork(); | ||
207 | if (pid < 0) { | ||
208 | bb_perror_msg_and_die("vfork"); | ||
209 | } | ||
210 | if (pid == 0) { | ||
211 | struct termios termchild; | ||
212 | char *shell = getenv("SHELL"); | ||
213 | |||
214 | if (!shell) | ||
215 | shell = (char *) DEFAULT_SHELL; | ||
216 | signal(SIGHUP, SIG_IGN); | ||
217 | // set tty as a controlling tty | ||
218 | setsid(); | ||
219 | // make tty to be input, output, error | ||
220 | close(0); | ||
221 | xopen(tty_name, O_RDWR); // uses fd 0 | ||
222 | xdup2(0, 1); | ||
223 | xdup2(0, 2); | ||
224 | ioctl(0, TIOCSCTTY, 1); | ||
225 | tcsetpgrp(0, getpid()); | ||
226 | tcgetattr(0, &termchild); | ||
227 | termchild.c_lflag |= ECHO; | ||
228 | termchild.c_oflag |= ONLCR | XTABS; | ||
229 | termchild.c_iflag |= ICRNL; | ||
230 | termchild.c_iflag &= ~IXOFF; | ||
231 | tcsetattr_stdin_TCSANOW(&termchild); | ||
232 | execl(shell, shell, "-i", (char *) NULL); | ||
233 | bb_simple_perror_msg_and_die(shell); | ||
234 | } | ||
235 | } | ||
236 | |||
237 | enum { | ||
238 | FLAG_v, // view only | ||
239 | FLAG_c, // create device if need | ||
240 | FLAG_s, // session | ||
241 | FLAG_n, // no colors | ||
242 | FLAG_d, // dump screen | ||
243 | FLAG_f, // follow cursor | ||
244 | }; | ||
245 | #define FLAG(x) (1 << FLAG_##x) | ||
246 | |||
247 | int conspy_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
248 | int conspy_main(int argc UNUSED_PARAM, char **argv) | ||
249 | { | ||
250 | char *buffer[2]; | ||
251 | char vcsa_name[sizeof("/dev/vcsa") + 2]; | ||
252 | char tty_name[sizeof("/dev/tty") + 2]; | ||
253 | #define keybuf bb_common_bufsiz1 | ||
254 | struct termios termbuf; | ||
255 | unsigned opts; | ||
256 | unsigned ttynum; | ||
257 | int poll_timeout_ms; | ||
258 | int current; | ||
259 | int ioerror_count; | ||
260 | int key_count; | ||
261 | int escape_count; | ||
262 | int nokeys; | ||
263 | #if ENABLE_LONG_OPTS | ||
264 | static const char getopt_longopts[] ALIGN1 = | ||
265 | "viewonly\0" No_argument "v" | ||
266 | "createdevice\0" No_argument "c" | ||
267 | "session\0" No_argument "s" | ||
268 | "nocolors\0" No_argument "n" | ||
269 | "dump\0" No_argument "d" | ||
270 | "follow\0" No_argument "f" | ||
271 | ; | ||
272 | |||
273 | applet_long_options = getopt_longopts; | ||
274 | #endif | ||
275 | INIT_G(); | ||
276 | strcpy(vcsa_name, "/dev/vcsa"); | ||
277 | |||
278 | opt_complementary = "x+:y+"; // numeric params | ||
279 | opts = getopt32(argv, "vcsndfx:y:", &G.x, &G.y); | ||
280 | argv += optind; | ||
281 | ttynum = 0; | ||
282 | if (argv[0]) { | ||
283 | ttynum = xatou_range(argv[0], 0, 63); | ||
284 | sprintf(vcsa_name + sizeof("/dev/vcsa")-1, "%u", ttynum); | ||
285 | } | ||
286 | sprintf(tty_name, "%s%u", "/dev/tty", ttynum); | ||
287 | if (!(opts & FLAG(n))) | ||
288 | G.mask = 0xff; | ||
289 | if (opts & FLAG(c)) { | ||
290 | if ((opts & (FLAG(s)|FLAG(v))) != FLAG(v)) | ||
291 | create_cdev_if_doesnt_exist(tty_name, makedev(4, ttynum)); | ||
292 | create_cdev_if_doesnt_exist(vcsa_name, makedev(7, 128 + ttynum)); | ||
293 | } | ||
294 | if ((opts & FLAG(s)) && ttynum) { | ||
295 | start_shell_in_child(tty_name); | ||
296 | } | ||
297 | |||
298 | get_initial_data(vcsa_name); | ||
299 | G.kbd_fd = xopen(CURRENT_TTY, O_RDONLY); | ||
300 | if (opts & FLAG(d)) { | ||
301 | screen_dump(G.data); | ||
302 | bb_putchar('\n'); | ||
303 | if (ENABLE_FEATURE_CLEAN_UP) { | ||
304 | free(ptr_to_globals); | ||
305 | close(G.kbd_fd); | ||
306 | } | ||
307 | return 0; | ||
308 | } | ||
309 | |||
310 | bb_signals(BB_FATAL_SIGS, cleanup); | ||
311 | // All characters must be passed through to us unaltered | ||
312 | tcgetattr(G.kbd_fd, &G.term_orig); | ||
313 | termbuf = G.term_orig; | ||
314 | termbuf.c_iflag &= ~(BRKINT|INLCR|ICRNL|IXON|IXOFF|IUCLC|IXANY|IMAXBEL); | ||
315 | termbuf.c_oflag &= ~(OPOST); | ||
316 | termbuf.c_lflag &= ~(ISIG|ICANON|ECHO); | ||
317 | termbuf.c_cc[VMIN] = 1; | ||
318 | termbuf.c_cc[VTIME] = 0; | ||
319 | tcsetattr(G.kbd_fd, TCSANOW, &termbuf); | ||
320 | buffer[0] = G.data; | ||
321 | buffer[1] = G.data + G.size; | ||
322 | poll_timeout_ms = 250; | ||
323 | ioerror_count = key_count = escape_count = nokeys = current = 0; | ||
324 | while (1) { | ||
325 | struct pollfd pfd; | ||
326 | int vcsa_handle; | ||
327 | int bytes_read; | ||
328 | int i, j; | ||
329 | int next = 1 - current; | ||
330 | char *data = buffer[next]; | ||
331 | char *old = buffer[current]; | ||
332 | |||
333 | // Close & re-open vcsa in case they have | ||
334 | // swapped virtual consoles | ||
335 | vcsa_handle = xopen(vcsa_name, O_RDONLY); | ||
336 | xread(vcsa_handle, &G.info, 4); | ||
337 | if (G.size != (G.info.rows * G.info.lines * 2)) { | ||
338 | cleanup(1); | ||
339 | } | ||
340 | i = G.width; | ||
341 | j = G.height; | ||
342 | get_terminal_width_height(G.kbd_fd, &G.width, &G.height); | ||
343 | if ((option_mask32 & FLAG(f))) { | ||
344 | int nx = G.info.cursor_x - G.width + 1; | ||
345 | int ny = G.info.cursor_y - G.height + 1; | ||
346 | |||
347 | if (G.info.cursor_x < G.x) { | ||
348 | G.x = G.info.cursor_x; | ||
349 | i = 0; // force refresh | ||
350 | } | ||
351 | if (nx > G.x) { | ||
352 | G.x = nx; | ||
353 | i = 0; // force refresh | ||
354 | } | ||
355 | if (G.info.cursor_y < G.y) { | ||
356 | G.y = G.info.cursor_y; | ||
357 | i = 0; // force refresh | ||
358 | } | ||
359 | if (ny > G.y) { | ||
360 | G.y = ny; | ||
361 | i = 0; // force refresh | ||
362 | } | ||
363 | } | ||
364 | |||
365 | // Scan console data and redraw our tty where needed | ||
366 | screen_read_close(vcsa_handle, data); | ||
367 | if (i != G.width || j != G.height) { | ||
368 | clrscr(); | ||
369 | screen_dump(data); | ||
370 | } | ||
371 | else for (i = 0; i < G.info.lines; i++) { | ||
372 | char *last = last; | ||
373 | char *first = NULL; | ||
374 | int iy = i - G.y; | ||
375 | |||
376 | if (iy >= (int) G.height) | ||
377 | break; | ||
378 | for (j = 0; j < G.info.rows; j++) { | ||
379 | last = data; | ||
380 | if (DATA(data) != DATA(old) && iy >= 0) { | ||
381 | unsigned jx = j - G.x; | ||
382 | |||
383 | last = NULL; | ||
384 | if (first == NULL && jx < G.width) { | ||
385 | first = data; | ||
386 | gotoxy(jx, iy); | ||
387 | } | ||
388 | } | ||
389 | NEXT(old); | ||
390 | NEXT(data); | ||
391 | } | ||
392 | if (first == NULL) | ||
393 | continue; | ||
394 | if (last == NULL) | ||
395 | last = data; | ||
396 | |||
397 | // Write the data to the screen | ||
398 | for (; first < last; NEXT(first)) | ||
399 | screen_char(first); | ||
400 | } | ||
401 | current = next; | ||
402 | curmove(); | ||
403 | |||
404 | // Wait for local user keypresses | ||
405 | pfd.fd = G.kbd_fd; | ||
406 | pfd.events = POLLIN; | ||
407 | bytes_read = 0; | ||
408 | switch (poll(&pfd, 1, poll_timeout_ms)) { | ||
409 | case -1: | ||
410 | if (errno != EINTR) | ||
411 | cleanup(1); | ||
412 | break; | ||
413 | case 0: | ||
414 | if (++nokeys >= 4) | ||
415 | nokeys = escape_count = 0; | ||
416 | break; | ||
417 | default: | ||
418 | // Read the keys pressed | ||
419 | bytes_read = read(G.kbd_fd, keybuf + key_count, | ||
420 | sizeof(keybuf) - key_count); | ||
421 | if (bytes_read < 0) | ||
422 | cleanup(1); | ||
423 | |||
424 | // Do exit processing | ||
425 | for (i = 0; i < bytes_read; i++) { | ||
426 | if (keybuf[key_count + i] != '\033') | ||
427 | escape_count = 0; | ||
428 | else if (++escape_count >= 3) | ||
429 | cleanup(0); | ||
430 | } | ||
431 | } | ||
432 | poll_timeout_ms = 250; | ||
433 | |||
434 | // Insert all keys pressed into the virtual console's input | ||
435 | // buffer. Don't do this if the virtual console is in scan | ||
436 | // code mode - giving ASCII characters to a program expecting | ||
437 | // scan codes will confuse it. | ||
438 | if (!(option_mask32 & FLAG(v)) && escape_count == 0) { | ||
439 | int handle, result; | ||
440 | long kbd_mode; | ||
441 | |||
442 | key_count += bytes_read; | ||
443 | handle = xopen(tty_name, O_WRONLY); | ||
444 | result = ioctl(handle, KDGKBMODE, &kbd_mode); | ||
445 | if (result == -1) | ||
446 | /* nothing */; | ||
447 | else if (kbd_mode != K_XLATE && kbd_mode != K_UNICODE) | ||
448 | key_count = 0; // scan code mode | ||
449 | else { | ||
450 | for (i = 0; i < key_count && result != -1; i++) | ||
451 | result = ioctl(handle, TIOCSTI, keybuf + i); | ||
452 | key_count -= i; | ||
453 | if (key_count) | ||
454 | memmove(keybuf, keybuf + i, key_count); | ||
455 | // If there is an application on console which reacts | ||
456 | // to keypresses, we need to make our first sleep | ||
457 | // shorter to quickly redraw whatever it printed there. | ||
458 | poll_timeout_ms = 20; | ||
459 | } | ||
460 | // Close & re-open tty in case they have | ||
461 | // swapped virtual consoles | ||
462 | close(handle); | ||
463 | |||
464 | // We sometimes get spurious IO errors on the TTY | ||
465 | // as programs close and re-open it | ||
466 | if (result != -1) | ||
467 | ioerror_count = 0; | ||
468 | else if (errno != EIO || ++ioerror_count > 4) | ||
469 | cleanup(1); | ||
470 | } | ||
471 | } | ||
472 | } | ||