CoffeeScript を使える環境を作ったメモ

FreeBSD

% sudo portinstall www/node
% npm install -g coffee-script

OS X

# homebrew を入れて
% /usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/gist/323731)"
# node.js をインストールして
% brew install node
# npm を入れてから
% curl http://npmjs.org/install.sh | sh
% npm install -g coffee-script

OS X では、MacPorts から Homebrew に移行してみた。

サーバーが壊れた

自宅で 3 年間使っていた ML115 G5 の電源が壊れたので、新しいサーバーを買いました。

新サーバーは、ML110 G6 (Core i3-530) で、メモリは 10GB に増やしてみました。快適…。


以下、残っていたサーバー死亡直前のグラフ。電圧が徐々に下がって、NIC で異常が起きたり、CPU 負荷が上がっていたようで。



FreeBSD なサーバーに VirtualBox を入れて、その上で Ubuntu server を動かす

というのを CUI だけで行う手順。(途中で VNC は使っちゃうけど。)


VBoxGuestAdditions について追記。

VirtualBox インストール

まずは、VirtualBox をインストールする。ただし、make option で、"Build with VNC support" を追加しておく。

% sudo portinstall emulators/virtualbox-ose

インストールが終わったら /boot/loader.conf に以下を追記する。

vboxdrv_load="YES"

さらに、/etc/rc.conf に以下を追記する。

vboxnet_enable="YES"

インストールはここまで。ドライバーを読ませるために再起動しておく。

ゲスト OS インストール

ゲスト OS をインストールするには、まず以下でサポートされているOSのタイプを調べる。

% VBoxManage list ostypes
(snip)
ID:          Ubuntu
Description: Ubuntu
(snip)

今回は ubuntu を入れるので、これをメモ。

いよいよ VM 作成。

# VM を作成
% VBoxManage createvm -name "ubuntu server" -basefolder /path/to/vm/dir --ostype Ubuntu --register
# HDD を作成
% VBoxManage createhd --filename "ubuntu server/ubuntu server.vdi" --size 102400
# IDE コントローラーを VM に追加。(初めは SirialATA でやってみたけど、途中でハングした。)
% VBoxManage storagectl "ubuntu server" --name "IDE Controller" --add ide
# HDD をアタッチ
% VBoxManage storageattach "ubuntu server" --storagectl "IDE Controller" --port 0 --device 0 --type hdd --medium "ubuntu server/ubuntu server.vdi"
# ゲスト OS のインストールディスクをアタッチ
% VBoxManage storageattach "ubuntu server" --storagectl "IDE Controller" --port 1 --device 1 --type dvddrive --medium ubuntu-11.04-server-i386.iso
# VM を VNC (port 2929) 付きで起動
% VBoxHeadless --startvm "ubuntu server" --vnc --vncport 2929

この状態で、VNC で繋いでみると ubuntu のインストーラーが上がっているはずなので、普通にインストールする。

インストールが完了したら、インストールディスクをデタッチする。

% VBoxManage storageattach "ubuntu server" --storagectl "IDE Controller" --port 1 --device 1 --type dvddrive --medium none

NIC をブリッジ接続でアタッチする。

% VBoxManage modifyvm "ubuntu server" --nic1 bridged --bridgeadapter1 bge0 --nicspeed1 1000000

使わないので VRDE を切る

% VBoxManage modifyvm "ubuntu server" --vrde off


これで、完了。あとは、ゲスト OS 側で SSH を設定してやれば VNC はもう要らないので、次回以降 VM を起動するときは以下のコマンドを使う。

% VBoxHeadless --vrde off --startvm "ubuntu server"

VBoxGuestAdditions インストール

まず、Guest OS 側で dkms をインストールしておく。

% sudo aptitude install dkms

ホスト側で、VBoxGuestAdditions.iso をアタッチする。(Guest 停止中じゃないとダメかも。)

% VBoxManage storageattach "ubuntu server" --storagectl "IDE Controller" --port 1 --device 0 --type dvddrive --medium /usr/local/lib/virtualbox/additions/VBoxGuestAdditions.iso

次に、Guest 側で CDROM を mount して、cd して以下を実行する。(x 入ってないので --nox11 付けてる。)

% sudo sh VBoxLinuxAdditions.run --nox11

