FastCGI+lighttpd で C プログラムを動かす

FastCGI(fcgi)とは、通常のCGIのようにリクエストが発生する度に起動、実行される仕組みではなく、一度起動したら起動しっぱなしでリクエストを処理するタイプのCGIです。常にメモリの上に乗っているので、メモリを食いますが、大量のリクエストをオーバーヘッドなしで処理できる利点があります。

FastCGIでバイナリプログラムを実行するケースのパフォーマンスはなかなか良いらしいので、我が家のlighttpdサーバにも導入してみることにしました。

久しぶりに lighttpd.conf を覗いてみると、モジュール読み込み部分に既に mod_fastcgi が登録されていました。(そう言えば、以前 trac を動かしてみた際に FastCGI を使ってたっけ…)

server.modules = (
"mod_access", "mod_alias",
"mod_accesslog", "mod_compress",
"mod_redirect",
"mod_expire", "mod_fastcgi"
(略)
)

こんな感じです。次に、FastCGIの設定を行います。

fastcgi.server = (
  "/fcgi" => (
    "fcgi" => (
      "socket" => "/var/run/lighttpd/fcgi.socket",
      "check-local" => "disable",
      "bin-path" => "/var/www/fcgi-bin/test.fcgi",
      "min-procs" => 1,
      "max-procs" => 2
    )
  )
)

bin-path には、これから準備するバイナリのパスを指定しています。
我が家では、上の fcgi の設定に加えて、trac の設定もしてあります。

次にバイナリの用意です。FastCGI用のライブラリを持ってきます。

[bash]
$ sudo aptitude install libfcgi-dev
[/bash]

bin-path で指定した位置にバイナリを作ります。

[bash]
$ cd /var/www
$ sudo mkdir fcgi-bin
$ cd fcgi-bin
$ sudo vim fcgi.c
[/bash]

fcgi.c の内容は次のとおりです。

[cpp]

include <fcgi_stdio.h>

int main(int c, char** argv)
{
int cnt = 0;
while (FCGI_Accept() >= 0)
printf("Content-type: text/html\n\ncount=%d\n", ++cnt);
return 0;
}
[/cpp]

fcgi_stdio.h をインクルードすると、printf() などの標準ライブラリ関数が FastCGI のものに置き換えられ、FCGI_Accept() などの関数が使えるようになります。

このコードをビルドします。-lfcgi オプションで FastCGI へのリンクもお忘れなく。

[bash]
$ sudo gcc -o test.fcgi test.c -lfcgi -Wall -O3
$ sudo chmod a+rx test.fcgi
[/bash]

念の為、全ての権限に rx を付与しています。
最後に設定を反映させる為、lighttpd を再起動します。

[bash]
$ sudo /etc/init.d/lighttpd restart
[/bash]

lighttpd.conf の fastcgi.server で設定したパスにアクセスしてみます。

[bash]
$ curl http://127.0.0.1/fcgi
count=1
[/bash]

何度もアクセスしてみて、count の値が増えつづければプロセスが常駐していることが分かります。

[bash]
$ curl http://127.0.0.1/fcgi
count=2
$ curl http://127.0.0.1/fcgi
count=3
$ curl http://127.0.0.1/fcgi
count=4
[/bash]

ライブラリを作成、使用する(Windows編)

先日、覚え書きとして「ライブラリを作成、使用する」を書きましたが、同じ事を Microsoft Windows と Microsoft Visual Studio で行うためのメモです。

環境は Microsoft Windows 7 Professional 日本語版 32bit / Microsoft Visual Studio 2010 Professional です。

Linux では共有ライブラリと静的ライブラリでしたが、Windows の世界ではダイナミックライブラリ(.dll)と 共通オブジェクトファイル COFF(.lib)があるようです。後者は、Microsoft の文書中で「COFF形式の標準ライブラリ」とも表記されていますが、C 標準ライブラリとは無関係で、Linux で言う静的ライブラリの事を指すようです。

                       | Linux              | Windows
標準コンパイラ         | gcc                | cl.exe
オブジェクトファイル   | .o                 | .obj
静的ライブラリ         | .a                 | .lib
共有ライブラリ         | .so                | .dll
インポートライブラリ   |                    | .lib
エクスポートライブラリ |                    | .exp
エクスポート定義       | GCC version script | .def
ライブラリ検索パス     | 可変               | 強制
バージョン管理         | 環境変数、リンク   | Side by Side

