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 | } | ||
