pax_global_header00006660000000000000000000000064121537313440014515gustar00rootroot0000000000000052 comment=493ca94779f98c057fe259b3b24b54610fc7bec5 nelhage-reptyr-2f0b1e1/000077500000000000000000000000001215373134400150455ustar00rootroot00000000000000nelhage-reptyr-2f0b1e1/.gitignore000066400000000000000000000000221215373134400170270ustar00rootroot00000000000000reptyr ptrace *.o nelhage-reptyr-2f0b1e1/COPYING000066400000000000000000000020441215373134400161000ustar00rootroot00000000000000Copyright (C) 2011 by Nelson Elhage Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nelhage-reptyr-2f0b1e1/ChangeLog000066400000000000000000000010341215373134400166150ustar00rootroot00000000000000* 0.5 (Jun 5, 2013) - Add an early check and error if attaching a process with children. * 0.4 (Aug 16, 2012) - Add support for scripting with the -l flag. - Add a French translation of the man page - Add a -V flag * 0.3 (May 27, 2011) - Add support for attaching 32-bit programs on x86_64. - Fix a bug on ARM. * 0.2 (Mar 1, 2011) - Add a man page - Add a 'make install' target - Improve detection of which fd's to attach, - Add a '-s' option to attach programs not currently on a tty. * 0.1 (Jan 27, 2011) - Initial release nelhage-reptyr-2f0b1e1/Makefile000066400000000000000000000011511215373134400165030ustar00rootroot00000000000000override CFLAGS+=-Wall -Werror -D_GNU_SOURCE -g OBJS=reptyr.o ptrace.o attach.o PREFIX=/usr/local all: reptyr reptyr: $(OBJS) attach.o: reptyr.h ptrace.h reptyr.o: reptyr.h ptrace.o: ptrace.h $(wildcard arch/*.h) clean: rm -f reptyr $(OBJS) install: reptyr install -d -m 755 $(DESTDIR)$(PREFIX)/bin/ install -m 755 reptyr $(DESTDIR)$(PREFIX)/bin/reptyr install -d -m 755 $(DESTDIR)$(PREFIX)/share/man/man1 install -m 644 reptyr.1 $(DESTDIR)$(PREFIX)/share/man/man1/reptyr.1 install -d -m 755 $(DESTDIR)$(PREFIX)/share/man/fr/man1 install -m 644 reptyr.fr.1 $(DESTDIR)$(PREFIX)/share/man/fr/man1/reptyr.1 nelhage-reptyr-2f0b1e1/NOTES000066400000000000000000000007101215373134400156560ustar00rootroot00000000000000Attaching: - Find an fd corresponding to the tty in the child - Open the new pty in the child - Copy the termios settings over - dup() it over the old ones - Make the new tty the controlling tty: - Fork a dummy child - Find all processes in the child's process group. - For each one, move them to the dummy child's process group - Make the child setsid() - Set the terminal as the controlling tty - Close the newly allocated tty - Detach nelhage-reptyr-2f0b1e1/README.md000066400000000000000000000067551215373134400163410ustar00rootroot00000000000000reptyr - A tool for "re-ptying" programs. ========================================= reptyr is a utility for taking an existing running program and attaching it to a new terminal. Started a long-running process over ssh, but have to leave and don't want to interrupt it? Just start a screen, use reptyr to grab it, and then kill the ssh session and head on home. USAGE ----- reptyr PID "reptyr PID" will grab the process with id PID and attach it to your current terminal. After attaching, the process will take input from and write output to the new terminal, including ^C and ^Z. (Unfortunately, if you background it, you will still have to run "bg" or "fg" in the old terminal. This is likely impossible to fix in a reasonable way without patching your shell.) "But wait, isn't this just screenify?" -------------------------------------- There's a shell script called "screenify" that's been going around the internet for nigh on 10 years now that uses gdb to (supposedly) accomplish the same thing. The difference is that reptyr works much, much, better. If you attach a "less" using screenify, it will still take input from the old terminal. If you attach an ncurses program using screenify, and resize the window, your program won't notice. If you attach a process with screenify, ^C in the new terminal won't work. reptyr fixes all of these problems, and is the only such tool I know of that does so. See below for some more details on how it accomplishes this. PORTABILITY ----------- reptyr is Linux-only. It uses ptrace to attach to the target and control it at the syscall level, so it is highly dependent on Linux's particular syscall API, syscalls, and terminal ioctl()s. A port to Solaris or BSD may be technically feasible, but would probably require significant re-architecting to abstract out the platform-specific bits. reptyr works on i386, x86_64, and ARM. Ports to other architectures should be straightforward, and should in most cases be as simple as adding an arch/ARCH.h file and adding a clause to the ifdef ladder in ptrace.c. ptrace_scope on Ubuntu Maverick and up -------------------------------------- `reptyr` depends on the `ptrace` system call to attach to the remote program. On Ubuntu Maverick and higher, this ability is disabled by default for security reasons. You can enable it temporarily by doing # echo 0 > /proc/sys/kernel/yama/ptrace_scope as root, or permanently by editing the file /etc/sysctl.d/10-ptrace.conf, which also contains more information about exactly what this setting accomplishes. reptyr -l --------- As a bonus feature, if you run "reptyr -l", reptyr will create a new pseudo-terminal pair with nothing attached to the slave end, and print its name out. If you are debugging a program in gdb, you can pass that name to "set inferior-pty". Because there is no existing program listening to that tty, this will work much better than passing an existing shell's terminal. How does it work? ----------------- The main thing that reptyr does that no one else does is that it actually changes the controlling terminal of the process you are attaching. I plan on writing up more about just how this works soon, but for now, the source is only about 1000 lines if you're curious :) PRONUNCIATION ------------- I pronounce it like "repeater", but since that's easily ambiguous, "re-P-T-Y-er" is also acceptable. CREDITS ------- reptyr was written by Nelson Elhage . Contact him with any questions or bug reports. URL --- [http://github.com/nelhage/reptyr]() nelhage-reptyr-2f0b1e1/arch/000077500000000000000000000000001215373134400157625ustar00rootroot00000000000000nelhage-reptyr-2f0b1e1/arch/amd64.h000066400000000000000000000063421215373134400170530ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "x86_common.h" #define ARCH_HAVE_MULTIPLE_PERSONALITIES static struct ptrace_personality arch_personality[2] = { { offsetof(struct user, regs.rax), offsetof(struct user, regs.rdi), offsetof(struct user, regs.rsi), offsetof(struct user, regs.rdx), offsetof(struct user, regs.r10), offsetof(struct user, regs.r8), offsetof(struct user, regs.r9), offsetof(struct user, regs.rip), }, { offsetof(struct user, regs.rax), offsetof(struct user, regs.rbx), offsetof(struct user, regs.rcx), offsetof(struct user, regs.rdx), offsetof(struct user, regs.rsi), offsetof(struct user, regs.rdi), offsetof(struct user, regs.rbp), offsetof(struct user, regs.rip), }, }; struct x86_personality x86_personality[2] = { { offsetof(struct user, regs.orig_rax), offsetof(struct user, regs.rax), }, { offsetof(struct user, regs.orig_rax), offsetof(struct user, regs.rax), }, }; struct syscall_numbers arch_syscall_numbers[2] = { #include "default-syscalls.h" { /* * These don't seem to be available in any convenient header. We could * include unistd_32.h, but those definitions would conflict with the * standard ones. So, let's just hardcode the values for now. Probably * we should generate this from unistd_32.h during the build process or * soemthing. */ .nr_mmap = 90, .nr_mmap2 = 192, .nr_munmap = 91, .nr_getsid = 147, .nr_setsid = 66, .nr_setpgid = 57, .nr_fork = 2, .nr_wait4 = 114, .nr_signal = 48, .nr_rt_sigaction = 173, .nr_open = 5, .nr_close = 6, .nr_ioctl = 54, .nr_dup2 = 63 } }; int arch_get_personality(struct ptrace_child *child) { unsigned long cs; cs = ptrace_command(child, PTRACE_PEEKUSER, offsetof(struct user, regs.cs)); if (child->error) return -1; if (cs == 0x23) child->personality = 1; return 0; } nelhage-reptyr-2f0b1e1/arch/arm.h000066400000000000000000000044521215373134400167170ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ static struct ptrace_personality arch_personality[1] = { { offsetof(struct user, regs.uregs[0]), offsetof(struct user, regs.uregs[0]), offsetof(struct user, regs.uregs[1]), offsetof(struct user, regs.uregs[2]), offsetof(struct user, regs.uregs[3]), offsetof(struct user, regs.uregs[4]), offsetof(struct user, regs.uregs[5]), offsetof(struct user, regs.ARM_pc), } }; static inline void arch_fixup_regs(struct ptrace_child *child) { child->user.regs.ARM_pc -= 4; } static inline int arch_set_syscall(struct ptrace_child *child, unsigned long sysno) { return ptrace_command(child, PTRACE_SET_SYSCALL, 0, sysno); } static inline int arch_save_syscall(struct ptrace_child *child) { unsigned long swi; swi = ptrace_command(child, PTRACE_PEEKTEXT, child->user.regs.ARM_pc); if (child->error) return -1; if (swi == 0xef000000) child->saved_syscall = child->user.regs.uregs[7]; else child->saved_syscall = (swi & 0x000fffff); return 0; } static inline int arch_restore_syscall(struct ptrace_child *child) { return arch_set_syscall(child, child->saved_syscall); } nelhage-reptyr-2f0b1e1/arch/default-syscalls.h000066400000000000000000000006601215373134400214140ustar00rootroot00000000000000#define SC(name) .nr_##name = __NR_##name { #ifdef __NR_mmap SC(mmap), #else .nr_mmap = -1, #endif #ifdef __NR_mmap2 SC(mmap2), #else .nr_mmap2 = -1, #endif SC(munmap), SC(getsid), SC(setsid), SC(setpgid), SC(fork), SC(wait4), #ifdef __NR_signal SC(signal), #else .nr_signal = -1, #endif SC(rt_sigaction), SC(open), SC(close), SC(ioctl), SC(dup2), }, #undef SC nelhage-reptyr-2f0b1e1/arch/i386.h000066400000000000000000000032371215373134400166310ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "x86_common.h" static struct ptrace_personality arch_personality[1] = { { offsetof(struct user, regs.eax), offsetof(struct user, regs.ebx), offsetof(struct user, regs.ecx), offsetof(struct user, regs.edx), offsetof(struct user, regs.esi), offsetof(struct user, regs.edi), offsetof(struct user, regs.ebp), offsetof(struct user, regs.eip), } }; struct x86_personality x86_personality[1] = { { offsetof(struct user, regs.orig_eax), offsetof(struct user, regs.eax), } }; nelhage-reptyr-2f0b1e1/arch/x86_common.h000066400000000000000000000043101215373134400201260ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ struct x86_personality { size_t orig_ax; size_t ax; }; struct x86_personality x86_personality[]; static inline struct x86_personality *x86_pers(struct ptrace_child *child) { return &x86_personality[child->personality]; } static inline void arch_fixup_regs(struct ptrace_child *child) { struct x86_personality *x86pers = x86_pers(child); struct ptrace_personality *pers = personality(child); struct user *user = &child->user; #define ptr(user, off) ((unsigned long*)((void*)(user)+(off))) *ptr(user, pers->reg_ip) -= 2; *ptr(user, x86pers->ax) = *ptr(user, x86pers->orig_ax); } static inline int arch_set_syscall(struct ptrace_child *child, unsigned long sysno) { return ptrace_command(child, PTRACE_POKEUSER, x86_pers(child)->orig_ax, sysno); } static inline int arch_save_syscall(struct ptrace_child *child) { child->saved_syscall = *ptr(&child->user, x86_pers(child)->orig_ax); return 0; } static inline int arch_restore_syscall(struct ptrace_child *child) { return 0; } #undef ptr nelhage-reptyr-2f0b1e1/attach.c000066400000000000000000000342661215373134400164700ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ptrace.h" #include "reptyr.h" #define TASK_COMM_LENGTH 16 struct proc_stat { pid_t pid; char comm[TASK_COMM_LENGTH+1]; char state; pid_t ppid, sid, pgid; dev_t ctty; }; #define do_syscall(child, name, a0, a1, a2, a3, a4, a5) \ ptrace_remote_syscall((child), ptrace_syscall_numbers((child))->nr_##name, \ a0, a1, a2, a3, a4, a5) int parse_proc_stat(int statfd, struct proc_stat *out) { char buf[1024]; int n; unsigned dev; lseek(statfd, 0, SEEK_SET); if (read(statfd, buf, sizeof buf) < 0) return errno; n = sscanf(buf, "%d (%16[^)]) %c %d %d %d %u", &out->pid, out->comm, &out->state, &out->ppid, &out->sid, &out->pgid, &dev); if (n == EOF) return errno; if (n != 7) { return EINVAL; } out->ctty = dev; return 0; } int read_proc_stat(pid_t pid, struct proc_stat *out) { char stat_path[PATH_MAX]; int statfd; int err; snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); statfd = open(stat_path, O_RDONLY); if (statfd < 0) { error("Unable to open %s: %s", stat_path, strerror(errno)); return -statfd; } err = parse_proc_stat(statfd, out); close(statfd); return err; } static void do_unmap(struct ptrace_child *child, child_addr_t addr, unsigned long len) { if (addr == (unsigned long)-1) return; do_syscall(child, munmap, addr, len, 0, 0, 0, 0); } int *get_child_tty_fds(struct ptrace_child *child, int statfd, int *count) { struct proc_stat child_status; struct stat tty_st, st; char buf[PATH_MAX]; int n = 0, allocated = 0; int *fds = NULL; DIR *dir; struct dirent *d; int *tmp = NULL; debug("Looking up fds for tty in child."); if ((child->error = parse_proc_stat(statfd, &child_status))) return NULL; debug("Resolved child tty: %x", (unsigned)child_status.ctty); if (stat("/dev/tty", &tty_st) < 0) { child->error = errno; error("Unable to stat /dev/tty"); return NULL; } snprintf(buf, sizeof buf, "/proc/%d/fd/", child->pid); if ((dir = opendir(buf)) == NULL) return NULL; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; snprintf(buf, sizeof buf, "/proc/%d/fd/%s", child->pid, d->d_name); if (stat(buf, &st) < 0) continue; if (st.st_rdev == child_status.ctty || st.st_rdev == tty_st.st_rdev) { if (n == allocated) { allocated = allocated ? 2 * allocated : 2; tmp = realloc(fds, allocated * sizeof *tmp); if (tmp == NULL) { child->error = errno; error("Unable to allocate memory for fd array."); free(fds); fds = NULL; goto out; } fds = tmp; } debug("Found an alias for the tty: %s", d->d_name); fds[n++] = atoi(d->d_name); } } out: *count = n; closedir(dir); return fds; } void move_process_group(struct ptrace_child *child, pid_t from, pid_t to) { DIR *dir; struct dirent *d; pid_t pid; char *p; int err; if ((dir = opendir("/proc/")) == NULL) return; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; pid = strtol(d->d_name, &p, 10); if (*p) continue; if (getpgid(pid) == from) { debug("Change pgid for pid %d", pid); err = do_syscall(child, setpgid, pid, to, 0, 0, 0, 0); if (err < 0) error(" failed: %s", strerror(-err)); } } closedir(dir); } int do_setsid(struct ptrace_child *child) { int err = 0; struct ptrace_child dummy; err = do_syscall(child, fork, 0, 0, 0, 0, 0, 0); if (err < 0) return err; debug("Forked a child: %ld", child->forked_pid); err = ptrace_finish_attach(&dummy, child->forked_pid); if (err < 0) goto out_kill; dummy.state = ptrace_after_syscall; memcpy(&dummy.user, &child->user, sizeof child->user); if (ptrace_restore_regs(&dummy)) { err = dummy.error; goto out_kill; } err = do_syscall(&dummy, setpgid, 0, 0, 0, 0, 0, 0); if (err < 0) { error("Failed to setpgid: %s", strerror(-err)); goto out_kill; } move_process_group(child, child->pid, dummy.pid); err = do_syscall(child, setsid, 0, 0, 0, 0, 0, 0); if (err < 0) { error("Failed to setsid: %s", strerror(-err)); move_process_group(child, dummy.pid, child->pid); goto out_kill; } debug("Did setsid()"); out_kill: kill(dummy.pid, SIGKILL); ptrace_detach_child(&dummy); ptrace_wait(&dummy); do_syscall(child, wait4, dummy.pid, 0, WNOHANG, 0, 0, 0); return err; } int ignore_hup(struct ptrace_child *child, unsigned long scratch_page) { int err; if (ptrace_syscall_numbers(child)->nr_signal != -1) { err = do_syscall(child, signal, SIGHUP, (unsigned long)SIG_IGN, 0, 0, 0, 0); } else { struct sigaction act = { .sa_handler = SIG_IGN, }; err = ptrace_memcpy_to_child(child, scratch_page, &act, sizeof act); if (err < 0) return err; err = do_syscall(child, rt_sigaction, SIGHUP, scratch_page, 0, 8, 0, 0); } return err; } /* * Wait for the specific pid to enter state 'T', or stopped. We have to pull the * /proc file rather than attaching with ptrace() and doing a wait() because * half the point of this exercise is for the process's real parent (the shell) * to see the TSTP. * * In case the process is masking or ignoring SIGTSTP, we time out after a * second and continue with the attach -- it'll still work mostly right, you * just won't get the old shell back. */ void wait_for_stop(pid_t pid, int fd) { struct timeval start, now; struct timespec sleep; struct proc_stat st; gettimeofday(&start, NULL); while (1) { gettimeofday(&now, NULL); if ((now.tv_sec > start.tv_sec && now.tv_usec > start.tv_usec) || (now.tv_sec - start.tv_sec > 1)) { error("Timed out waiting for child stop."); break; } /* * If anything goes wrong reading or parsing the stat node, just give * up. */ if (parse_proc_stat(fd, &st)) break; if (st.state == 'T') break; sleep.tv_sec = 0; sleep.tv_nsec = 10000000; nanosleep(&sleep, NULL); } } int copy_tty_state(pid_t pid, const char *pty) { char buf[PATH_MAX]; int fd, err = EINVAL; struct termios tio; int i; for (i = 0; i < 3 && err; i++) { err = 0; snprintf(buf, sizeof buf, "/proc/%d/fd/%d", pid, i); if ((fd = open(buf, O_RDONLY)) < 0) { err = -fd; continue; } if (!isatty(fd)) { err = ENOTTY; goto retry; } if (tcgetattr(fd, &tio) < 0) { err = -errno; } retry: close(fd); } if (err) return err; if ((fd = open(pty, O_RDONLY)) < 0) return -errno; if (tcsetattr(fd, TCSANOW, &tio) < 0) err = errno; close(fd); return -err; } int check_pgroup(pid_t target) { pid_t pg; DIR *dir; struct dirent *d; pid_t pid; char *p; int err = 0; struct proc_stat pid_stat; debug("Checking for problematic process group members..."); pg = getpgid(target); if (pg < 0) { error("Unable to get pgid (does process %d exist?)", (int)target); return pg; } if ((dir = opendir("/proc/")) == NULL) return errno; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; pid = strtol(d->d_name, &p, 10); if (*p) continue; if (pid == target) continue; if (getpgid(pid) == pg) { /* * We are actually being somewhat overly-conservative here * -- if pid is a child of target, and has not yet called * execve(), reptyr's setpgid() strategy may suffice. That * is a fairly rare case, and annoying to check for, so * for now let's just bail out. */ if ((err = read_proc_stat(pid, &pid_stat))) { memcpy(pid_stat.comm, "???", 4); } error("Process %d (%.*s) shares %d's process group. Unable to attach.\n" "(This most commonly means that %d has a suprocesses).", (int)pid, TASK_COMM_LENGTH, pid_stat.comm, (int)target, (int)target); err = EINVAL; goto out; } } out: closedir(dir); return err; } int attach_child(pid_t pid, const char *pty, int force_stdio) { struct ptrace_child child; unsigned long scratch_page = -1; int *child_tty_fds = NULL, n_fds, child_fd, statfd; int i; int err = 0; long page_size = sysconf(_SC_PAGE_SIZE); char stat_path[PATH_MAX]; long mmap_syscall; if ((err = check_pgroup(pid))) { return err; } if ((err = copy_tty_state(pid, pty))) { if (err == ENOTTY && !force_stdio) { error("Target is not connected to a terminal.\n" " Use -s to force attaching anyways."); return err; } } snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); statfd = open(stat_path, O_RDONLY); if (statfd < 0) { error("Unable to open %s: %s", stat_path, strerror(errno)); return -statfd; } kill(pid, SIGTSTP); wait_for_stop(pid, statfd); if (ptrace_attach_child(&child, pid)) { err = child.error; goto out_cont; } if (ptrace_advance_to_state(&child, ptrace_at_syscall)) { err = child.error; goto out_detach; } if (ptrace_save_regs(&child)) { err = child.error; goto out_detach; } mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap2; if (mmap_syscall == -1) mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap; scratch_page = ptrace_remote_syscall(&child, mmap_syscall, 0, page_size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); if (scratch_page > (unsigned long)-1000) { err = -(signed long)scratch_page; goto out_unmap; } debug("Allocated scratch page: %lx", scratch_page); if (force_stdio) { child_tty_fds = malloc(3 * sizeof(int)); if (!child_tty_fds) { err = ENOMEM; goto out_unmap; } n_fds = 3; child_tty_fds[0] = 0; child_tty_fds[1] = 1; child_tty_fds[2] = 2; } else { child_tty_fds = get_child_tty_fds(&child, statfd, &n_fds); if (!child_tty_fds) { err = child.error; goto out_unmap; } } if (ptrace_memcpy_to_child(&child, scratch_page, pty, strlen(pty)+1)) { err = child.error; error("Unable to memcpy the pty path to child."); goto out_free_fds; } child_fd = do_syscall(&child, open, scratch_page, O_RDWR|O_NOCTTY, 0, 0, 0, 0); if (child_fd < 0) { err = child_fd; error("Unable to open the tty in the child."); goto out_free_fds; } debug("Opened the new tty in the child: %d", child_fd); err = ignore_hup(&child, scratch_page); if (err < 0) goto out_close; err = do_syscall(&child, getsid, 0, 0, 0, 0, 0, 0); if (err != child.pid) { debug("Target is not a session leader, attempting to setsid."); err = do_setsid(&child); } else { do_syscall(&child, ioctl, child_tty_fds[0], TIOCNOTTY, 0, 0, 0, 0); } if (err < 0) goto out_close; err = do_syscall(&child, ioctl, child_fd, TIOCSCTTY, 0, 0, 0, 0); if (err < 0) { error("Unable to set controlling terminal."); goto out_close; } debug("Set the controlling tty"); for (i = 0; i < n_fds; i++) do_syscall(&child, dup2, child_fd, child_tty_fds[i], 0, 0, 0, 0); err = 0; out_close: do_syscall(&child, close, child_fd, 0, 0, 0, 0, 0); out_free_fds: free(child_tty_fds); out_unmap: do_unmap(&child, scratch_page, page_size); ptrace_restore_regs(&child); out_detach: ptrace_detach_child(&child); if (err == 0) { kill(child.pid, SIGSTOP); wait_for_stop(child.pid, statfd); } kill(child.pid, SIGWINCH); out_cont: kill(child.pid, SIGCONT); close(statfd); return err < 0 ? -err : err; } nelhage-reptyr-2f0b1e1/ptrace.c000066400000000000000000000237671215373134400165060ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ptrace.h" /* * RHEL 5's kernel supports these flags, but their libc doesn't ship a ptrace.h * that defines them. Define them here, and if our kernel doesn't support them, * we'll find out when PTRACE_SETOPTIONS fails. */ #ifndef PTRACE_O_TRACESYSGOOD #define PTRACE_O_TRACESYSGOOD 0x00000001 #endif #ifndef PTRACE_O_TRACEFORK #define PTRACE_O_TRACEFORK 0x00000002 #endif #ifndef PTRACE_EVENT_FORK #define PTRACE_EVENT_FORK 1 #endif #define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ _min1 < _min2 ? _min1 : _min2; }) static long __ptrace_command(struct ptrace_child *child, enum __ptrace_request req, void *, void*); #define ptrace_command(cld, req, ...) _ptrace_command(cld, req, ## __VA_ARGS__, NULL, NULL) #define _ptrace_command(cld, req, addr, data, ...) __ptrace_command((cld), (req), (void*)(addr), (void*)(data)) struct ptrace_personality { size_t syscall_rv; size_t syscall_arg0; size_t syscall_arg1; size_t syscall_arg2; size_t syscall_arg3; size_t syscall_arg4; size_t syscall_arg5; size_t reg_ip; }; static struct ptrace_personality *personality(struct ptrace_child *child); #if defined(__amd64__) #include "arch/amd64.h" #elif defined(__i386__) #include "arch/i386.h" #elif defined(__arm__) #include "arch/arm.h" #else #error Unsupported architecture. #endif #ifndef ARCH_HAVE_MULTIPLE_PERSONALITIES int arch_get_personality(struct ptrace_child *child) { return 0; } struct syscall_numbers arch_syscall_numbers[] = { #include "arch/default-syscalls.h" }; #endif static struct ptrace_personality *personality(struct ptrace_child *child) { return &arch_personality[child->personality]; } struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child) { return &arch_syscall_numbers[child->personality]; } int ptrace_attach_child(struct ptrace_child *child, pid_t pid) { memset(child, 0, sizeof *child); child->pid = pid; if (ptrace_command(child, PTRACE_ATTACH) < 0) return -1; return ptrace_finish_attach(child, pid); } int ptrace_finish_attach(struct ptrace_child *child, pid_t pid) { memset(child, 0, sizeof *child); child->pid = pid; kill(pid, SIGCONT); if (ptrace_wait(child) < 0) goto detach; if (arch_get_personality(child)) goto detach; if (ptrace_command(child, PTRACE_SETOPTIONS, 0, PTRACE_O_TRACESYSGOOD|PTRACE_O_TRACEFORK) < 0) goto detach; return 0; detach: /* Don't clobber child->error */ ptrace(PTRACE_DETACH, child->pid, 0, 0); return -1; } int ptrace_detach_child(struct ptrace_child *child) { if (ptrace_command(child, PTRACE_DETACH, 0, 0) < 0) return -1; child->state = ptrace_detached; return 0; } int ptrace_wait(struct ptrace_child *child) { if (waitpid(child->pid, &child->status, 0) < 0) { child->error = errno; return -1; } if (WIFEXITED(child->status) || WIFSIGNALED(child->status)) { child->state = ptrace_exited; } else if (WIFSTOPPED(child->status)) { int sig = WSTOPSIG(child->status); if (sig & 0x80) { child->state = (child->state == ptrace_at_syscall) ? ptrace_after_syscall : ptrace_at_syscall; } else { if (sig == SIGTRAP && (((child->status >> 8) & PTRACE_EVENT_FORK) == PTRACE_EVENT_FORK)) ptrace_command(child, PTRACE_GETEVENTMSG, 0, &child->forked_pid); if (child->state != ptrace_at_syscall) child->state = ptrace_stopped; } } else { child->error = EINVAL; return -1; } return 0; } int ptrace_advance_to_state(struct ptrace_child *child, enum child_state desired) { int err; while (child->state != desired) { switch(desired) { case ptrace_after_syscall: case ptrace_at_syscall: if (WIFSTOPPED(child->status) && WSTOPSIG(child->status) == SIGSEGV) { child->error = EAGAIN; return -1; } err = ptrace_command(child, PTRACE_SYSCALL, 0, 0); break; case ptrace_running: return ptrace_command(child, PTRACE_CONT, 0, 0); case ptrace_stopped: err = kill(child->pid, SIGSTOP); if (err < 0) child->error = errno; break; default: child->error = EINVAL; return -1; } if (err < 0) return err; if (ptrace_wait(child) < 0) return -1; } return 0; } int ptrace_save_regs(struct ptrace_child *child) { if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) return -1; if (ptrace_command(child, PTRACE_GETREGS, 0, &child->user) < 0) return -1; arch_fixup_regs(child); if (arch_save_syscall(child) < 0) return -1; return 0; } int ptrace_restore_regs(struct ptrace_child *child) { int err; err = ptrace_command(child, PTRACE_SETREGS, 0, &child->user); if (err < 0) return err; return arch_restore_syscall(child); } unsigned long ptrace_remote_syscall(struct ptrace_child *child, unsigned long sysno, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4, unsigned long p5) { unsigned long rv; if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) return -1; #define setreg(r, v) do { \ if (ptrace_command(child, PTRACE_POKEUSER, \ personality(child)->r, \ (v)) < 0) \ return -1; \ } while (0) if (arch_set_syscall(child, sysno) < 0) return -1; setreg(syscall_arg0, p0); setreg(syscall_arg1, p1); setreg(syscall_arg2, p2); setreg(syscall_arg3, p3); setreg(syscall_arg4, p4); setreg(syscall_arg5, p5); if (ptrace_advance_to_state(child, ptrace_after_syscall) < 0) return -1; rv = ptrace_command(child, PTRACE_PEEKUSER, personality(child)->syscall_rv); if (child->error) return -1; setreg(reg_ip, *(unsigned long*)((void*)&child->user + personality(child)->reg_ip)); #undef setreg return rv; } int ptrace_memcpy_to_child(struct ptrace_child *child, child_addr_t dst, const void *src, size_t n) { unsigned long scratch; while (n >= sizeof(unsigned long)) { if (ptrace_command(child, PTRACE_POKEDATA, dst, *((unsigned long*)src)) < 0) return -1; dst += sizeof(unsigned long); src += sizeof(unsigned long); n -= sizeof(unsigned long); } if (n) { scratch = ptrace_command(child, PTRACE_PEEKDATA, dst); if (child->error) return -1; memcpy(&scratch, src, n); if (ptrace_command(child, PTRACE_POKEDATA, dst, scratch) < 0) return -1; } return 0; } int ptrace_memcpy_from_child(struct ptrace_child *child, void *dst, child_addr_t src, size_t n) { unsigned long scratch; while (n) { scratch = ptrace_command(child, PTRACE_PEEKDATA, src); if (child->error) return -1; memcpy(dst, &scratch, min(n, sizeof(unsigned long))); dst += sizeof(unsigned long); src += sizeof(unsigned long); if (n >= sizeof(unsigned long)) n -= sizeof(unsigned long); else n = 0; } return 0; } static long __ptrace_command(struct ptrace_child *child, enum __ptrace_request req, void *addr, void *data) { long rv; errno = 0; rv = ptrace(req, child->pid, addr, data); child->error = errno; return rv; } #ifdef BUILD_PTRACE_MAIN int main(int argc, char **argv) { struct ptrace_child child; pid_t pid; if (argc < 2) { printf("Usage: %s pid\n", argv[0]); return 1; } pid = atoi(argv[1]); assert(!ptrace_attach_child(&child, pid)); assert(!ptrace_save_regs(&child)); printf("mmap = %lx\n", ptrace_remote_syscall(&child, mmap_syscall, 0, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0)); reset_user_struct(&child.user); assert(!ptrace_restore_regs(&child)); assert(!ptrace_detach_child(&child)); return 0; } #endif nelhage-reptyr-2f0b1e1/ptrace.h000066400000000000000000000056171215373134400165050ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include enum child_state { ptrace_detached = 0, ptrace_at_syscall, ptrace_after_syscall, ptrace_running, ptrace_stopped, ptrace_exited }; struct ptrace_child { pid_t pid; enum child_state state; int personality; int status; int error; unsigned long forked_pid; struct user user; unsigned long saved_syscall; }; struct syscall_numbers { long nr_mmap; long nr_mmap2; long nr_munmap; long nr_getsid; long nr_setsid; long nr_setpgid; long nr_fork; long nr_wait4; long nr_signal; long nr_rt_sigaction; long nr_open; long nr_close; long nr_ioctl; long nr_dup2; }; typedef unsigned long child_addr_t; int ptrace_wait(struct ptrace_child *child); int ptrace_attach_child(struct ptrace_child *child, pid_t pid); int ptrace_finish_attach(struct ptrace_child *child, pid_t pid); int ptrace_detach_child(struct ptrace_child *child); int ptrace_wait(struct ptrace_child *child); int ptrace_advance_to_state(struct ptrace_child *child, enum child_state desired); int ptrace_save_regs(struct ptrace_child *child); int ptrace_restore_regs(struct ptrace_child *child); unsigned long ptrace_remote_syscall(struct ptrace_child *child, unsigned long sysno, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4, unsigned long p5); int ptrace_memcpy_to_child(struct ptrace_child *, child_addr_t, const void*, size_t); int ptrace_memcpy_from_child(struct ptrace_child *, void*, child_addr_t, size_t); struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child); nelhage-reptyr-2f0b1e1/reptyr.1000066400000000000000000000075411215373134400164630ustar00rootroot00000000000000.mso www.tmac .TH reptyr 1 "03 Feb 2011" .SH NAME reptyr \- Reparent a running program to a new terminal .SH SYNOPSIS .B reptyr .I PID .B reptyr \-l|\-L [COMMAND [ARGS]] .SH DESCRIPTION .B reptyr is a utility for taking an existing running program and attaching it to a new terminal. Started a long-running process over ssh, but have to leave and don't want to interrupt it? Just start a screen, use .B reptyr to grab it, and then kill the ssh session and head on home. .LP .B reptyr works by attaching to the target program using .BR ptrace (2), redirecting relevant file descriptors, and changing the program's controlling terminal (See .BR tty (4)) It is this last detail that makes .B reptyr work much better than alternatives such as .BR retty (1). .LP After attaching a program, the program will appear to be either backgrounded or suspended to the shell it was launched from (depending on the shell). For maximal safety you can run .IP bg; disown .LP in the old shell to remove the association with the program, but .B reptyr will attempt to ensure that the target program remains running even if you close the shell without doing so. .SH OPTIONS .B \-l, \-L [COMMAND [ARGS]] .IP Instead of attaching to a new process, create a new pty pair, proxy the master end to the current terminal, and then print the name of the slave pty. This can be passed to e.g. .B gdb\'s .I set inferior-tty option. If an optional .B COMMAND and .B ARGS are passed in conjunction with .B -l, that command will be executed as a child of .B reptyr with the .B REPTYR_PTY environment variable set to the name of the slave pty. If .B -L is used instead of .B -l, then fds 0-2 of the child will also be redirected to point to the slave, and the child will be run in a fresh session with the slave as its controlling terminal. .LP .B \-s .IP By default, reptyr will move any file descriptors in the target that were connected to the target's controlling terminal to point to the new terminal. The .B -s option will cause reptyr to unconditionally attach file descriptors 0, 1, and 2 in the target, even if the target has no controlling terminal or they are not connected to a terminal. .LP .B \-v .IP Print the version of .B reptyr and exit. .LP .B \-h .IP Print a usage message and exit. .LP .B \-V .IP Print verbose debug output while running. .LP .SH NOTES .B reptyr depends on the .BR ptrace (2) system call to attach to the remote program. On Ubuntu Maverick and higher, this ability is disabled by default for security reasons. You can enable it temporarily by doing .IP # echo 0 > /proc/sys/kernel/yama/ptrace_scope .LP as root, or permanently by editing the file .IR /etc/sysctl.d/10-ptrace.conf , which also contains more information about this setting. .SH BUGS When attaching to some curses programs, they will not redraw the screen right away, and a .B ^L or similar will be needed to force a redraw. Similarly, after attaching to certain programs, the old terminal will be left in an odd state, and a .B clear or even .B reset may be required before the old terminal is usable again. Attaching to rtorrent (and probably some other apps) doesn't work right (rtorrent stops accepting input) (The problem is that rtorrent is using epoll to poll stdin, and we don't update the internal reference that the epoll fd has to the old tty). Attaching to a process with children doesn't work right. This should be possible to fix -- I just need to ptrace each child individually and do the same games to it. Attaching a .BR less (1) process doesn't work if you have a .I .lessfilter file, as .BR less leaves around a zombie child in this case. This could be worked around. Bugs should be reported to the author (see below) or via the issue tracker on GitHub. .SH AUTHORS reptyr was written by Nelson Elhage . .SH HOMEPAGE .URL https://github.com/nelhage/reptyr .SH SEE ALSO .BR neercs (1), .BR screen (1) nelhage-reptyr-2f0b1e1/reptyr.c000066400000000000000000000177171215373134400165530ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "reptyr.h" #ifndef __linux__ #error reptyr is currently Linux-only. #endif static int verbose = 0; void _debug(const char *pfx, const char *msg, va_list ap) { if (pfx) fprintf(stderr, "%s", pfx); vfprintf(stderr, msg, ap); fprintf(stderr, "\n"); } void die(const char *msg, ...) { va_list ap; va_start(ap, msg); _debug("[!] ", msg, ap); va_end(ap); exit(1); } void debug(const char *msg, ...) { va_list ap; if (!verbose) return; va_start(ap, msg); _debug("[+] ", msg, ap); va_end(ap); } void error(const char *msg, ...) { va_list ap; va_start(ap, msg); _debug("[-] ", msg, ap); va_end(ap); } void setup_raw(struct termios *save) { struct termios set; if (tcgetattr(0, save) < 0) die("Unable to read terminal attributes: %m"); set = *save; cfmakeraw(&set); if (tcsetattr(0, TCSANOW, &set) < 0) die("Unable to set terminal attributes: %m"); } void resize_pty(int pty) { struct winsize sz; if (ioctl(0, TIOCGWINSZ, &sz) < 0) return; ioctl(pty, TIOCSWINSZ, &sz); } int writeall(int fd, const void *buf, ssize_t count) { ssize_t rv; while (count > 0) { rv = write(fd, buf, count); if (rv < 0) { if (errno == EINTR) continue; return rv; } count -= rv; buf += rv; } return 0; } volatile sig_atomic_t winch_happened = 0; void do_winch(int signal) { winch_happened = 1; } void do_proxy(int pty) { char buf[4096]; ssize_t count; fd_set set; while (1) { if (winch_happened) { winch_happened = 0; /* * FIXME: If a signal comes in after this point but before * select(), the resize will be delayed until we get more * input. signalfd() is probably the cleanest solution. */ resize_pty(pty); } FD_ZERO(&set); FD_SET(0, &set); FD_SET(pty, &set); if (select(pty+1, &set, NULL, NULL, NULL) < 0) { if (errno == EINTR) continue; fprintf(stderr, "select: %m"); return; } if (FD_ISSET(0, &set)) { count = read(0, buf, sizeof buf); if (count < 0) return; writeall(pty, buf, count); } if (FD_ISSET(pty, &set)) { count = read(pty, buf, sizeof buf); if (count < 0) return; writeall(1, buf, count); } } } void usage(char *me) { fprintf(stderr, "Usage: %s [-s] PID\n", me); fprintf(stderr, " %s -l|-L [COMMAND [ARGS]]\n", me); fprintf(stderr, " -l Create a new pty pair and print the name of the slave.\n"); fprintf(stderr, " if there are command-line arguments after -l\n"); fprintf(stderr, " they are executed with REPTYR_PTY set to path of pty.\n"); fprintf(stderr, " -L Like '-l', but also redirect the child's stdio to the slave.\n"); fprintf(stderr, " -s Attach fds 0-2 on the target, even if it is not attached to a tty.\n"); fprintf(stderr, " -h Print this help message and exit.\n"); fprintf(stderr, " -v Print the version number and exit.\n"); fprintf(stderr, " -V Print verbose debug output.\n"); } void check_yama_ptrace_scope(void) { int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY); if (fd >= 0) { char buf[256]; int n; n = read(fd, buf, sizeof buf); close(fd); if (n > 0) { if (!atoi(buf)) { return; } } } else if (errno == ENOENT) return; fprintf(stderr, "The kernel denied permission while attaching. If your uid matches\n"); fprintf(stderr, "the target's, check the value of /proc/sys/kernel/yama/ptrace_scope.\n"); fprintf(stderr, "For more information, see /etc/sysctl.d/10-ptrace.conf\n"); } int main(int argc, char **argv) { struct termios saved_termios; struct sigaction act; int pty; int arg = 1; int do_attach = 1; int force_stdio = 0; int unattached_script_redirection = 0; if (argc < 2) { usage(argv[0]); return 2; } if (argv[arg][0] == '-') { switch(argv[arg][1]) { case 'h': usage(argv[0]); return 0; case 'l': do_attach = 0; break; case 'L': do_attach = 0; unattached_script_redirection = 1; break; case 's': arg++; force_stdio = 1; break; case 'v': printf("This is reptyr version %s.\n", REPTYR_VERSION); printf(" by Nelson Elhage \n"); printf("http://github.com/nelhage/reptyr/\n"); return 0; case 'V': arg++; verbose = 1; break; default: usage(argv[0]); return 1; } } if (do_attach && arg >= argc) { fprintf(stderr, "%s: No pid specified to attach\n", argv[0]); usage(argv[0]); return 1; } if ((pty = open("/dev/ptmx", O_RDWR|O_NOCTTY)) < 0) die("Unable to open /dev/ptmx: %m"); if (unlockpt(pty) < 0) die("Unable to unlockpt: %m"); if (grantpt(pty) < 0) die("Unable to grantpt: %m"); if (do_attach) { pid_t child = atoi(argv[arg]); int err; if ((err = attach_child(child, ptsname(pty), force_stdio))) { fprintf(stderr, "Unable to attach to pid %d: %s\n", child, strerror(err)); if (err == EPERM) { check_yama_ptrace_scope(); } return 1; } } else { printf("Opened a new pty: %s\n", ptsname(pty)); fflush(stdout); if (argc > 2) { if(!fork()) { setenv("REPTYR_PTY", ptsname(pty), 1); if (unattached_script_redirection) { int f; setpgid(0, getppid()); setsid(); f = open(ptsname(pty), O_RDONLY, 0); dup2(f, 0); close(f); f = open(ptsname(pty), O_WRONLY, 0); dup2(f, 1); dup2(f,2); close(f); } close(pty); execvp(argv[2], argv+2); exit(1); } } } setup_raw(&saved_termios); memset(&act, 0, sizeof act); act.sa_handler = do_winch; act.sa_flags = 0; sigaction(SIGWINCH, &act, NULL); resize_pty(pty); do_proxy(pty); tcsetattr(0, TCSANOW, &saved_termios); return 0; } nelhage-reptyr-2f0b1e1/reptyr.fr.1000066400000000000000000000076721215373134400170760ustar00rootroot00000000000000.\" Traduction Laurent GAUTROT - 2011-08-06 .mso www.tmac .TH reptyr 1 "03 Feb 2011" .SH NOM reptyr \- Reassoccie un programme en cours d'exécution à un nouveau terminal .SH SYNOPSIS .B reptyr .I PID .B reptyr \-l .SH DESCRIPTION .B reptyr est un utilitaire qui prend un programme en cours d'exécution et l'attache à un nouveau terminal. Vous avez démarré un programme long à travers ssh, mais vous devez partir et vous ne voulez pas l'interrompre\ ? Démarrez simplement un screen, utilisez .B reptyr pour l'attraper, puis tuez la session ssh et vous pouvez rentrer à la maison. .LP .B reptyr fonctionne en s'attachant au programme visé à l'aide de .BR ptrace (2), en redirigeant les descripteurs de fichiers appropriés et en modifiant le terminal de contrôle du programme (Voir .BR tty (4)) C'est le détail qui fait que .B reptyr focntionne bien mieux que les autres programmes du même type, comme .BR retty (1). .LP Après avoir attaché un programme, il apparaît soit à l'arrière-plan, soit suspendu pour le shell qui l'a lancé (variable en fonction du shell). Pour une sécurité maximale, vous pouvez exécuter .IP bg; disown .LP dans le vieux shell pour supprimer l'association avec le programme, mais .B reptyr tente de s'assurer que le programme visé reste en cours d'exécution même si vous fermez le shell sans le faire. .SH OPTIONS .B \-l .IP Plutôt que d'attacher un nouveau processus, crée un couveau couple de pty, redirige l'extrémité maîtresse vers le terminal en corus, puis affiche le nom du pty esclave. Il pourra être passé en argument par exemple à l'option .I set inferior-tty de .B gdb. .LP .B \-s .IP Par défaut, reptyr déplace tout descripteur de fichier de la cible qui était connecté au terminal de contrôle vers le nouveau terminal. L'option .B -s fait que reptyr attache les descripteurs de fichiers 0, 1 et 2 sans condition même si la cible n'a pas de terminal de contrôle ou qu'elle n'est pas connectée à un terminal. .LP .B \-v .IP Affiche la version de .B reptyr et sort. .LP .B \-h .IP Affiche un message d'usage et sort. .LP .B \-V .IP Affiche des messages verbeux. .LP .SH NOTES .B reptyr dépend de l'appel système .BR ptrace (2) pour s'attacher au programme distant. Sur Ubuntu Maverick et suivant cette possibilité est désactivée par défaut pour des raisons de sécurité. Vous pouvez l'activer temporairement avec .IP # echo 0 > /proc/sys/kernel/yama/ptrace_scope .LP en tant que rootn ou de manière permanente en éditant le fichier .IR /etc/sysctl.d/10-ptrace.conf , ui contient aussi plus d'information sur ce réglage. .SH BUGS Quand on s'attache à quelques programmes curses, ils ne redessinent pas immédiatement l'écran, et un .B ^L ou équivalent est nécessaire pour forcer l'actualisation. De la même manière, après avoir attaché certains programmes, le vieux terminal est dans un état étrange et un .B clear ou même un .B reset est nécessaire avant que le vieux terminal ne soit à nouveau utilisable. L'attachement à rtorrent (et peut-être à d'autres applications) ne fonctionne pas (rtorrent arrête d'accepter des entrées). Le problème est que rtorrent utilise epoll pour vérifier l'entrée standard et qu'on ne met pas à jour la référence interne que le descripteur de fichier d'epoll a de l'ancien terminal. L'attachement à un processus avec des fils ne fonctionne pas correctement. Il devrait être possible de le corriger. Il faut juste ptracer chaque fils individuellement et de jouer avec lui. L'attachement à un processus .BR less (1) ne fonctionne pas si vous avez un fichier .I .lessfilter parce que .BR less abandonne un fils zombie dans ce cas. Ça devrait pouvoir être corrigé. Vous pouvez rapporter des bugs à l'auteur (voir ci-dessous) ou par l'issue tracker sur GitHub. .SH AUTEURS reptyr est écrit par Nelson Elhage . .SH HOMEPAGE .URL https://github.com/nelhage/reptyr .SH VOIR AUSSI .BR neercs (1), .BR screen (1) nelhage-reptyr-2f0b1e1/reptyr.h000066400000000000000000000025651215373134400165530ustar00rootroot00000000000000/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #define REPTYR_VERSION "0.5" int attach_child(pid_t pid, const char *pty, int force_stdio); #define __printf __attribute__((format(printf, 1, 2))) void __printf die(const char *msg, ...); void __printf debug(const char *msg, ...); void __printf error(const char *msg, ...);