Guest を停止して、ホスト側で以下を実行して VBoxGuestAdditions.iso をデタッチする。

% VBoxManage storageattach "ubuntu server" --storagectl "IDE Controller" --port 1 --device 0 --type dvddrive --medium none

Guest を起動しなおせば完成。

SSL_read, SSL_write での SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE と、SSL_pending の話

TLS/SSL による通信をやりたい場合、OpenSSL を使えば簡単に実装することができる。
具体的には、recv(2), send(2) を直に発行するノリで、SSL_read(3), SSL_write(3) を使えばいい……と思っていたが、そうではないらしい。

ということで、調べたことをつらつら書いてみる。なお、以下は socket が non-blocking であることを想定して書いている。

それから、OpenSSL をそこまで読み込んだわけではないので、間違っているかもしれない。ツッコミ大歓迎!

SSL_read での SSL_ERROR_WANT_READ. SSL_write での SSL_ERROR_WANT_WRITE

データの送信を行おうとしたときに TCP/IP のバッファがいっぱいで積めない場合、send は EAGAIN でエラーする。
同様に SSL_write は SSL_ERROR_WANT_WRITE を返す。この時、socket が writable になったら、全く同じ引数で SSL_write を呼ばなければならない。


WARNING
When an SSL_write() operation has to be repeated because of
SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be repeated with
the same arguments.

When calling SSL_write() with num=0 bytes to be sent the behaviour is
undefined.


recv の場合は、TCP/IP のバッファが空であった場合に EAGAIN でエラーする。
同様に SSL_read は SSL_ERROR_WANT_READ でエラーする。この時 SSL_write と同じで、socket が readable になったら、全く同じ引数で SSL_read を呼ばなければならない。
ここで注意しなければならないのが、select(2) で readable だったとしても、SSL_read が SSL_ERROR_WANT_READ でエラーすることがあるという点。
TLS/SSL では、データはある程度ブロックに分けられた上で、暗号化されて送受信されているため、1ブロック分全部そろっていないとデコードできない。よって、中途半端にデータを受信した場合にも SSL_ERROR_WANT_READ が返るということになる。

SSL_read での SSL_ERROR_WANT_WRITE. SSL_write での SSL_ERROR_WANT_READ

ややこしい事に、先ほどとは逆の理由でエラーすることがある。
OpenSSL では、SSL_read, SSL_write の延長上でネゴシエーション処理が実行されることがあるために、read しようとしたのに WANT_WRITE, write しようとしたのに WANT_READ が返ってきてしまう。

よって、先程の仕様とあわせると、

  • SSL_ERROR_WANT_READ: readable になってから関数を再実行
  • SSL_ERROR_WANT_WRITE: writable になってから関数を再実行

しないといけない。

すなわち……

SSL_read SSL_write
SSL_ERROR_WANT_READ でエラー select(readable), SSL_read select(readable), SSL_write
SSL_ERROR_WANT_WRITE でエラー select(writable), SSL_read select(writable), SSL_write

これはメンドクサイですね…。

SSL_pending

更に、SSL_pending という物がある。
この関数は、ssl オブジェクトのバッファから block せずに読み取り可能な byte 数を返すものである。

ここで注意しなければならないのが、socket が readable ではない場合にも ssl オブジェクトのバッファにデータが乗っていることがあるということである。*1

これを考慮すると、select で readable か判定する前に SSL_pending で SSL_read *2可能かどうかチェックする必要性が見えてくる。

ただし、次の記載が man に有って、少々気にはなる。


BUGS
(snip)

Up to OpenSSL 0.9.6, SSL_pending() does not check if the record type of
pending data is application data.

*1:ネゴシエーション処理などの関係で、らしい。

*2:SSL_pending は受信バッファなので、SSL_write は無関係

IPC::Open3 を使って、子プロセスの標準出力と標準エラー出力をポーリングする。(Windows でも動くよ!)

先日の
IPC::Open3 を使って、子プロセスの標準出力と標準エラー出力をポーリングする。(Windows では動かなかった…) - ◆F99a.q8oVEの日記
は Windows では動きませんでした。

Windows のルートでは