DLL だけでも、レギュラー DLL、MFC に静的リンクしたレギュラー DLL、MFC に動的にリンクしたレギュラーDLL、MFC 拡張 DLL、COM をサポートした DLL(OLE/ActiveX/COM+)、Microsoft .NET Framework 向けのマネージド DLL など、様々な種類や実装方法があるようですが、最も純粋なレギュラーDLLをベースに書きたいと思います。

作業に取り掛かる前に、開発環境に必要な環境変数を定義する必要があります。Visual Studio をインストールすると、環境変数「VS100COMNTOOLS」が定義されます。これは名前のとおり、Visual Studio 2010 の共通ツールへのパスが定義されています。標準では「C:\Program Files\Microsoft Visual Studio 10.0\Common7\Tools」というパスになっているはずです。Visual Studio 2008 や 2005 でも VS**COMNTOOLS という環境変数が定義されるはずですので、読み替えればOKだと思います。

コマンドプロンプトを立ち上げ、次のようにタイプします。

> "%VS100COMNTOOLS%\vsvars32.bat"
 Setting environment for using Microsoft Visual Studio 2010 x86 tools.

これで開発に必要なツールに PATH が通り、開発ができるようになります。(なお、%VS100COMNTOOLS% には標準で空白文字が含まれているので、上記のようにダブルクォートで囲ってあげないと、変数展開後にパスを見失なってしまうので注意が必要です。vsvars32.bat で定義された環境変数の寿命は、コマンドプロンプトのセッション内ですので、コマンドプロンプトを一度閉じてしまったら、再度、上記のコマンドを実行する必要があります。)

前回と同様、サンプルとして次の func.c を準備します。

[cpp]
int add(int a, int b)
{
return a+b;
}
[/cpp]

二つの整数値を足して、その値を返すだけの簡単な関数 add() を定義しています。

オブジェクトファイルの静的リンク

前回と同様、Microsoft の C コンパイラ cl.exe も -c オプション(コンパイルのみ)をサポートしています。 Microsoft 流儀で言うと /c オプションとなりますが、このオプションはハイフン(-)でもスラッシュ(/)でも通るようです。実際に func.c をビルドすると、静的ライブラリ func.obj が作成できます。gcc では func.o ですが、拡張子が DOS 風に3文字となっています。

> cl -c func.c
 Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
 Copyright (C) Microsoft Corporation.  All rights reserved.

 func.c

