| 1 | #!/usr/bin/python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | |
|---|
| 4 | # Copyright (c) 2006 by KANEMOTO Shigeru |
|---|
| 5 | |
|---|
| 6 | import os |
|---|
| 7 | import sys |
|---|
| 8 | import tty |
|---|
| 9 | import pty |
|---|
| 10 | import select |
|---|
| 11 | import struct |
|---|
| 12 | import fcntl |
|---|
| 13 | import termios |
|---|
| 14 | import errno |
|---|
| 15 | import signal |
|---|
| 16 | import socket |
|---|
| 17 | import getopt |
|---|
| 18 | import curses |
|---|
| 19 | import pwd |
|---|
| 20 | |
|---|
| 21 | STDIN_FILENO = 0 |
|---|
| 22 | STDOUT_FILENO = 1 |
|---|
| 23 | |
|---|
| 24 | ENV_FLAGNAME = 'DUALPY' |
|---|
| 25 | |
|---|
| 26 | version = 'dual.py 2006/4/17 Copyright (c) 2006 by KANEMOTO Shigeru' |
|---|
| 27 | progname = sys.argv[0].split('/')[-1] |
|---|
| 28 | |
|---|
| 29 | |
|---|
| 30 | def getwinsz(fd): |
|---|
| 31 | winsize = struct.pack('HHHH', 0, 0, 0, 0) |
|---|
| 32 | winsize = fcntl.ioctl(fd, termios.TIOCGWINSZ, winsize) |
|---|
| 33 | return struct.unpack('HHHH', winsize) |
|---|
| 34 | # return (row, col, xpixel, ypixel) |
|---|
| 35 | |
|---|
| 36 | def setwinsz(fd, list): |
|---|
| 37 | # list = (row, col, xpixel, ypixel) |
|---|
| 38 | winsize = struct.pack('HHHH', *list) |
|---|
| 39 | fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize) |
|---|
| 40 | |
|---|
| 41 | xterm = 0 |
|---|
| 42 | |
|---|
| 43 | def settermwinsz(fd, list): |
|---|
| 44 | # list = (row, col, hpixel, vpixel) |
|---|
| 45 | global xterm |
|---|
| 46 | if xterm: |
|---|
| 47 | (row, col, xpixel, ypixel) = list |
|---|
| 48 | os.write(fd, '\033[8;%d;%d;t\033[4;%d;%d;t' % (row, col, ypixel, xpixel)) |
|---|
| 49 | |
|---|
| 50 | # |
|---|
| 51 | # xterm specific escape sequences |
|---|
| 52 | # |
|---|
| 53 | # ESC [ 4 ; height ; width ; t |
|---|
| 54 | # set screen size in pixels |
|---|
| 55 | # ESC [ 8 ; height ; width ; t |
|---|
| 56 | # set screen size in characters |
|---|
| 57 | # ESC [ 14 ; height ; width ; t |
|---|
| 58 | # request screen size in pixels |
|---|
| 59 | # ESC [ 18 ; height ; width ; t |
|---|
| 60 | # request screen size in characters |
|---|
| 61 | # |
|---|
| 62 | |
|---|
| 63 | owinsz = (0, 0, 0, 0) |
|---|
| 64 | |
|---|
| 65 | def enter_mode(): |
|---|
| 66 | try: |
|---|
| 67 | global owinsz, omode |
|---|
| 68 | owinsz = getwinsz(STDOUT_FILENO) |
|---|
| 69 | omode = tty.tcgetattr(STDIN_FILENO) |
|---|
| 70 | tty.setraw(STDIN_FILENO) |
|---|
| 71 | except tty.error, e: |
|---|
| 72 | print >>sys.stderr, "%s: Can't get/set tty mode: %s" % (progname, e[1]) |
|---|
| 73 | sys.exit(os.EX_IOERR) |
|---|
| 74 | |
|---|
| 75 | def reset_mode(): |
|---|
| 76 | global owinsz, omode |
|---|
| 77 | tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, omode) |
|---|
| 78 | if owinsz != (0, 0, 0, 0): |
|---|
| 79 | settermwinsz(STDOUT_FILENO, owinsz) |
|---|
| 80 | |
|---|
| 81 | def clearscreen(): |
|---|
| 82 | os.write(STDOUT_FILENO, curses.tigetstr('clear')) |
|---|
| 83 | |
|---|
| 84 | def writeall(fd, data): |
|---|
| 85 | while len(data) != 0: |
|---|
| 86 | n = os.write(fd, data) |
|---|
| 87 | data = buffer(data, n) |
|---|
| 88 | |
|---|
| 89 | class Decoder: |
|---|
| 90 | def __init__(self, sink, screen): |
|---|
| 91 | self.sink_ = sink |
|---|
| 92 | self.screen_ = screen |
|---|
| 93 | self.first_ = 1 |
|---|
| 94 | self.s_ = '' |
|---|
| 95 | |
|---|
| 96 | def data(self, s): |
|---|
| 97 | self.s_ += s |
|---|
| 98 | while 1: |
|---|
| 99 | if len(self.s_) < 3: |
|---|
| 100 | return |
|---|
| 101 | (length, cmd) = struct.unpack('!HB', self.s_[:3]) |
|---|
| 102 | if len(self.s_) < length: |
|---|
| 103 | return |
|---|
| 104 | |
|---|
| 105 | s = self.s_[3:length] |
|---|
| 106 | self.s_ = self.s_[length:] |
|---|
| 107 | |
|---|
| 108 | if cmd == 0: |
|---|
| 109 | writeall(self.sink_, s) |
|---|
| 110 | elif cmd == 1: |
|---|
| 111 | (height, width) = struct.unpack('!HH', s[:4]) |
|---|
| 112 | (oheight, owidth, oxpixel, oypixel) = getwinsz(self.screen_) |
|---|
| 113 | change = 0 |
|---|
| 114 | if self.first_: |
|---|
| 115 | if height < oheight: |
|---|
| 116 | change = 1 |
|---|
| 117 | oheight = height |
|---|
| 118 | if width < owidth: |
|---|
| 119 | change = 1 |
|---|
| 120 | owidth = width |
|---|
| 121 | self.first_ = 0 |
|---|
| 122 | else: |
|---|
| 123 | if height != oheight or width != owidth: |
|---|
| 124 | change = 1 |
|---|
| 125 | oheight = height |
|---|
| 126 | owidth = width |
|---|
| 127 | if change: |
|---|
| 128 | setwinsz(self.screen_, (oheight, owidth, oxpixel, oypixel)) |
|---|
| 129 | settermwinsz(self.screen_, (oheight, owidth, oxpixel, oypixel)) |
|---|
| 130 | |
|---|
| 131 | class Encoder: |
|---|
| 132 | def __init__(self, dst): |
|---|
| 133 | self.dst_ = dst |
|---|
| 134 | |
|---|
| 135 | def sink(self, s): |
|---|
| 136 | self.dst_.send(struct.pack('!HB', len(s) + 3, 0) + s) |
|---|
| 137 | |
|---|
| 138 | def winsz(self, list): |
|---|
| 139 | (height, width, xpixel, ypixel) = list |
|---|
| 140 | self.dst_.send(struct.pack('!HBHH', 7, 1, height, width)) |
|---|
| 141 | |
|---|
| 142 | |
|---|
| 143 | # |
|---|
| 144 | # Client |
|---|
| 145 | # |
|---|
| 146 | |
|---|
| 147 | def client(dstport): |
|---|
| 148 | try: |
|---|
| 149 | print >>sys.stderr, '*** Connecting to port %d' % dstport |
|---|
| 150 | link = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|---|
| 151 | link.connect(('127.0.0.1', dstport)) |
|---|
| 152 | except socket.error, e: |
|---|
| 153 | print >>sys.stderr, "%s: Can't connect: %s" % (progname, e[1]) |
|---|
| 154 | sys.exit(os.EX_UNAVAILABLE) |
|---|
| 155 | |
|---|
| 156 | clearscreen() |
|---|
| 157 | enter_mode() |
|---|
| 158 | decoder = Decoder(STDOUT_FILENO, STDOUT_FILENO) |
|---|
| 159 | encoder = Encoder(link) |
|---|
| 160 | encoder.winsz(getwinsz(STDOUT_FILENO)) |
|---|
| 161 | |
|---|
| 162 | def sigwinch(s, frame): |
|---|
| 163 | encoder.winsz(getwinsz(STDOUT_FILENO)) |
|---|
| 164 | signal.signal(signal.SIGWINCH, sigwinch) |
|---|
| 165 | |
|---|
| 166 | try: |
|---|
| 167 | try: |
|---|
| 168 | while 1: |
|---|
| 169 | try: |
|---|
| 170 | (rfds, wfds, xfds) = select.select([STDIN_FILENO, link], [], [], 1) |
|---|
| 171 | |
|---|
| 172 | if STDIN_FILENO in rfds: |
|---|
| 173 | encoder.sink(os.read(STDIN_FILENO, 1024)) |
|---|
| 174 | |
|---|
| 175 | if link in rfds: |
|---|
| 176 | data = link.recv(1024) |
|---|
| 177 | if len(data) == 0: |
|---|
| 178 | break |
|---|
| 179 | decoder.data(data) |
|---|
| 180 | |
|---|
| 181 | except (select.error, OSError), e: |
|---|
| 182 | if e[0] != errno.EINTR: |
|---|
| 183 | raise e |
|---|
| 184 | |
|---|
| 185 | except OSError, e: |
|---|
| 186 | if e[0] != errno.EIO: # EIO when the link losts |
|---|
| 187 | raise |
|---|
| 188 | |
|---|
| 189 | finally: |
|---|
| 190 | reset_mode() |
|---|
| 191 | print >>sys.stderr, '*** Closed the connection with port %d' % dstport |
|---|
| 192 | |
|---|
| 193 | |
|---|
| 194 | # |
|---|
| 195 | # Server |
|---|
| 196 | # |
|---|
| 197 | |
|---|
| 198 | def server(argv): |
|---|
| 199 | (pid, fd) = pty.fork() |
|---|
| 200 | if pid == 0: |
|---|
| 201 | # child |
|---|
| 202 | try: |
|---|
| 203 | os.environ[ENV_FLAGNAME] = '' |
|---|
| 204 | os.execvp(argv[0], argv) |
|---|
| 205 | except OSError, e: |
|---|
| 206 | os.write(STDOUT_FILENO, |
|---|
| 207 | '\0\0\0Can\'t exec "%s": %s\0\0\0' % (argv[0], e[1])) |
|---|
| 208 | os._exit(os.EX_OSERR) |
|---|
| 209 | |
|---|
| 210 | data = os.read(fd, 1024) |
|---|
| 211 | if data[0:3] == '\0\0\0': |
|---|
| 212 | data = data[3:] |
|---|
| 213 | data = data[:data.index('\0\0\0')] |
|---|
| 214 | print >>sys.stderr, '%s: %s' % (progname, data) |
|---|
| 215 | (pid, st) = os.waitpid(pid, 0) |
|---|
| 216 | sys.exit(os.WIFEXITED(st) and os.WEXITSTATUS(st) or os.EX_OSERR) |
|---|
| 217 | |
|---|
| 218 | listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|---|
| 219 | listener.bind(('127.0.0.1', 0)) # localhost, any port |
|---|
| 220 | print >>sys.stderr, '*** Waiting on port %d' % listener.getsockname()[1] |
|---|
| 221 | listener.listen(1) |
|---|
| 222 | (link, sa) = listener.accept() |
|---|
| 223 | del listener |
|---|
| 224 | link.setblocking(0) |
|---|
| 225 | |
|---|
| 226 | clearscreen() |
|---|
| 227 | enter_mode() |
|---|
| 228 | decoder = Decoder(fd, STDOUT_FILENO) |
|---|
| 229 | setwinsz(fd, getwinsz(STDOUT_FILENO)) |
|---|
| 230 | encoder = Encoder(link) |
|---|
| 231 | encoder.winsz(getwinsz(STDOUT_FILENO)) |
|---|
| 232 | encoder.sink(data) |
|---|
| 233 | writeall(STDOUT_FILENO, data) |
|---|
| 234 | |
|---|
| 235 | def sigwinch(s, frame): |
|---|
| 236 | winsz = getwinsz(STDOUT_FILENO) |
|---|
| 237 | setwinsz(fd, winsz) |
|---|
| 238 | encoder.winsz(winsz) |
|---|
| 239 | signal.signal(signal.SIGWINCH, sigwinch) |
|---|
| 240 | |
|---|
| 241 | try: |
|---|
| 242 | try: |
|---|
| 243 | while 1: |
|---|
| 244 | try: |
|---|
| 245 | (rfds, x, x) = select.select([fd, STDIN_FILENO, link], [], [], 1) |
|---|
| 246 | |
|---|
| 247 | if fd in rfds: |
|---|
| 248 | data = os.read(fd, 1024) |
|---|
| 249 | writeall(STDOUT_FILENO, data) |
|---|
| 250 | encoder.sink(data) |
|---|
| 251 | |
|---|
| 252 | if STDIN_FILENO in rfds: |
|---|
| 253 | writeall(fd, os.read(STDIN_FILENO, 1024)) |
|---|
| 254 | |
|---|
| 255 | if link in rfds: |
|---|
| 256 | decoder.data(link.recv(1024)) |
|---|
| 257 | |
|---|
| 258 | except (select.error, OSError), e: |
|---|
| 259 | if e[0] != errno.EINTR: |
|---|
| 260 | raise |
|---|
| 261 | |
|---|
| 262 | except OSError, e: |
|---|
| 263 | if e[0] != errno.EIO: # EIO when the shell exits |
|---|
| 264 | raise |
|---|
| 265 | |
|---|
| 266 | finally: |
|---|
| 267 | reset_mode() |
|---|
| 268 | print >>sys.stderr, '*** Closed the session for "%s".' % ' '.join(argv) |
|---|
| 269 | |
|---|
| 270 | |
|---|
| 271 | # |
|---|
| 272 | # Main |
|---|
| 273 | # |
|---|
| 274 | |
|---|
| 275 | def usage(): |
|---|
| 276 | print >>sys.stderr, "Usage: %s [-x] [-f] [-c port | program-to-exec]" % progname |
|---|
| 277 | |
|---|
| 278 | xterm = os.environ.has_key('DUALPYXTERM') or 'xterm' in os.environ.get('TERM', '') |
|---|
| 279 | |
|---|
| 280 | try: |
|---|
| 281 | dstport = 0 |
|---|
| 282 | force = False |
|---|
| 283 | (opts, argv) = getopt.getopt( |
|---|
| 284 | sys.argv[1:], 'xfc:hv', ['xterm', 'force', 'version', 'help']) |
|---|
| 285 | for (o, a) in opts: |
|---|
| 286 | if o == '-c': |
|---|
| 287 | dstport = a |
|---|
| 288 | elif o in ('-x', '--xterm'): |
|---|
| 289 | xterm = 1 |
|---|
| 290 | elif o in ('-f', '--force'): |
|---|
| 291 | force = True |
|---|
| 292 | elif o in ('-v', '--version'): |
|---|
| 293 | print >>sys.stderr, '%s: %s' % (progname, version) |
|---|
| 294 | sys.exit(os.EX_OK) |
|---|
| 295 | elif o in ('-h', '--help'): |
|---|
| 296 | usage() |
|---|
| 297 | sys.exit(os.EX_OK) |
|---|
| 298 | except getopt.GetoptError: |
|---|
| 299 | usage() |
|---|
| 300 | sys.exit(os.EX_USAGE) |
|---|
| 301 | |
|---|
| 302 | if not force and os.environ.has_key(ENV_FLAGNAME): |
|---|
| 303 | print >>sys.stderr, "%s: Already in the %s environment" % (progname, progname) |
|---|
| 304 | sys.exit(os.EX_USAGE) |
|---|
| 305 | |
|---|
| 306 | try: |
|---|
| 307 | curses.setupterm() |
|---|
| 308 | |
|---|
| 309 | if dstport: |
|---|
| 310 | if argv: |
|---|
| 311 | usage() |
|---|
| 312 | sys.exit(os.EX_USAGE) |
|---|
| 313 | try: |
|---|
| 314 | client(int(dstport)) |
|---|
| 315 | except ValueError: |
|---|
| 316 | usage() |
|---|
| 317 | sys.exit(os.EX_USAGE) |
|---|
| 318 | |
|---|
| 319 | else: |
|---|
| 320 | if not argv: |
|---|
| 321 | s = os.environ.get('SUDO_USER') or \ |
|---|
| 322 | os.environ.get('LOGNAME') or \ |
|---|
| 323 | os.environ.get('USER') |
|---|
| 324 | try: |
|---|
| 325 | s = s and pwd.getpwnam(s)[6] |
|---|
| 326 | except KeyError: |
|---|
| 327 | pass |
|---|
| 328 | try: |
|---|
| 329 | s = s or pwd.getpwuid(os.getuid())[6] |
|---|
| 330 | except KeyError: |
|---|
| 331 | pass |
|---|
| 332 | argv = (s or '/bin/bash',) |
|---|
| 333 | server(argv) |
|---|
| 334 | |
|---|
| 335 | except SystemExit: |
|---|
| 336 | raise |
|---|
| 337 | |
|---|
| 338 | except KeyboardInterrupt: |
|---|
| 339 | print >>sys.stderr, '^C' |
|---|
| 340 | sys.exit(os.EX_OK) |
|---|
| 341 | |
|---|
| 342 | except OSError, e: |
|---|
| 343 | print >>sys.stderr, '%s: %s' % (progname, e[1]) |
|---|
| 344 | sys.exit(os.EX_OSERR) |
|---|
| 345 | |
|---|
| 346 | except Exception: |
|---|
| 347 | print >>sys.stderr, '%s: Unknown error' % progname |
|---|
| 348 | sys.exit(os.EX_SOFTWARE) |
|---|