# $dad_wtr は open3 の第2引数
#   open3(undef, '>&'. fileno($child_out), '>&' . fileno($child_err), @cmd)
#   を実行したとすると '>&'. fileno($child_out)
$dad_wtr =~ s/^[<>]&//
$kid_rdr = \*{$dad_wtr};

されたものが fdopen に渡されています。
ここで、上の例のように fd を渡してもうまくいきませんでした。

そこで、

open3(undef, '>&'. $child_out, '>&' . $child_err, @cmd);

こうして見てもうまくいかないので…

色々試して↓ならうまくいきました。"*" できるもの、つまりグロブを指定しないといけないということかな?

open3(undef, '>&CHILD_OUT', '>&CHILD_ERR', @cmd);

ということで、完成版。

qx2 は Windows 以外、qx2_win は Windows 用。Windows では WIFEXITED が定義されていないと思うので、適宜あれしてください。

#!/usr/bin/env perl
use strict;
use warnings;

sub qx2_win {
    my @cmd = @_;

    use IPC::Open3;
    use File::Temp qw/tmpnam/;

    my $out_file = tmpnam();
    my $err_file = tmpnam();
    local (*CHILD_OUT, *CHILD_ERR);
    open CHILD_OUT, '+>', $out_file or die $!;
    open CHILD_ERR, '+>', $err_file or die $!;
    my $pid = open3(undef, '>&CHILD_OUT', '>&CHILD_ERR', @cmd);
    waitpid $pid, 0;

    # my $code = WIFEXITED($?) ? WEXITSTATUS($?) : WTERMSIG($?);
    my $code = ($? | 0xFF) ? ($? >> 8) & 0xFF : $?;

    seek CHILD_OUT, 0, 0;
    seek CHILD_ERR, 0, 0;
    my $out = do { local $/; <CHILD_OUT>; };
    my $err = do { local $/; <CHILD_ERR>; };
    close CHILD_OUT;
    close CHILD_ERR;
    unlink $out_file, $err_file;

    return ($out, $err, $code);
}

sub qx2 {
    my @cmd = @_;

    use IPC::Open3;
    use Symbol qw/gensym/;
    use IO::Select;
    use POSIX ":sys_wait_h";

    my ($child_out, $child_err) = (gensym, gensym);
    my $pid = open3(undef, $child_out, $child_err, @cmd);

    my $s = new IO::Select($child_out, $child_err);
    my $out = my $err = '';

    while (1) {
        while (my @ready = $s->can_read) {
            for my $fh (@ready) {
                if (sysread($fh, my $buf, 4096) > 0) {
                    if ($fh == $child_out) {
                        $out .= $buf;
                    } elsif ($fh == $child_err) {
                        $err .= $buf;
                    }
                } else {
                    $s->remove($fh);
                    close $fh;
                }
            }
        }

        if (waitpid($pid, WNOHANG) > 0) {
            last;
        }
    }

    my $code = WIFEXITED($?) ? WEXITSTATUS($?) : WTERMSIG($?);
    return ($out, $err, $code);
}

use Test::More;


my @tests = (
    { command => [ 'perl', '-e', q{print 'a'} ], out => 'a', err => '', code => 0 },
    { command => [ 'perl', '-e', q{exit 0} ], out => '', err => '', code => 0 },
    { command => [ 'perl', '-e', q{exit 1} ], out => '', err => '', code => 1 },
    { command => [ 'perl', '-e', q{exit 255} ], out => '', err => '', code => 255 },
    { command => [ 'perl', '-e', q{print 'a' x (1024 ** 2)} ], out => 'a' x (1024 ** 2), err => '', code => 0 },
    { command => [ 'perl', '-e', q{print STDERR 'a' x (1024 ** 2)} ], out => '', err => 'a' x (1024 ** 2), code => 0 },
    { command => [ 'perl', '-e', q{print STDERR 'b' x (1024 ** 2); sleep 3; print 'a' x (1024 ** 2)} ], out => 'a' x (1024 ** 2), err => 'b' x (1024 ** 2), code => 0 },
);


for my $func (\&qx2, \&qx2_win) {
    for my $test (@tests) {
        my ($out, $err, $code) = &$func(@{$test->{command}});
        is $out, $test->{out};
        is $err, $test->{err};
        is $code, $test->{code};
    }
}

done_testing;