A healthy dose of sillyness
[retty.git] / retty.c
blob8b7768d26578b14acf40a69399767819378aff6c
1 /* retty.c - attach process to current terminal
3 * Usage: retty PID
5 * PID is the pid of a running process.
7 * retty works on x86 Linux.
9 * Copyright (c) 2006 Petr Baudis, Jan Sembera
11 * ./-~~-\.
12 * | o o |
13 * | vv |
14 * \_. ._/
15 * \_> <_/
16 * |_/..\_|
17 * / \
21 * 'So!' cried Denethor. 'Thou hadst already stolen half my son's love. Now
22 * thou stealest the hearts of my knights also, so that they rob me wholly of
23 * my son at the last. But in this at least thou shalt not defy my will: to
24 * rule my own end.'
27 #define _GNU_SOURCE // grantpt & family
28 #include <signal.h>
29 #include <sys/ioctl.h>
30 #include <termios.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <fcntl.h>
36 #include <sys/ptrace.h>
37 #include <sys/user.h>
38 #include <sys/types.h>
39 #include <sys/wait.h>
40 #include <sys/select.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <stdbool.h>
45 #define VERSION "1.0"
48 void sigwinch(int x);
50 static int oldin, oldout, olderr, die, intr;
51 int stin = 0, sout = 1, serr = 2;
52 pid_t pid = 0;
53 bool forking = 0;
54 struct termios t_orig;
57 /* Write NLONG 4 byte words from BUF into PID starting
58 at address POS. Calling process must be attached to PID. */
59 static int
60 write_mem(pid_t pid, unsigned long *buf, int nlong, unsigned long pos)
62 unsigned long *p;
63 int i;
65 for (p = buf, i = 0; i < nlong; p++, i++)
66 if (0 > ptrace(PTRACE_POKEDATA, pid, pos+(i*4), *p))
67 return -1;
68 return 0;
72 static void
73 poke_32(unsigned char *data, off_t offset, uint32_t val)
75 *((uint32_t *)(&data[offset])) = val;
78 #ifdef DEBUG
79 void
80 dump_code(unsigned char *code, size_t size)
82 size_t i;
83 for (i = 0; i < size; i++) {
84 if (i % 8 == 0) {
85 printf("\n");
87 printf("0x%02x, ", code[i]);
90 #endif
93 static void
94 inject_attach(pid_t pid, int n, char ptsname[])
96 struct user_regs_struct regs;
97 unsigned long codeaddr, ptsnameaddr;
98 int waitst;
100 int fd_cervena = stin, fd_zelena = sout, fd_modra = serr;
101 int fd_fialova = stin, fd_oranzova = sout, fd_bezova = serr;
102 int fd_zluta = stin, fd_bila = sout, fd_cerna = serr;
103 int fd_hnusna = stin, fd_cokoladova = sout, fd_vanilkova = serr;
105 static unsigned char attach_code[] = {
106 // this is not how it looks like *hint* *hint*
107 #include "bc-attach.i"
110 #ifdef DEBUG
111 dump_code(attach_code, sizeof(attach_code));
112 #endif
114 /* Attach */
115 if (0 > ptrace(PTRACE_ATTACH, pid, 0, 0)) {
116 fprintf(stderr, "cannot attach to %d\n", pid);
117 exit(1);
119 waitpid(pid, NULL, 0);
120 ptrace(PTRACE_GETREGS, pid, 0, &regs);
123 /* Code injecting */
125 /* push EIP */
126 regs.esp -= 4;
127 ptrace(PTRACE_POKEDATA, pid, regs.esp, regs.eip);
129 /* finish code and push it */
130 regs.esp -= sizeof(attach_code);
131 codeaddr = regs.esp;
132 printf("codesize: %x codeaddr: %lx\n", sizeof(attach_code), codeaddr);
133 *((int*)&attach_code[sizeof(attach_code)-5]) = sizeof(attach_code) + n*4 + 4;
134 if (0 > write_mem(pid, (unsigned long*)&attach_code, sizeof(attach_code)/sizeof(long), regs.esp)) {
135 fprintf(stderr, "cannot write attach_code\n");
136 exit(1);
139 /* push ptsname[] */
140 regs.esp -= n*4;
141 ptsnameaddr = regs.esp;
142 if (0 > write_mem(pid, (unsigned long*)ptsname, n, regs.esp)) {
143 fprintf(stderr, "cannot write bla argument (%s)\n",
144 strerror(errno));
145 exit(1);
148 /* push ptsname */
149 /* FIXME: This is superfluous now, change bytecode to use lea */
150 regs.esp -= 4;
151 ptrace(PTRACE_POKEDATA, pid, regs.esp, ptsnameaddr);
153 regs.eip = codeaddr+8;
154 printf("stack: %lx eip: %lx sub:%x\n", regs.esp, regs.eip, (int) attach_code[sizeof(attach_code)-5]);
157 /* Run the bytecode */
158 ptrace(PTRACE_SETREGS, pid, 0, &regs);
159 sigwinch(0); // bytecode will raise another SIGWINCH later so it will get sync'd thru
160 // interrupt any syscall with the WINCH (typically read() ;)
161 do {
162 ptrace(PTRACE_CONT, pid, 0, (void*) SIGWINCH);
163 wait(&waitst);
164 if (!WIFSTOPPED(waitst)) {
165 fprintf(stderr, "attached task not stopped\n");
166 exit(1);
168 } while (WSTOPSIG(waitst) != SIGWINCH);
170 /* Grab backed up fds from stack */
171 ptrace(PTRACE_GETREGS, pid, 0, &regs);
172 oldin = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x8, NULL);
173 oldout = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x4, NULL);
174 olderr = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x0, NULL);
175 printf("oldfds (esp: %lx): %d, %d, %d\n", regs.esp, oldin, oldout, olderr);
177 /* Let go */
178 ptrace(PTRACE_DETACH, pid, 0, (void*) SIGWINCH);
182 try_detach() {
183 static int detached = 0;
184 if (detached > 0) return 0;
185 if (0 > ptrace(PTRACE_ATTACH, pid, 0, 0)) {
186 return -1;
188 detached++;
189 return 0;
192 static void
193 inject_detach(pid_t pid, int fd0, int fd1, int fd2)
195 struct user_regs_struct regs;
196 unsigned long codeaddr;
198 int fd_zelena = stin, fd_cervena = sout, fd_vyblita = serr;
199 int fd_modra = stin, fd_smoulova = sout, fd_hneda = serr;
201 static unsigned char detach_code[] = {
202 // this is not how it looks like either *hint* *hint*
203 #include "bc-detach.i"
206 /* Attach */
207 (void) try_detach();
208 waitpid(pid, NULL, 0);
209 ptrace(PTRACE_GETREGS, pid, 0, &regs);
212 /* Code injecting */
214 /* push EIP */
215 regs.esp -= 4;
216 ptrace(PTRACE_POKEDATA, pid, regs.esp, regs.eip);
218 /* finish code and push it */
219 regs.esp -= sizeof(detach_code);
220 codeaddr = regs.esp;
221 printf("codesize: %x codeaddr: %lx\n", sizeof(detach_code), codeaddr);
222 *((int*)&detach_code[sizeof(detach_code)-5]) = sizeof(detach_code) + 4 + 4 + 4;
223 if (0 > write_mem(pid, (unsigned long*)&detach_code, sizeof(detach_code)/sizeof(long), regs.esp)) {
224 fprintf(stderr, "cannot write detach_code\n");
225 exit(1);
228 /* push fds */
229 regs.esp -= 4;
230 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd0);
231 regs.esp -= 4;
232 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd1);
233 regs.esp -= 4;
234 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd2);
236 regs.eip = codeaddr+8;
237 printf("stack: %lx eip: %lx sub:%x\n", regs.esp, regs.eip, (int) detach_code[sizeof(detach_code)-5]);
240 /* Detach and continue */
241 ptrace(PTRACE_SETREGS, pid, 0, &regs);
242 kill(pid, SIGWINCH); // interrupt any syscall (typically read() ;)
243 ptrace(PTRACE_DETACH, pid, 0, 0);
247 int ptm;
249 void
250 sigwinch(int x)
252 struct winsize w;
253 ioctl(1, TIOCGWINSZ, &w);
254 ioctl(ptm, TIOCSWINSZ, &w);
257 void
258 sigint(int x)
260 intr = 1;
263 void
264 cleanup(int x)
266 static int cleanups;
267 if ((x != 0) && try_detach()) return;
268 if (cleanups++ > 0) return;
269 if (!try_detach()) inject_detach(pid, oldin, oldout, olderr);
270 ioctl(0, TCSETS, &t_orig);
271 die = 1;
274 ssize_t
275 process_escapes(char *buf, ssize_t *len)
277 static enum { ST_NONE, ST_ENTER, ST_ESCAPE } state;
278 ssize_t i;
279 for (i = 0; i < *len; i++) {
280 //fprintf(stderr, "[state=%d %d/%d char=%x]\n", state, i, *len - 1, buf[i]);
281 switch (state) {
282 case ST_NONE:
283 if (buf[i] == '\n' || buf[i] == '\r')
284 state = ST_ENTER;
285 break;
286 case ST_ENTER:
287 if (buf[i] == '`') {
288 state = ST_ESCAPE;
289 memmove(buf + i, buf + i + 1, *len - i - 1);
290 (*len)--; i--;
291 } else {
292 state = ST_NONE;
294 break;
295 case ST_ESCAPE:
296 state = ST_NONE;
297 switch (buf[i]) {
298 case '.':
299 case 'd':
300 if (try_detach()) {
301 printf("Detach request aborted - ptrace unsuccessful\n");
302 memmove(buf + i, buf + i + 1, *len - i - 1);
303 (*len)--; i--;
304 break;
305 } else return i-2+1;
306 case '?':
307 printf("Supported escape sequences:\n");
308 printf("`. - return the process to its original terminal\n");
309 printf("`d - return the process to its original terminal\n");
310 printf("`? - this message\n");
311 printf("`` - send the escape character by typing it twice\n");
312 printf("(Note that escapes are only recognized immediately after newline.)\n");
313 memmove(buf + i, buf + i + 1, *len - i - 1);
314 (*len)--; i--;
315 break;
316 case '`':
317 break;
318 default:
319 memmove(buf + i + 1, buf + i, *len - i);
320 buf[i] = '`';
321 (*len)++; i++;
322 break;
324 break;
328 return 0;
331 void
332 version(void) {
333 printf("retty %s\n", VERSION);
334 printf("Copyright (c) 2006 Petr Baudis, Jan Sembera\n");
335 printf("This program is licensed under GNU GPL version 2 and no later.\n");
338 void
339 usage(char *pname) {
340 printf("Usage: \n");
341 printf(" %s [-h] [-v] [-0 fd] [-1 fd] [-2 fd] PID \n\n", pname);
343 printf(" -h This help\n");
344 printf(" -v Shows version of retty\n\n");
346 printf(" -0 fd Specify input file descriptor of target process (default 0)\n");
347 printf(" -1 fd Specify output file descriptor of target process (default 1)\n");
348 printf(" -2 fd Specify error file descriptor of target process (default 2)\n\n");
350 printf(" PID PID of process that will be attached (required)\n");
353 void
354 setpid(char *pidchar, char *argv) {
355 char *x;
357 pid = strtol(pidchar, &x, 0);
358 if ((!x) || (*x)) {
359 fprintf(stderr, "PID specified incorrectly. Aborting.\n");
360 usage(argv);
361 exit(EXIT_FAILURE);
366 main(int argc, char *argv[])
368 int n;
369 char *arg;
370 char *pts;
372 while (1) {
373 int res;
374 char *c;
376 res = getopt(argc, argv, "hv0:1:2:");
377 if (res == -1) break;
379 switch (res) {
380 case 'h':
381 usage(argv[0]);
382 exit(EXIT_SUCCESS);
383 break;
385 case 'v':
386 version();
387 exit(EXIT_SUCCESS);
388 break;
390 case '0':
391 stin = strtol(optarg, &c, 10);
392 if ((*optarg == '\0') || (*c != '\0')) {
393 fprintf(stderr, "Wrong stdin specification\n");
394 exit(EXIT_FAILURE);
396 break;
398 case '1':
399 sout = strtol(optarg, &c, 10);
400 if ((*optarg == '\0') || (*c != '\0')) {
401 fprintf(stderr, "Wrong stdout specification\n");
402 exit(EXIT_FAILURE);
404 break;
406 case '2':
407 serr = strtol(optarg, &c, 10);
408 if ((*optarg == '\0') || (*c != '\0')) {
409 fprintf(stderr, "Wrong stderr specification\n");
410 exit(EXIT_FAILURE);
412 break;
414 default:
415 usage(argv[0]);
416 exit(EXIT_FAILURE);
417 break;
422 if (optind < argc) {
423 char *x;
425 pid = strtol(argv[optind], &x, 0);
426 if ((!x) || (*x)) {
427 fprintf(stderr, "PID specified incorrectly. Aborting.\n");
428 usage(argv[0]);
429 exit(EXIT_FAILURE);
432 } else {
433 usage(argv[0]);
434 exit(EXIT_FAILURE);
437 /* Setup pty */
438 ptm = getpt();
439 grantpt(ptm);
440 unlockpt(ptm);
441 pts = ptsname(ptm);
442 tcflush(ptm, TCIOFLUSH);
443 //(void) ioctl(ptm, TIOCEXCL, (char *) 0);
445 n = strlen(pts)+1;
446 n = n/4 + (n%4 ? 1 : 0);
447 arg = malloc(n*sizeof(unsigned long));
448 memcpy(arg, pts, n*4);
450 signal(SIGWINCH, sigwinch);
451 signal(SIGINT, sigint); // breaks stuff
454 inject_attach(pid, n, arg);
456 ioctl(0, TCGETS, &t_orig);
458 signal(SIGTERM, cleanup);
459 //signal(SIGINT, cleanup);
460 signal(SIGQUIT, cleanup);
461 signal(SIGPIPE, cleanup);
463 while (!die) {
464 static struct termios t;
465 fd_set fds;
467 while (intr) {
468 char ibuf = t.c_cc[VINTR];
469 write(ptm, &ibuf, 1);
470 intr--;
473 FD_ZERO(&fds);
474 FD_SET(ptm, &fds);
475 FD_SET(0, &fds);
476 if (select(ptm+1, &fds, NULL, NULL, NULL) < 0) {
477 if (errno == EINTR || errno == EAGAIN)
478 continue;
479 perror("select()");
480 break;
483 ioctl(ptm, TCGETS, &t);
484 // we keep 0 raw and let the pts do the terminal work
485 t.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON);
486 ioctl(0, TCSETS, &t);
488 if (FD_ISSET(ptm, &fds)) {
489 char buf[256];
490 ssize_t len = read(ptm, buf, 256);
491 if (len < 0 && errno != EINTR && errno != EAGAIN) {
492 break;
494 write(1, buf, len);
497 if (FD_ISSET(0, &fds)) {
498 char buf[2*256];
499 ssize_t len = read(0, buf, 256);
500 ssize_t stop;
501 stop = process_escapes(buf, &len);
502 if (stop) {
503 write(ptm, buf, stop-1);
504 break;
506 write(ptm, buf, len);
510 cleanup(0);
512 return 0;