Microsoft の著作権情報は /nologo オプションで非表示にすることができます。コマンドラインが長くなりますが… :(

呼び出し元から静的リンクするには、次のように一緒にビルドするだけです。ここはほとんど gcc と変わりませんね。

> cl /out:app.exe app.c func.obj
 Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
 Copyright (C) Microsoft Corporation.  All rights reserved.
app.c
 Microsoft (R) Incremental Linker Version 10.00.30319.01
 Copyright (C) Microsoft Corporation.  All rights reserved.
/out:app.exe
 app.obj
 func.obj
> .\app.exe
 a+b=8

gcc の -o オプションは cl だと /out: になります。省略すると、最初に指定されたファイル名に拡張子 exe をつけたものを吐いてくれますが、ビルドするファイルが多い場合や処理を自動化した場合、予期せぬファイル名を自動的につけて出力する危険性があるので、/out: オプションはなるべく省略しない方がいいと思います。

ちなみに、コマンドプロンプトから実行ファイルの指定を確実にするには、

> .\app.exe

と相対パスで実行する方が無難でしょう。

ダイナミックリンクライブラリを動的リンク

共有ライブラリのようなものとして、ダイナミックリンクライブラリ(DLL)という実装があります。これは、前回の Linux と gcc をやった時のようにコンパイルオプション一つ変更するだけで完成する訳ではなく、専用の記述やファイルを準備しなければなりません。

func.c を次のように修正します。

[cpp]
__declspec(dllexport)
int add(int a, int b)
{
return a+b;
}
[/cpp]

__declspec(dllexport) が関数宣言の前につきました。これは、ライブラリが公開する関数を制御する為に用いられるものらしいです。前回に比べればちょっと面倒くさいですが、そういうものらしいです。

これをビルドします。

>cl /LD func.c
 Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
 Copyright (C) Microsoft Corporation.  All rights reserved.

 func.c

 Microsoft (R) Incremental Linker Version 10.00.30319.01
 Copyright (C) Microsoft Corporation.  All rights reserved.

 /out:func.dll
 /dll
 /implib:func.lib

 func.obj

 ライブラリ func.lib とオブジェクト func.exp を作成中

オプションは /LD です。大文字・小文字を判別しますので注意してください。上手くビルドされると、4つのファイルが生成されます。

 func.dll ... DLL ファイル。
 func.exp ... エクスポートライブラリ。循環参照の解決に用いられるらしい。
 func.lib ... インポートライブラリ。外部から DLL を参照する際に用いられる。
 func.obj ... オブジェクトファイル。

これで DLL ファイルが完成しました。

次は、DEF ファイルを用いて作成してみます。DEF ファイルを用いると、オリジナルのソースコードをライブラリ化の為に変更しなくていいので、func.c を最初のサンプルのように戻します。

[cpp]
int add(int a, int b)
{
return a+b;
}
[/cpp]

そして func.def という新しいファイルを作成し、下記のように入力します。

LIBRARY    func
EXPORTS
     add @1

add 関数を序数1で「エクスポート(つまり公開)」する、という意味らしいです。

DEF ファイルを用いてビルドするには次のコマンドを実行します。

>cl /LD /DEF:func.def func.c
 Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
 Copyright (C) Microsoft Corporation.  All rights reserved.

 func.c

 Microsoft (R) Incremental Linker Version 10.00.30319.01
 Copyright (C) Microsoft Corporation.  All rights reserved.

 /out:func.dll
 /dll
 /implib:func.lib

 func.obj

DEF ファイルが、gcc でいうバージョンスクリプト的なものなら、なんとなく理解できる仕様です。ソースコードと DEF ファイルの管理が煩雑にならないか注意する必要がありますね。

さて、生成された DLL ファイルを利用するには、app.c も改変します。

[cpp]

include <stdio.h>

__declspec(dllimport) int add(int, int);
int main()
{
printf("a+b=%d\n", add(3, 5));
return 0;
}
[/cpp]

dllimport がついた関数宣言を追加しました。ただし、前回と同様に必須ではなく、MSDNでの解説曰く

__declspec(dllimport) を使用しなくてもコードは正しくコンパイルされますが、使用した方がコードがより洗練されます。__declspec(dllimport) を使うと、ある関数が DLL 内にあるかないかをコンパイラが判断できるようになり、その結果、コードでは通常 DLL の境界を越える関数呼び出し内に存在する間接操作のレベルをスキップできます。ただし、DLL 内に変数をインポートするには、__declspec(dllimport) を必ず使用する必要があります。

との事です。

さらにビルド時には、インポートライブラリを渡してやらなければなりません。func.lib を指定していますが、この場合は共通オブジェクトファイルとは全く別のものなので注意が必要です。

> cl app.c func.lib
 Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
 Copyright (C) Microsoft Corporation.  All rights reserved.

 app.c

 Microsoft (R) Incremental Linker Version 10.00.30319.01
 Copyright (C) Microsoft Corporation.  All rights reserved.

 /out:app.exe
 app.obj
 func.lib

 E:\tmp>app.exe
 a+b=8

これで実行バイナリ app.exe が生成されます。
ちなみに、インポートライブラリを使わずに DEF ファイルを用いて「インポート」する方法もあるらしいです。

ダイナミックリンクライブラリを動的ロード

ライブラリを作成、使用する

Linux 環境で gcc を用いて共有ライブラリを作成する際の基本的なことをメモしておきます。 g++ でも手順は同じです。

サンプルとして次の func.c を準備します。

int add(int a, int b)
{
    return a+b;
}

二つの整数値を足して、その値を返すだけの簡単な関数 add() を定義しています。

オブジェクトファイルの静的リンク

func.c-c (コンパイルのみ)でビルドすると、オブジェクトファイル func.o が作成できます。

$ gcc -c func.c

呼び出し元から静的リンクするには、次のように一緒にビルドするだけです。

$ gcc -o app app.c func.o
$ ./app
a+b=8

複数のオブジェクトファイルをまとめて、静的ライブラリにする

a.cb.cc.c のそれぞれがあった場合、アーカイブコマンド ar によってライブラリをまとめることができます。

$ gcc -c a.c b.c c.c
$ ar r libabc.a a.o b.o c.o

まとめられた libabc.a はただのアーカイブファイルですので、ranlib でライブラリ内のインデックスを構築してあげます。

$ ranlib libabc.a

libabc.a を使ったソースのビルドはこんな具合です。

$ gcc app.c libabc.a

共有ライブラリの動的リンク

func.c を共有ライブラリ libfunc.so としてビルドします。

$ gcc -shared -o libfunc.so func.c

-shared オプションをつけるだけです。このライブラリを使うには、次のような app.c を準備して

#include <stdio.h>
int main()
{
    printf("a+b=%d\n", add(3, 5));
    return 0;
}

まとめてビルドします。ヘッダファイルも不要です。

$ gcc -o app app.c libfunc.so
$ LD_LIBRARY_PATH=. ./app
a+b=8

ライブラリパスをカレントに指定して実行すると、ちゃんと libfunc.so の機能が使えていることが分かります。 簡単ですねー。

共有ライブラリの動的ロード

dlopen(3) を用います。ライブラリ側に特殊なコーディングは必要ありません。上記のように共有ライブラリとしてビルドすればOKです。

#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
    void *handle
    = dlopen("./libfunc.so", RTLD_LAZY);         // ライブラリを開く
    int (*add)(int, int) = dlsym(handle, "add"); // 関数ポインタを取ってくる
    printf("a+b=%d\n", (*add)(3, 5));            // 関数を叩く
    dlclose(handle);                             // ライブラリを閉じる
}

