シリアルポートを UNIX ドメインソケットに見立てる

追記: socat で stdio を指定すればいいような気がしてきた。

f:id:moriyoshi:20070523220945p:image:left:w320
VMWare には、ゲストOSのシリアルポートを、ホストOSの UNIX ドメインソケットに見立てる機能がある *1。これを有効に活用するためには、現在使用している端末とそのソケットをつなぐプロキシがあればいいのだけど、ちょっと探した限りそのような要求を満たすものが見つからなかったのでざっくり書いてみたのが次の Ruby スクリプト。ツッコミどころ満載な気がするけども、何かの役に立つことを願いつつ。

端末には VT220 互換のものを想定している。F12 を押すとプロキシを終了する。使用するときは sockaddr_un('/tmp/aho') の /tmp/aho を、VMWare のソケットへのパス名に変えてください。

追記: そういえばこのままだとエスケープキー効かなくなるということに後で気づいた。そのうち直す。

追記2: ESC がバッファの最後の文字でなければエスケープシーケンスだとみなすようにしてみた。とりあえずはこれでおk。


require 'socket'
require 'termios'

ci = IO.open(0)
co = IO.open(1)

orig_term_state = Termios.tcgetattr(ci);

term_state = orig_term_state.clone()
term_state.lflag &= ~(Termios::ISIG | Termios::ECHO | Termios::ICANON)
Termios.tcsetattr(ci, Termios::TCSANOW, term_state)

s = Socket.open(Socket::PF_UNIX, Socket::SOCK_STREAM, 0)
s.connect(Socket.sockaddr_un('/tmp/aho'))

outbuf = ''
inbuf = ''
escbuf = ''
state = 0
running = true

while running
    r = IO.select([ci,s],[co,s],nil,1)
    if r[0].include?(s)
        inbuf += s.sysread(16384)
    end
    if r[1].include?(co) and not inbuf.empty?
        co.syswrite(inbuf)
        inbuf = ''
    end
    if r[0].include?(ci)
        buf = ci.sysread(16384)
        p = 0
        while p < buf.length
            case state
            when 0
                i = buf.index(0x1b, p)
                if i != nil and i != buf.length - 1
                    outbuf += buf[p...i]
                    p = i
                    esc_buf = ''
                    state = 1
                else
                    outbuf += buf[p..-1]
                    p = buf.length
                end
            when 1
                if esc_buf.length < 5
                    c = buf.length - p
                    c = 5 - esc_buf.length if c > 5 - esc_buf.length
                    esc_buf += buf[p, c]
                    p += c
                end

                if esc_buf.length >= 5
                    case esc_buf
                    when "\x1b[24~": # VT220 F12
                        running = false
                    end
                    state = 0
                end
            end
        end
    end
    if r[1].include?(s) and not outbuf.empty?
        s.syswrite(outbuf)
        outbuf = ''
    end
end

Termios.tcsetattr(ci, Termios::TCSANOW, orig_term_state)

*1:Named Pipe と書かれているがこれは Windows 上での呼称を意識したもので UNIX 的な FIFO ではない