このコードは次のようにビルドします。

$ gcc -rdynamic -ldl -o app2 app2.c
$ ./app2
a+b=8

-ldl は動的ロードライブラリへのリンクです。

参考リンク

その他、留意点などは次のページを参考にしてください。

MewとSylpheedでnetnewsを読み書きする

プライベートなニュースサーバを稼動してみただけで安心してしまって、各種クライアントの設定は一度だけ行なったものの放置していたので、覚書として書いておくことにしました。

Mewでnetnewsを読み書きする

普段は、コンソール端末経由でどこからでも利用ができる Mew on Emacs でメールをしています。キーバインドを覚えるまでは敷居が高そうだったですが、覚えた後の効率は鬼の如しです。

まず、~/.mew.el にnetnewsの設定を記載します。

[lisp]
(setq mew-nntp-server “news.example.com”)
(setq mew-nntp-user nil)
(setq mew-nntp-header-only t)
(setq mew-nntp-newsgroup “-groupname”)
[/lisp]

私の場合、上のように設定したらOKでした。自宅LAN内からのアクセスのみを許可しているので、ユーザ認証は設けていません。(出先からアクセスしたい場合は、SSHトンネル経由でLAN接続しています)
公式のリファレンスや古いML記事などにmewでnetnewsをする為の設定はちらほら書いてありましたが、絶対数が少ない上にニッチな設定方法が多かった為、今の自分の環境に適した設定方法を見つけるのには苦労しました。私が見つけた中で、一番最近のmew+netnewsの記事でも既に5年前のものでした。

  1. Mew を起動します。
  2. g -groupname をタイプし、グループディレクトリを移動します。もし、「Type ‘C-uZ’ to collect newsgroups」と怒られてしまったら、C-u Z をタイプしてローカルのグループディレクトリを作成してください。
  3. メッセージの取得は s all です。 mew-nntp-header-only を t にしてますので、メッセージヘッダのみをダウンロードします。
  4. 本文をダウンロードするには、サマリでメッセージにカーソルを合わせた状態で I をタイプします。

あとは、通常と同じオペレーションで、a でリプライしたり w で投稿したりできます。

Sylpheedでnetnewsを読み書きする

マルチプラットフォームであり、メールデータの内部保持を mbox ライクなプレーンな形式で持っており、使い勝手もなかなか良いので Windows では Sylpheed を利用しています。

  1. メニューの「設定」から「アカウントの編集」を選択します。
  2. 「追加」ボタンを押し、「新規アカウントの設定」ダイアログを表示します。
  3. 「基本」タブの「サーバ情報」フィールド内にある「プロトコル」を「ニュース(NNTP)」に設定し、サーバの設定を入力します。
  4. アカウントの追加が完了すると、メイン画面の「フォルダ」に新規に追加したニュースのアカウントが追加されます。
  5. そのフォルダを右クリックして、「ニュースグループを購読」をクリックします。
  6. 目的のニュースグループを追加すると、フォルダツリーにサブディレクトリとしてグループが追加されます。
  7. グループフォルダを右クリックして、「ダウンロード」すると新着のメッセージを取得することができます。

上記の手順で設定を終えると、あとは通常のメールと同じようなオペレーションで投稿やリプライができます。

Mozilla Thunderbird で netnews を読み書きする

Bash, Perl, Ruby, Pythonで正規表現置換

前回のC++/boost.NETに加え、各種インタプリタ言語でも同じ動作をするスクリプトを書いてみました。

シェルスクリプト(bash)

シェルスクリプトはそもそもグルー言語ですので、他のコマンドを呼び出して処理をすることが一般的です。下のサンプルではsedコマンドで置換処理をしています。bashだと正規表現マッチングは可能なので、ガリガリとスクリプトを書けばsedを使わずに実現できるかもしれません。

[bash]

!/usr/bin/env bash

[ $# -ne 2 ] && exit 1
cat | sed -e "s/$1/$2/g"
exit 0
[/bash]

リプレイスメント置換文字は、sedの書式になります。グループ指示子はダラー($)ではなくバックスラッシュ(\)を用いています。

Perl

Perlの場合、正規表現による文字列操作は、関数でもクラスでもなく構文として組み込まれています。その為、コマンドライン引数から渡された「$1」のようなリプレイスメントの展開方法に、若干の工夫が必要です。

[perl]

!/usr/bin/env perl

exit 1 if ($#ARGV != 1);
for (<stdin>) {
eval "s/$ARGV[0]/$ARGV[1]/g" && print;

s/$ARGV[0]/$ARGV[1]/gee && print; # これではダメ

}
exit 0;
[/perl]

リプレイスメントが格納されている$ARGV[1]は展開されると、例えば「$1$2」という文字列になります。この展開後の文字列をリプレイスメントとして正規表現置換の処理に投げたいのですが、正規表現置換処理を行なった後に変数展開されるようでリプレイスメントのグループ指示子としての$nが正規表現置換処理に伝わりません。そこで、正規表現置換処理を行う前に$ARGV[1]を展開させるべく、eval関数に投げています。これにより、正規表現置換処理が評価される時点でリプレイスメントは「$ARGV[1]」ではなく「$1$2」という文字列として解釈され、指示子が正しく伝わるようです。
検索パターンに加え、リプレイスメント文字列も変数で持つという処理は多々あるはずなので、もっとスマートな方法が準備されているのかもしれませんが、性質さえ知っていれば公式を知らずとも期待する処理が可能である、まさに”TMTOWTDI / There’s More Than One Way To Do It(やり方はひとつじゃない)”という設計思想を持つPerlらしい実装です。理に適った挙動は見ていて気持ちがいいです。

Python

Pythonでは、シェルスクリプトやPerlと違って正規表現機能はクラス(re)として提供されています。下のサンプルではreの静的関数を叩いていますが、プリコンパイルしたオブジェクトとしても利用できたと思います。

[python]

!/usr/bin/env python

import sys
import re
v = sys.argv
if len(v) != 3: quit(1)
for line in sys.stdin:
print re.sub(v[1], v[2], line),
quit(0)
[/python]

リプレイスメントのグループ指示子は、sedと同じくバックスラッシュです。Cやシェルライクなエスケープ文字という捉え方をするなら、バックスラッシュがしっくり来ますね。(といっても、ダラーの場合でも同じく「シェルライク」ですけど :D )

ちなみに、len() がオブジェクトのメソッドではなく独立した関数になっているのは、評価対象(上の例の場合は v )が null オブジェクトでも、null チェックなしで利用できるようにする為だとどこかで読みました。Python の場合は空文字列(len=0)は null オブジェクトになるんですね。

Ruby

Rubyの場合は、シェルスクリプトを除いた他のどの言語よりもマニアックな感じがしますが、よく見てみると一番「ナチュラル」に理解し易い構文です。これだけの構文で比較するのはアレかもしれませんが、これだけの構文だけでここまで革新的な要素をたくさん見てとれるのは、rubyだからこそと言った感じでしょうか。ファイルディスクリプタまでオブジェクトであり、イテレータをせおっているあたり、カワイイです。

[ruby]

!/usr/bin/env ruby

if ARGV.size != 2 then exit 1 end
STDIN.each_line do |line|
puts line.gsub(/#{ARGV[0]}/, ARGV[1])
end
exit 0
[/ruby]

Rubyの何十倍もPythonのコードは書いていますが、Pythonは「VB.NETよりも使いものになる、綺麗なVB.NET的な何か」というイメージが拭えません。いいところ、悪いところの両方を知った上で使いこなせていけたらいいなあ、と思いました。

See also

.NETで正規表現ライブラリを使う

前回のboostの正規表現ライブラリのサンプルに引き続き、C#とC++/CLI、VB.NETでも同じサンプルを書いてみました。全て.NET FrameworkのSystem.Text.RegularExpressions.Regexを用いています。

なんてことはありませんね。

C#版

こちらはMono 2.6.7環境のmcs/gmcsでビルドしてチェックしました。

[csharp]
using System;
using System.Text.RegularExpressions;

// usage: a.exe pattern replace-to

public class Program
{
static int Main(string[] v)
{
if (v.Length != 2) return 1;

Regex r = new Regex(v[0]); // 正規表現オブジェクト
string buf;                // 読み取り用のバッファ

while ((buf = Console.ReadLine()) != null)
  Console.WriteLine(r.Replace(buf, v[1]));

return 0;

}
}
[/csharp]

C++/CLI版

C++/CLIはオープンソースなコンパイラが存在しないため、Microsoft Visual Studio 2008 SP1を用いてビルド、チェックしました。

[cpp]
using namespace System;
using namespace System::Text::RegularExpressions;

// usage: a.exe pattern replace-to

int main(array<System::String ^> ^v)
{
if (v->Length != 2) return 1;

Regex^ r = gcnew Regex(v[0]); // 正規表現オブジェクト
String^ buf;                  // 読み取り用のバッファ

while ((buf = Console::ReadLine()) != nullptr)
    Console::WriteLine(r-&gt;Replace(buf, v[1]));

return 0;

}
[/cpp]

VB.NET版

ついでにVB.NET版も。これもMonoのvbncでテストしました。

[vb]
Imports System
Imports System.Text.RegularExpressions

Module Program

Public Sub Main(ByVal v() As String)

If v.Length &lt;&gt; 2 Then Exit Sub

Dim r As New Regex(v(0)) ' 正規表現オブジェクト
Dim buf As String        ' 読み取り用のバッファ
buf = Console.ReadLine() ' 代入結合判定できないので
                         ' 予め内容を詰めておく。

While buf &lt;&gt; Nothing
  Console.WriteLine(r.Replace(buf, v(1)))
  buf = Console.ReadLine()
End While

End Sub

End Module
[/vb]

1979年のOS『Version 7 UNIX』を使う

Version 7 UNIXとは、DECのPDP-11で動いていたUNIXです。AT&Tベル研究所製の初期のUNIXシリーズの直系のバージョンで、1979年にリリースされました。

V7は現在、フリーソフトウェアとして公開されており、こちらからブートイメージを入手することができます。また、x86への移植活動もあり、VM用のディスクイメージが公開されていたりします。

古いUNIXを触ってみたくなったので、古いハードウェアをシミュレートしてくれるエミュレータ simh を導入して遊んでみることにしました。

下準備

まずは必要なものを揃えます。simh は、dpkg系のパッケージ管理システムを採用しているディストリビューション上で次のようにしてインストールします。

$ sudo aptitude install simh

次に、V7のイメージをダウンロードし、解凍しておきます。

$ mkdir /tmp/v7
$ cd /tmp/v7
$ wget http://ftp.fibranet.cat/UnixArchive/PDP-11/Boot_Images/v7_rl02_1145.gz
$ gunzip v7_r102_1145.gz
$ ls
v7_rl02_1145
$

設定

V7用の設定ファイルを書きます。

$ cat > uv7
set cpu 11/45
set rl0 RL02
att rl0 v7_rl02_1145
set rl1 RL02
att rl1 rl1.dsk
att tm0 tm0.tap
boot rl0
$

4行目の最後のカラムに書いている「v7_r102_1145」は、ダウンロードして解凍したV7のディスクイメージです。

実行

さっそく実行してみます。ホスト側とゲスト側を区別する為、ホスト側には「host$」プロンプトを用います。

host$ pdp11 uv7

PDP-11 simulator V3.8-1
Disabling XQ
Overwrite last track? [N]
@

最後のトラックを上書きするか?と聞かれましたが、とりあえず標準の「N」と答えておきました。この「@」プロンプトの場面はきっとPDP-11の組込みブートローダなんでしょう。ブートします。

@boot
New Boot, known devices are hp ht rk rl rp tm vt
:

ブートデバイスを聞いてきますので、次のように入力します。

rl(0,0)rl2unix
mem = 177856
#

プロンプトが「#」に変更され、シングルユーザモードでV7が使えるようになりました。ここで、C-dをタイプしてマルチユーザモードに移行します。

Restricted rights: Use, duplication, or disclosure
is subject to restrictions stated in your contract with
Western Electric Company, Inc.
Thu Sep 22 05:50:55 EDT 1988

login:

見慣れたログイン画面が登場しました。とりあえずrootでログインします。パスワードは「root」です。

login: root
Password:
You have mail.
#

無事、ログインすることができました。

シェルの使い方

stty
speed 300 baud
erase = ‘#’; kill = ‘@’
even odd -nl echo -tabs

sttyコマンドを実行してみると、古いUNIX入門の本にあるように、訂正は「#」記号、破棄は「@」記号にバインドされているようです。
バックスペースなどはかなり基本的な画面制御文字だと思うんですが、テレタイプ端末も現役で動いていた当時の事情を考えると、ビデオ表示端末のような書き換え可能な表示媒体をもつデバイスにしかできない「バックスペース」ではなく、より多くのデバイスに可能な「#」による訂正の方が都合が良かったんでしょう。
実際にタイプした文字を訂正するには、以下のようにします。

la#s

これは「ls」を間違って「la」とタイプしてしまったので、「#」で直前の1文字を訂正して、正しい「s」をタイプしたところです。「la#s」でシェルには「ls」と解釈されます。また、

pqd##wd

「pwd」を「pqd」とタイポした場合、「##」で2文字訂正して、正しい「wd」をタイプすることにより、「pwd」と解釈されています。

現在の入力を全て破棄するには、

asdf@

のように、「@」記号を用いれば破棄されて新しいプロンプトに移ります。

終了

haltもshutdownコマンドも入っていない為、simh側から終了させます。ファイルシステムに変更を加えた場合、デバイスとの同期をとるためにsyncコマンドを打ちます。

sync;sync;sync

# 次に、C-eをタイプしてsimhのコンソールに移行します。

Simulation stopped, PC: 002306 (MOV (SP)+,177776)
sim> exit
Goodbye
host$

ここで、「exit」とタイプすると終了することができます。

man pageを見れるようにする

初期状態では/tmpが存在しない為、manページを表示しようとすると次のように怒られます。

man sh
Cannot create temp file.

そこで、/tmpを作成しておきましょう。

mkdir /tmp
chmod 777 /tmp
man sh

これでmanページが見れるようになりますが、ページャが入っていません。やはりビデオ表示端末向けのツールは、まだ充実していないようですね。

ユーザの追加

初期状態では、ユーザ情報は次のとおりになっています。

cat /etc/passwd
root:VwL97VCAx1Qhs:0:1::/:
daemon:x:1:1::/:
sys::2:2::/usr/sys:
bin::3:3::/bin:
uucp::4:4::/usr/lib/uucp:/usr/lib/uucico
dmr::7:3::/usr/dmr:

シャドウ・パスワードが登場する前なので、/etc/passwdに素直に(ハッシュ化された)パスワードが記載されていますね。7行目の「dmr」というユーザ名は、先日逝去されたUNIXの開発者デニス・リッチー博士の愛称です。実際に運用されていた頃からデフォルトで入っていたのかもしれませんが、そうでなくとも、フリーソフトとして公開される際に、彼に敬意を称して追加されたのかもしれません。

`echo dyama::8:3::/usr/dyama: >> /etc/passwd`

畏れ大くも、dmr博士の次にアカウントを追加してみました。さらにホームディレクトリも設定します。

cd /usr
mkdir dyama
chown dyama dyama
ls -l
total 11
drwxrwxr-x 3 bin 128 Sep 22 05:45 dict
drwxrwxr-x 2 dyama 32 Sep 22 06:18 dyama
drwxrwxr-x 5 bin 416 Sep 22 05:46 games
drwxrwxr-x 3 sys 496 Sep 22 05:42 include
drwxrwxr-x10 bin 528 Sep 22 05:43 lib
drwxrwxr-x11 bin 176 Sep 22 05:45 man
drwxrwxr-x 3 bin 208 Sep 22 05:46 mdec
drwxrwxr-x 2 bin 80 Sep 22 05:46 pub
drwxrwxr-x 6 root 96 Sep 22 05:45 spool
drwxrwxr-x13 root 208 Sep 22 05:42 src
#

当時のファイルシステム階層では、ユーザのホームディレクトリは/homeではなく、名前のとおり/usrに格納されていたと古い本で読みました。慣例に習って設置しました。

一度、C-dをタイプしてログアウトします。

#
login: dyama
$

「login:」と出てきたので、先ほど追加したユーザ名「dyama」をタイプします。パスワードを設定していない為、聞かれることなく一般ユーザプロンプト「$」が表示されました。
エミュレータ上のアカウントなのでこのままでもいいんですが、とりあえずパスワードを設定します。

$ passwd
Changing password for dyama
New password:
Retype new password:`

これでパスワードが変更されたはずです。試しに「hogehoge」と入力しています。

$ su
Password:
cat /etc/passwd | grep dyama
dyama:7N.C0KCSdg3aI:8:3::/usr/dyama:
#

パスワードカラムに、ハッシュ化されたパスワード文字列が追加されたことが分かります。

Cプログラムを書いてみる

ccが入っていますので、C言語でプログラムを組むこともできます。ビジュアル・エディターが入っていない為、catコマンドで標準入力からダイレクトにファイルを書いていきたいと思います。

先に述べましたとおり「#」記号が訂正にバインドされているので、まずは#を正しく入力できるように設定を変更してあげます。

$ stty erase ?
$ stty
speed 300 baud
erase = ‘?’; kill = ‘@’
even odd -nl echo -tabs
$

sttyコマンドで訂正を「#」から「?」に変更しました。続いてコーディングです。

$ cat > test.c
#include
int main()
{
printf(“hello, world\n”);
return 0;
}
$ cat test.c
#include
int main()
{
printf(“hello, world\n”);
return 0;
}
[/bash]

コーディングができたら、catで確認してみてください。訂正が「?」になっていれば、プリプロセッサの「#include」は正しく入力できているはずです。

$ stty erase #
$ stty
speed 300 baud
erase = ‘#’; kill = ‘@’
even odd -nl echo -tabs

忘れないうちに訂正を「#」に戻しておきます。
あとは、ccコマンドでコンパイルを行なって出力されたファイルを実行します。

$ cc test.c
$ ls
a.out
test.c
$ ./a.out
hello, world
$

上記のように「hello, world」が表示されたら成功です。

boostの正規表現ライブラリを使う

boostの正規表現ライブラリ boost::regex を使ったサンプルです。
boost::regex は以下のコマンドでインストールすることができます。

[bash]
$ sudo aptitude install libboost-regex-dev
[/bash]

サンプル regextest.cpp を準備します。これは正規表現リプレイサです。

[cpp]

include <iostream>

include <string>

include <boost/regex.hpp>

// usage: a.out pattern replace-to

using namespace std;

int main(int c, char** v)
{
if (c != 3) return 1;

boost::regex ex(v[1]); // 正規表現オブジェクト
char buf[256];         // 読み取り用のバッファ

while (cin.getline(buf, sizeof(buf)))
cout << boost::regex_replace(string(buf), ex, v[2]) << endl;

return 0;
}
[/cpp]

ビルドします。

[bash]
$ g++ -lboost_regex regextest.cpp
[/bash]

実行します。

[bash]

先頭の二文字を入れ替える

$ ls / | ./a.out ‘^(.)(.)’ ‘$2$1’
ibn
obot
edv
tec
ohme
niitrd.img
ilb
olst+found
nmt
pot
rpoc
orot
bsin
eslinux
rsv
yss
mtp
sur
avr
mvlinuz
[/bash]

簡単ですね。

タバコ用に空気清浄機を購入

部屋でタバコを吸う時は、基本的に窓を開けて吸っているのですが、ここのところ涼しくなってきたのもあり、思いきって空気清浄機を購入しようと思いました。冬になってくると、どうしても換気が悪くなってきて、室内でタバコを吸っていると煙たくて仕方ありません。自分が不快にならない程度でも、ディスプレイやキーボード、壁やカーテンなどがヤニに侵されていきます。ヤニは嫌だけど、タバコをやめるには時間がかかる…ということで、決心に至りました。マトモな空気清浄機を購入するのは今回が初めてです。

さて、さまざまな通販サイトで確認してみるも、いわゆる「タバコ専用」を謳ったものってなかなか少ないようですね。喫煙人口がまだまだ多い日本だと、需要もありそうなんですが、煙ほど濃い「悪い空気」はガッツリした機械じゃないと清浄できないんでしょうか。まあ、何十万円もする業務用の大型のものなら、タバコ専用のようなものもあるようですが、そこまでお金は出せません。

ということで、一般向けの低価格な空気清浄機で代用することになりました。マイナスイオンが出る、とか保湿機能がある、とか静音タイプだとか、いろんな機能を備えたものがありますが、「タバコの煙をガンガン吸うよ!パワフル!」というものはやっぱりありません。

どこかのレビューで「前面の全面に吸い込み口があるもの、急速清浄モードがあるものがタバコ用には良い」という情報があったのでそれを基に、AmazonでZOJIRUSHIの空気清浄機 PA-HA16-WBを購入してみました。据置き型の空気清浄機にしては安い価格帯のものだと思います。

自分はよくベッドの上でゴロゴロしながらラップトップを扱い、タバコを吸うので、枕元に設置してみました。ちょうど良い高さで吸引してくれます。空気清浄機の前面から20~30センチメートルまでタバコを近づけないと吸引してくれませんが、自宅に居る時の半分は空気清浄機の前だし、なかなか効果がありそうです。口から吐く煙も、意図的に空気清浄機に向けて拡散してしまわないように心掛けて使っています。また、空気の汚れ具合をセンサーで検知するモードがありますので、タバコを吸い始めたら自動的に急速清浄モードになってぐんぐん吸い込んでくれて結構助かります。

数万円台の空気清浄機でも、部屋ひとつ清浄するのに30分とか1時間とかかかるのが普通みたいなので、この値段でこれだけの働きをしてくれるっていうのは、個人的に満足でした。

茶の間用にもう一台購入するかもしれません。