FreeBSDの最近のブログ記事

SyntaxHighlighter 3.0

|

プログラム・コードを Web ページに掲載する際,コードを pre タグでマークアップするのが一般的ではないだろうか。SyntaxHighlighter を使うと,C/C++ や Java などの予約語をカラー表示したり,行番号を付加したり,重要な行を強調したりできる。これを使用しているプログラマのブログを目にするのは稀ではない。私も使ってみることにした。

いま現在の SyntaxHighlighter 最新バージョンは 3.0.83。ダウンロード・ページから syntaxhighlighter_3.1.0.83.zip を取得して,ローカル計算機で解凍する。解凍してできたディレクトリ下にある compass, scripts, styles, src のディレクトリを Web ドキュメントルート下にコピーすれば,使い始めることができる。ここでは DocumentRoot/sh にインストールしたものとする。

使い方を簡単に説明すると以下のとおりである。SyntaxHighlighter - Installation に書かれているので,きちんとした仕様が知りたい方はそちらを参照のこと。

  1. プログラム・コードを掲載したい HTML(以下「対象ページ」)のヘッダ部に shCore.jsshCore.css を読み込むよう指定する。
  2. 出力様式のタイプがテーマ Theme として管理されており,好みのものを選択して,そのスタイルシートを対象ページに指定する。一覧は SyntaxHighlighter - Themes にある。とくにこだわりがなければ標準 Theme: shThemeDefault.css を指定しておけばよい。私は Eclipse Theme について表示色等カスタマイズしたものを shThemeSite.css として保存して使うことにした。
  3. 対象ページで掲載したいプログラム・コードの言語に応じて SyntaxHighlighter の JavaScript を選択し,それらを対象ページに指定する。JavaScript の一覧は SyntaxHighlighter - Bundled Brushes にある。

上記までで対象ページのヘッダ部は以下のようなものとなる。ここではシェルスクリプト,XML(HTML 含む),Java,C/C++,Perl,JavaScript を扱うものとした。これは SyntaxHighlighter の出力例でもある。

  <link type="text/css" rel="stylesheet" href="/sh/styles/shCore.css" />
  <link type="text/css" rel="stylesheet" href="/sh/styles/shThemeDefault.css" />
  <script type="text/javascript" src="/sh/scripts/shCore.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushBash.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushXml.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushJava.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushCpp.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushPerl.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushJScript.js"></script>

プログラム・コードは pre タグでマークアップする。class オプションに SyntaxHighlighter のコンフィギュレーションを指定する。扱う言語種別,行番号表示や開始番号,タブのサイズ,ハイライトすべき行番号,ツールバー表示有無,等である。SyntaxHighlighter - Configuration に指定可能なオプションの説明がある。以下は,Perl 言語コード,開始行番号 1,タブ・サイズ 4,ハイライト行なし,の指定である。title に題名を設定できる。

<pre class="brush: perl; first-line: 1; tab-size: 4; highlight: null;" 
     title="xlstocsv.pl">

行番号を出力したくない場合,gutter: false; を指定する。個別に行をハイライトしたいなら,highlight: [2, 4, 6]; のようにすればよい。プログラム・コードのマークアップが済んだら,最後に以下の JavaScript コードを書いておく。おまじないのようなものである。同一ページ内にいくつも SyntaxHighlighter 使用 pre タグを書いていても,最後に一度だけ発行すればよい。

<script type="text/javascript">SyntaxHighlighter.all()</script>

昨日作成した Excel 一括 CSV 出力の Perl コードは,以下のように表示される。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Excel to CSV
# $Id: xlstocsv.pl 12 2012-02-05 16:57:06Z isao $
use strict;
use utf8;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::FmtJapan;
binmode STDOUT, ":utf8"; binmode STDERR, ":utf8";
($#ARGV < 0) && die "Usage: $0 xls-file\n";
my $fnm = $ARGV[0]; # Excel file name
my $ct = 0;         # 出力行数
# Excel object
my $xls = Spreadsheet::ParseExcel->new();
my $fmt = Spreadsheet::ParseExcel::FmtJapan->new(); # 日本語あり
my $bko = $xls->parse($fnm, $fmt); # Excel Book object
my @wsa = $bko->worksheets();      # Worksheet object 配列
utf8::decode($fnm);
# ワークシート毎の処理
foreach my $ws (@wsa) {
    my $wsnm  = $ws->{"Name"};
    my $mxrow = $ws->{"MaxRow"};
    my $mxcol = $ws->{"MaxCol"};
    print STDERR "*** $0 $fnm - sheet: $wsnm start.\n";
    if ((defined $mxrow) && (defined $mxcol)) {
        # 行毎の処理
        for (my $row = 0; $row <= $mxrow; $row++) {
            my $line = "";
            # 列毎の処理
            for (my $col = 0; $col <= $mxcol; $col++) {
                # セル値をセット
                my $cell = $ws->get_cell($row, $col);
                $line .= $cell->value() if (defined $cell);
                $line .= ",";
            }
            print "$line\n";
            $ct++;
        }
    }
}
print STDERR "*** $0 from $fnm wrote $ct lines\n";

ただし,オリジナルの shBrushPerl.js では # 文字があるとそれ以降がすべてコメントとみなされてしまうので,$#ARGV など配列末添字番号変数の表示がおかしくなるので,上掲では少し訂正したものを用いた。53 行目の正規表現の先頭に [^\$] を追加し,以下のとおりに訂正した。

this.regexList = [
	{ regex: new RegExp('[^\$]#[^!].*$', 'gm'),	css: 'comments' },
	{ regex: new RegExp('^\\s*#!.*$', 'gm'),	css: 'preprocessor' },

ちょっと使ってみて制約があった。XML のタグのように < > で囲まれたテキストがあるとき,開始タグに対して終了タグがないと,終了タグが最後に強制的に付加されてしまう仕様のようである。空要素タグ < /> の場合も例外ではない。たとえば C/C++ の #include <stdio.h> の行をそのまま記述すると,終了タグを探しても当然ないので,ソースコード表示の最後に </stdio.h> なるゴミが出てしまうのである。私はまだ SyntaxHighlighter を使い込んでいないのでなんとも判断できないが,< > については,これまでどおり実体参照指定(&lt; &gt;)で記述した方がよさそうである。

漢詩詩語集を処理するに際して,Excel ファイルを CSV に一括変換するツール「Excel 一括 CSV」だと,JIS X 0212 のいわゆる JIS 第三・第四水準の文字の出力が「?」に化けてしまった。これをなんとかしたいと思い別のツールをいろいろ漁ってみたが,これというものが見当たらなかった。UNIX 系システムでの Excel ファイルのテキスト変換では,xlhtml というプログラムが昔から有名であるが,これとて日本語の扱いに関しては不十分なところがある。この調査の過程で,Windows でなくても動作する Perl Excel ファイルハンドリング・モジュール Spreadsheet::ParseExcel の存在を知り,これを使って一括変換ツールを自作することにした。このモジュールは標準ではないので,コマンドラインから cpan -i Spreadsheet::ParseExcel としてインストールする。

Excel は古いバージョンで作成したものだと,ファイル内の日本語文字コードが Shift_JIS なのか CP932 なのか UCS2 なのかよくわからない。おまけに私の目的として,出力すべきファイルは UTF-8 エンコードでなければならない。Spreadsheet::ParseExcelSpreadsheet::ParseExcel::FmtJapan という日本語処理のための下位モジュールを備えている。これらを併用すれば,日本語文字コードに煩わされずにすみ,JIS X 0212 文字もうまく処理でき,かつ UTF-8 で出力できることがわかった。Spreadsheet::ParseExcel のマニュアル(perldoc Spreadsheet::ParseExcel で閲覧できる)に API 解説とサンプルコードがあるので,参照いただきたい。

今回作成した Excel ワークシート一括 CSV 変換プログラム xlstocsv.pl を以下に掲げる。こんなに短いコードでできてしまうのなら,最初から自分の手を動かすべきであった。同じ課題をもつ方は必ずいらっしゃると思うので,以下のコードをコピって活用いただきたい。もちろん無保証,無サポートである。使い方は,コマンドラインで xlstocsv.pl 対象Excelファイル とする。引数に Excel ファイルを一つ指定する。すべてのワークシートが単一 CSV テキストストリームとして STDOUT に書き出される。ファイルに格納したいのならリダイレクトする。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Excel to CSV
# $Id: xlstocsv.pl 12 2012-02-05 16:57:06Z isao $
use strict;
use utf8;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::FmtJapan;
binmode STDOUT, ":utf8"; binmode STDERR, ":utf8";
($#ARGV < 0) && die "Usage: $0 xls-file\n";
my $fnm = $ARGV[0]; # Excel file name
my $ct = 0;         # 出力行数
# Excel object
my $xls = Spreadsheet::ParseExcel->new();
my $fmt = Spreadsheet::ParseExcel::FmtJapan->new(); # 日本語あり
my $bko = $xls->parse($fnm, $fmt); # Excel Book object
my @wsa = $bko->worksheets();      # Worksheet object 配列
utf8::decode($fnm);
# ワークシート毎の処理
foreach my $ws (@wsa) {
    my $wsnm  = $ws->{"Name"};
    my $mxrow = $ws->{"MaxRow"};
    my $mxcol = $ws->{"MaxCol"};
    print STDERR "*** $0 $fnm - sheet: $wsnm start.\n";
    if ((defined $mxrow) && (defined $mxcol)) {
        # 行毎の処理
        for (my $row = 0; $row <= $mxrow; $row++) {
            my $line = "";
            # 列毎の処理
            for (my $col = 0; $col <= $mxcol; $col++) {
                # セル値をセット
                my $cell = $ws->get_cell($row, $col);
                $line .= $cell->value() if (defined $cell);
                $line .= ",";
            }
            print "$line\n";
            $ct++;
        }
    }
}
print STDERR "*** $0 from $fnm wrote $ct lines\n";

もしワークシート毎にファイルをばらしたいのなら,foreach my $ws (@wsa) のブロック(ワークシート単位のループ)部分の始めと終わりに独自ファイル処理(「ファイル名+シート名」でファイルハンドルを割り当てる等)を入れればよい。また,このコードは CSV をカンマ区切り形式とし,かつセルの内容に半角カンマが含まれない前提である。もし半角カンマが含まれてもよいようにするなら,得られたセル値を出力時にダブルクォーテーションマークで括る,等の処置を施していただきたい。

これで,一つの Excel ファイルに夥しいワークシートがあろうが,いちいち Excel を開いて CVS として保存しなくても,コマンドラインから一発で CSV 変換ができるようになった。おまけに FreeBSD でも Linux でも Mac OS X でも動くのである。もちろん Windows でも動くし,Excel がインストールされていなくても Excel ファイルを処理出来る,というメリットがある。

私の課題 — いただいた詩語集の処理に関しては,71 の Excel ファイルそれぞれに何十ものワークシートがあって,Excel をひとつひとつ開いてワークシート毎に CSV エクスポートするのはとてもやっていられないが,xlstocsv.pl を使えば次のコマンド操作だけで二字詩語,三字詩語の CSV(2c.csv, 3c.csv)をまとめることができた。

isolde [1001] % foreach i (2c-xls/*.xls)
foreach? set BS=`basename $i .xls`
foreach? (xlstocsv $i > 2c-$BS.csv) >& 2c-$BS.err
foreach? end
...
isolde [1002] % foreach i (3c-xls/*.xls)
foreach? set BS=`basename $i .xls`
foreach? (xlstocsv $i > 3c-$BS.csv) >& 3c-$BS.err
foreach? end
...
isolde [1003] % env LC_ALL=C sort 2c-*.csv | uniq > 2c.csv
isolde [1004] % env LC_ALL=C sort 3c-*.csv | uniq > 3c.csv

xlstocsv.pl は引数に Excel ファイルを一つしか指定できないが,複数の Excel ファイルを扱いたいなら,上の tcsh 操作のようにループで回せばよい。次のシェルスクリプトは,カレントディレクトリにあるすべての Excel ファイルを,「xls と同じファイルベース名+拡張子 csv」という名の CSV ファイルに書き出す。

#!/bin/sh
for i in *.xls
do
    BS=`basename $i .xls`
    xlstocsv.pl $BS.xls > $BS.csv
done

misima 漢詩・詩語集拡充

|

misima 漢詩作成支援の詩語検索機能で参照する詩語データベースを大幅に拡充した。ここのところ,この漢詩プログラム改造に取り組んでいた延長で,詩語のデータベース拡充のための元ネタがないか探していたら,今月 2 日,ある方から個人的に集めた詩語集をいただいた。入手した Microsoft Excel 形式のファイルを CSV に変換して行数を確認すると,なんと 8 万行もある。長年の労苦が偲ばれる素晴らしい成果をいただいた次第である。これは早速データベースにして,提供してくれた方に使ってもらわないとバチが当たる。昨夜と今日のお休み一日かけて,これに取り組んだのである。

いくつものシートに分けられた Excel データを CSV データに変換するのに,「Excel 一括 CSV」を使わせてもらった。妻の Windows 7 で変換したのち,Mac OS X にコピーして,ここからいくつかプログラムを書いて既存のデータベースにマージした。主な作業手順は以下のとおり。

  • 文字コードを UTF-8 に変換(nkf 使用)。
  • CSV データをフォーマット編集(新規作成ツール)
  • 平仄付加(既存ツール)
  • 詩語を旧字体変換して増幅(既存ツール)
  • 新旧両字体で検索してもヒットするよう韻字カラム調整(新規作成ツール)
  • 重複排除(既存ツール)
  • SQLite3 import
  • FreeBSD サーバ Tomcat 環境への辞書・プログラムのデプロイ(配備・インストール)
  • 読み,意味,出典も詩語検索の結果に出力する処理(JavaScript)も追加した。旧字体変換を入れたので,韻字の検索で新旧字体のどちらを指定してもヒットするようデータ構造,クエリを変えた。例えば,韻字が「仏」・平仄「○-●」の検索で,「銅佛」が出て来るようにした。
     

    20120204-sigo.png

    これまでの詩語データベースは,ネットで漁った『唐詩選』の漢詩データ 470 首程度を Perl HTML::TreeBuilder パーサで処理して二字,三字の詩語に分解し,この結果得た約 4000 語程度しか蓄積してなかった。これがいまや二字 33601 語,三字 53657 語,計 87258 語にまで拡張された。まだまだ使い勝手としては疑問が残るけれども,詩語ブラウザとしてはそれなりの規模が備わったのではないかと思う。

    今回作業で使用した「Excel 一括 CSV」ツールでは,残念ながら JIS 第三・第四水準の漢字が文字化けしてしまった。データベースにはこれらは含めなかった。これらを反映するのが次なる課題である。

    misima 漢詩 Tomcat, Log4j エラー

    |

    misima 漢詩作成支援の機能追加を終えて,Tomcat ログ,misimaKansiServlet ログを眺めていたら,気になるエラーが出ていた。システム自体は正常に動作しているので,別に構わないわけだけれども対処することにした。
     

    * * *

    一つ目のエラーは Tomcat-5.5(JSP / Servlet コンテナのデファクトスタンダード・ソフトウェアのちょっと古めのバージョン)/usr/local/tomcat55/logs/stdout.log に出ていたもの。

    log4j:WARN No appenders could be found for logger →(折返し。実際は 1 行)
        (org.apache.commons.digester.Digester.sax).
    log4j:WARN Please initialize the log4j system properly.
    

    二行目の initialize 云々でロガー(ログ管理システム)Log4j の初期化がうまく行っていないようである。misimaKansiServlet そのものは独自に Log4j のプロパティ・ファイルを読み出して Log4j を初期化している。つまり,以下のように,デプロイメントディスクリプタ web.xmlinit-param タグにログ・プロパティ・ファイルの場所を書いておき,misimaKansiServlet Java プログラムから参照して logger を初期化している。

    web.xml:

    <servlet>
      <servlet-name>misimaKansiServlet</servlet-name>
      <servlet-class>misimaKansiServlet</servlet-class>
      ...
      <!-- logging プロパティのパス -->
      <init-param>
        <param-name>logproperty</param-name>
        <param-value>
          /usr/local/tomcat5.5/webapps/misimakansiservlet/log4kansi.properties
        </param-value>
      </init-param>
    </servlet>
    

    misimaKansiServlet.java:

    public class misimaKansiServlet extends HttpServlet {
        /** log4j property */
        private static String prop = "log4kansi.properties";
        /** log4j logger */
        private static Logger mslog;
        ...
     
        /** 初期化 */
        public void init() throws ServletException 
        {   
            // web.xml デプロイメントディスクリプタから初期パラメータを取得する.
            String p = getInitParameter("logproperty");
            if (p != null) { prop = p; }
     
            // log file initialize.
            mslog = Logger.getLogger(misimaKansiServlet.class.getName());
            PropertyConfigurator.configure(prop);
            mslog.info("misimaKansiServlet initialize.");
            mslog.info("- log properties file: " + prop);
        }
    ...
    

    これで何でまだ初期化されていないなどと言われるのか。misimaKansiServlet のログは初期化されてきちんとロギングできるので,どうやら Log4j の親玉みたいなクラスがシステムとしてのロガーの初期化ができないということのようである。調べたら,このためのプロパティ・ファイルを Log4j がきちんと読めるようにしておけばよいとわかった。つまり,/usr/local/tomcat5.5/conf/log4j.properties に全体用プロパティ・ファイルが格納されているとして,Tomcat 起動時に参照する JAVA_OPTS 環境変数に以下を追加記述しておけばよい。

    -Dlog4j.configuration=file:///usr/local/tomcat5.5/conf/log4j.properties
    

    これで Tomcat を再起動すれば,エラーは出なくなった。私の FreeBSD 環境の場合 Tomcat 起動用スクリプトのなかで JAVA_OPTStomcat55_java_opts 環境変数から取得しているので,/etc/rc.conf に以下を追加した。

    tomcat55_java_opts="-Dlog4j.configuration=file:///usr/local/tomcat5.5/conf/log4j.properties"
    
     
    * * *

    二つ目のエラーは,misimaKansiServlet ログの DWR 関連メッセージである。

    12/01/30 18:24:18.079, ERROR, [TP-Processor6] org.directwebremoting.util.LogErrorHandler,
    42: Line=23 The content of element type "dwr" must match "(init?,allow?,signatures?)".
    

    文面からは何も原因を想定できない。DWR サイト内を検索したら,メーリングリストに同じエラーに遭遇した人の投稿とそれへの回答があり,難なく原因がわかった。misimaKansiServlet 用の DWR 定義ファイル dwr.xml の記述において,今回追加した Java クラスのために allow タグも追加したのだが,これがまずかった。このタグは複数書いてはいけない仕様だったのである(それでも追加プログラムが動いてくれるのが面白い)。よって,対策は一つの allow タグにすべて放り込む,というのでOKだった。

    <dwr>
      <!-- without allow, DWR isn't allowed to do anything -->
      <allow>
        <create creator="new" javascript="SigoTable">
          <param name="class" value="SigoTable" />
        </create>
        <convert converter="bean" match="SigoBean" />
      <!--
      </allow> 単一 allow にするため削除
      <allow>  同上
      -->
        <create creator="new" javascript="KanjiTable">
          <param name="class" value="KanjiTable" />
        </create>
        <convert converter="bean" match="KanjiBean" />
      </allow>
    </dwr>
    

    misima 漢詩作成支援・平仄音韻分析・詩語検索ツールに,旧字体変換機能と漢字検索機能を追加した。ただし,このツールは友人向けの限定公開であり,ユーザ ID,パスワードを入力しないとアクセスできないようになっている。

    今回の追加機能は,先日,自分で漢詩を書く際に本ツールを使ってみて,足りないと思った機能である。そのときは手元の仮想端末から SQL を叩いて漢字データベースを検索して,欲しい平仄・韻をもつ漢字を探す,などしていた。せっかくだから,これらを Web 上で出来るようにした。旧字体変換はすでにある misima 旧字・旧仮名遣い変換サーブレットを呼び出すようにしただけである。漢字検索機能は,詩語検索とまったく同じ方式である(「DWR with Java: misima 漢詩詩語検索」を参照)。DWR(Dynamic Web Remoting)ライブラリをベースに,Java Beans クラス(KanjiBean.java)と,漢字 DB を検索してそのクラス配列にデータをストアするメソッド(KanjiTable.java)の二本の短い Java プログラム,非同期通信ライブラリ DWR とユーザインタフェースを制御する一本の JavaScript(kanji.js),検索用 HTML(kanjisearch.html)をごそごそと書いた。DWR のおかげで面倒な Java サーブレットのコーディング,非同期通信 Ajax JavaScript のブラウザ依存コーディングから解放され,たったこれだけの一日作業で機能追加が出来た。以下,追加機能の使い方を簡単にメモしておく。

    メインの画面(図 1.)に「漢字検索」と「旧字体」のボタンがある。
     

    20120128-kansi-1.jpg

    図 1. メイン画面

    分析対象漢詩テキストを入力し,「旧字体」のボタンをクリックすると,入力した内容が旧字体に置き換えられる。図 2. はその実行前後を示している。


    20120128-kansi-2.jpg

    図 2. 旧字体変換

    「漢字検索」右横の「開く」ボタンをクリックすると,漢字検索用の別ウィンドウがオープンする(図 3.)。
     

    20120128-kansi-3.jpg

    図 3. 漢字検索ウィンドウ

    ここで「漢字」,「韻字」,「韻目」で検索が可能である。ある漢字の平仄,韻目を知りたいとき,「漢字」のテキストエリアに文字を指定する。複数指定することができる。「韻字」のエリアに漢字を入力して検索すると,当該漢字と同じ韻目の漢字の一覧が得られる。押韻文字を考えるときに役立つはずである。「韻目」は,韻目を指定して該当する漢字の一覧を検索するためのものである。韻目は「上平聲一東」のような複雑なものでありかつシステム内部において特殊な形式で管理している。この形式でユーザが入力するのは困難なので,韻目テキストエリアにカーソルが位置づけられるとメニューがポップアップし,ここに一覧された平水韻 106 項目から求める韻目を選択することにより,検索条件が設定されるようになっている。図 4. に韻目検索のポップアップメニューを示す。
     

    20120128-kansi-4.jpg

    図 4. 韻目ポップアップメニュー

    「漢字」,「韻字」,「韻目」の条件は1回の検索ではどれかひとつしか指定できない。複雑な条件で検索するために「SQL」条件を設けてある。検索キー:漢字 ji; 平仄 hs; 字韻1 in1; 字韻2 in2; 音読 yo; 訓読 yk; 備考 bk を使って,SQL where 句のなかに書くクエリ条件を記述する。select * from KANJITBL where query condition; の SQL 文のうち query condition 部分のみをテキストエリアに記述する。最後の ;(クエリ終了を示すセミコロン)も書いてはならない。これはエキスパート向けであり,基本的には使う場面はないと思う。

    検索結果には,漢字,平仄,韻目,備考が出力される。備考欄には,音読み,訓などの付加情報があったりなかったりする。出力例を図 5. に示す。
     

    20120128-kansi-5.jpg

    図 5. 漢字検索結果例

    ここで,平仄欄の は平字, は仄字, は平仄両韻字(意味によって平仄が違う)を示している。韻目欄の,例えば「hs:07:陽」というのは下平聲七陽を示している。hk: 上平聲,hs: 下平聲,sj: 仄上聲,sk: 仄去聲,sn: 仄入聲であり,コロンで区切られた数字と漢字一文字で韻目を表している。

    これで,漢詩の平仄・音韻規則チェック,詩語の検索,漢字の平仄・韻目検索が揃った。これからは詩語データベースの充実を図り,漢詩作成の有益なツールになるようにしたいものである。
     

    20120128-kansi-6.jpg

    図 6. misima 漢詩作成支援・平仄音韻分析・詩語検索・漢字検索

    新年のスパムメール

    |

    自宅サーバでメールサーバを運用し,DynDNS で取得したドメイン名でメールをやり取りしている。当然ながらスパムメール・チェッカ SpamAssassin やメール振分ソフト Procmail を稼働させて,メールボックス配信時にスパムメールを取り除けている。一般に,スパムと判断されたメールは,これを /dev/null 行きにする管理者が多いのではないだろうか。/dev/null は UNIX のスペシャルファイルのひとつで,これを出力先にすると削除と同じ操作となる,言わば宇宙空間のブラックホールのようなものである。

    しかし,私はスパムをブラックホール送りにしないで専用ディレクトリに入れている。もちろん,きちんとしたメールがスパムと誤判断される場合を考慮したからであるが,たまにスパムの文面をみて大笑いするためでもある。この際,FreeBSD サーバ上の GNU Emacs 環境の MUA Mew を使って閲覧する。Windows では Outlook Express などでプレビュー表示しただけで感染してしまうウィルスが知られているが,UNIX 上のテキストベースの Mew なら,いかがわしいスパムを閲覧してもウィルス感染する心配はあんまりない(だから Windows ユーザは私の真似をしてはいけない)。

    私はサイトのあちこちに自分のメールアドレスを晒しているので,ロボットに収集されて,いろんなところからスパムが私のところに来る。これらスパムは,かつては英語のものが圧倒的に多かったが(ほとんどが「あなたの dick を三倍に」,「Rolex 高級時計を格安で」に類するものであった),この二週間ほどの傾向では,日本語が約 50%,中国語が約 30% である。英語のものは 10% 程度である(米国は何か対スパムの法整備をしたのか?)。ウクライナ/ロシアからも 3% 程度ある(ウクライナはアタックも多く,ならず者計算機使いが多いと私はみている)。中国語が目立つようになったけれども,やっぱり懲りない奴らは日本語を話すのが多いらしい。エロサイト,「金持ちマダムのお相手しませんか」的出会いサイト,裏ビデオ取引への勧誘がほとんどである。サーバアタックは米国,ウクライナ,中国からが多かった(ちょっと前の調査による)のだけれども。もちろん,これは「私のところに来たもの」(しかも二週間のデータ)がベースなので,スパムメール事情の全体的傾向とは判断できない。

    年明け,こんなスパムが来た。「パックリおまんこ開けましておめこでしょう」。「あけおめコ」というのもあった。「あけましておめでとう,ことしもよろしく」というのを若い人たちは「あけおめことよろ」と略したりする。このスパムメール,そのなかに卑猥な言葉が隠れていることを利用していて,大笑いさせられた。このスマパー,きっと関西人に違いない。メールヘッダを確認すると,どの IP から送付されて来たかがわかる。この「あけましておめこでしょう」メールヘッダは,次のようなものだった。

    From info@027ab.cdefg.info  Thu Jan  5 14:19:11 2012
    Received: from 027ab.cdefg.info ([175.106.71.27])
    	by beatrice.yasuda.org (8.14.4/8.14.4) with SMTP id s055R94k001229
    	for isao@yasuda.homeip.net; Thu, 5 Jan 2012 14:19:10 +0900 (JST)
    	(envelope-from info@027ab.cdefg.info)
    Message-Id: <201201050519.s055R94k001229@beatrice.yasuda.org>
    MIME-Version: 1.0
    Content-Type:text/plain; charset="iso-2022-jp"
    Content-Transfer-Encoding: 7bit
    

    IP は 175.106.71.27(スパマーの IP アドレスは晒してやってもかまわない?)。人間ではなく機械が自動的にバラまいたスパムであることがわかる(当然といえば当然)。人間がパソコンから出すメールはたいていプロバイダのメールゲートを通過し,そのたびに Received: 行が追加されるので,ヘッダにはこれが必ず複数行ある。上記ヘッダには 1 行しかないので,MUA が直接私のメールサーバに接続したことがわかり,つまりスパム発送プログラムのしわざだと考えられるのである。じゃ,いったいどこが発信源か。whois コマンドで調べてみる。

    isolde:/Users/i-yasuda[1004] % whois 175.106.71.27
    ...
    inetnum:        175.106.64.0 - 175.106.127.255
    ...
    person:         jung bxung wxx
    address:        Gyexnggi-dx Gwxnsexn-dxng
    address:        1056-XX XX  JNDINFO.CX
    country:        KR
    phone:          +82-31-226-XXXX
    e-mail:         comnetxx@hanmir.xxx
    mnt-by:         MNT-KRNIC-AP
    changed:        hostmaster@nic.xr.kr
    source:         KRNIC
    ...
    

    内容は xxx などと少し改変し,情報量もはしょってある。発信源は韓国だったニダ。関西じゃありませんでした。このメール本文には,スパムとしては当然ながら,誘導したいエロサイト URL がしるされているが,この URL から引いた IP アドレスは米国のサイトのようである。この日本語スパマーは韓国,米国を隠れ蓑にして商売していやがったわけだ。

    と,ま,時折りこのようにスパムを眺めて楽しんでいるのであります。下品なネタですみませんでした。

    Apache IP denial configuration

    |

    Apache のアクセスログを時折チェックすると,様々なアタックがあることに気付く。公開 Web サーバを運用しているとこれはもうしようがない。GET //dbadmin/scripts/setup.php HTTP/1.1 などの要求(世に言う php アタック)が頻発していて,すべて 404 レスポンス(document not found)で弾かれている。404 で帰ってくれるならよいのだが,隠れたセキュリティホールを突かれてサーバが乗っ取られないとも限らない。面倒だが,これら前科のあるヤクザ・ホストの IP アドレスを収拾し,こいつらのネットワークから来る HTTP 要求はすべて拒否することにした。

    Apache には特定の IP アドレス,ネットワークからの要求を拒絶する機能が備わっている。あるドキュメントに対して deny from IP | CIDR を指定すればよい。ヤクザ・ホストの IP アドレスをログから抽出しここに指定しておけば,Apache は 403 レスポンス(forbidden)を返し,アクセスを拒絶してくれる。今回は IP アドレスそのものではなく,こいつらヤクザが属しているネットワーク全体からの要求を拒絶するように CIDR(ネットワークアドレス/マスクビット)を指定することにした。暴力団排除条例が施行されたことだし,「関係者」ともども皆お断りというわけだ。今日は,その設定変更レシピの備忘録を示しておく。ここで % は UNIX コマンドライン tcsh プロンプトを示している。

    1. 存在しないドキュメント参照記録の抽出
    2. まず 404 の履歴を Apache のアクセスログから抽出する。user, webserver は,それぞれ Web サーバへのログイン ID,ホスト名である。リモートシェルを使って Mac OS からオペレーションしている。
      % rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log > 404list.txt
      
    3. アタックの特定
    4. 404list.txt をテキストエディタで開いて内容を確認し,明らかにアタックだと思われるアクセス(.cgi, .dll, .php の拡張子をもつ,いかがわしいプログラムを実行するもの)を特定し,それ以外の行を削除して格納する。

    5. IP アドレス・リストの作成
    6. 編集済のログデータ 404list.txt から IP アドレスだけを抽出・編集し,重複を排除し,対象リスト 404ip.txt を作成する。
      % cat 404list.txt | cut -f 1 -d ' ' | grep -e '[0-9]' | sort | uniq > 404ip.txt
      
      私の場合,上記 1〜3 のオペレーションは以下のワンライナーでよさそうであった(→印は,次の行と一続きで入力することを示す)。
      % rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log  | \
      grep -v 'GET .*\.html ' | grep -e 'bbs\.html\+\|\/manager\|\.php\|\.dll\|\.asp\|\.pl\|→
      GET http\|\/user\/\|\/muieblackcat\|blackhats\|++++++*\|GET \/\/\|GET \/crossdomain\|→
      GET \/idle\/\|GET \/appConf' | \
      cut -f 1 -d ' ' | grep -e '[0-9]' | sort | uniq > 404ip.txt
      
    7. Whois 情報から Apache denial list の作成
    8. 404ip.txt 中の IP アドレスで whois を参照し,このアドレスが属するネットワーク CIDR や国コードを取得する。国コードはなくてもよいが,いったいどの国にヤクザが多いのか興味はありませんか? この処理は UNIX の whois コマンドを IP アドレス毎にひとつひとつ叩いて CIDR を調べ,Apache の定義文を作成すればよいけれども,IP の数が多いとやってられない。ネットワークの IP アドレス範囲から CIDR を計算しなければならない場合もあり,面倒だ。そこで私は,これらを全部自動的に行う Perl コード ipwhois.pl を書いた。ipwhois.pl コードを記事の最後に掲げておく。このプログラムは IP アドレス・リストを標準入力から読みとって,IP アドレス毎に whois を照会し,当該 IP の国コードと CIDR を取得あるいは計算し,指定出力ファイルに Apache の拒絶定義文(deny from CIDR)の形式で出力するとともに,標準エラー出力に国コード毎の集計データを出力する。CIDR が得られなかったら IP アドレス単体指定を行うものとしている。さて,私の自宅サーバログに基づく実行結果は以下のとおり。標準エラーの全出力を掲げておきます。ヤクザ IP・CIDR の一覧でもありますので,お使いいただいても構いません。
      % ipwhois.pl -o denylist < 404ip.txt 
      113.59.33.9     CN      113.59.0.0/17
      113.84.73.25    CN      113.64.0.0/11
      119.254.72.218  CN      119.254.0.0/15
      121.14.129.100  CN      121.8.0.0/13
      121.141.172.40  KR      121.128.0.0/11
      121.166.70.252  KR      121.160.0.0/11
      140.112.41.58   TW      140.112.0.0/16
      173.162.102.20  US      173.162.64.0/18
      173.245.70.17   US      173.245.70.0/24
      175.177.100.182 JP      175.177.100.0/24
      182.20.206.193  JP      182.20.128.0/17
      184.107.108.37  CA      184.107.108.0/24
      188.127.230.124 RU      188.127.224.0/20
      188.143.233.14  RU      188.143.232.0/23
      188.143.233.160 RU      188.143.232.0/23
      188.143.233.34  RU      188.143.232.0/23
      188.163.105.52  UA      188.163.0.0/17
      188.163.65.234  UA      188.163.0.0/17
      188.163.67.245  UA      188.163.0.0/17
      188.163.67.254  UA      188.163.0.0/17
      188.40.106.11   DE      188.40.0.0/16
      188.40.53.213   DE      188.40.0.0/16
      190.144.175.133 CO      190.144.175.133 (CIDR 190.144/14 not available)
      193.105.210.42  UA      193.105.210.0/24
      195.68.223.44   UA      195.68.222.0/23
      202.19.227.40   JP      202.19.227.40   (CIDR 2 not available)
      203.142.24.17   SG      203.142.24.0/24
      203.180.234.16  JP      203.180.234.0/24
      203.238.185.121 KR      203.238.176.0/20
      204.111.110.29  US      204.111.0.0/16
      204.9.123.122   US      204.9.120.0/21
      208.78.244.107  US      208.78.240.0/21
      209.144.30.98   US      209.144.30.0/24
      209.172.57.77   CA      209.172.32.0/19
      209.190.38.98   US      209.190.38.96/29
      209.236.115.222 US      209.236.112.0/20
      210.165.234.248 JP      210.165.128.0/17
      210.253.108.17  JP      210.253.96.0/19
      211.144.82.8    CN      211.144.64.0/19
      211.233.71.243  KR      211.233.64.0/20
      211.62.35.217   KR      211.62.32.0/21
      211.68.122.12   CN      211.68.120.0/22
      211.72.32.244   TW      211.72.0.0/17
      212.138.82.23   SA      212.138.64.0/18
      212.54.226.117  IT      212.54.224.0/19
      216.171.171.163 US      216.171.160.0/20
      219.136.241.165 CN      219.136.241.165 (CIDR 6 not available)
      221.117.61.178  JP      221.117.61.176/29
      222.186.43.119  CN      222.184.0.0/13
      31.128.142.199  RU      31.128.128.0/19
      31.184.236.13   UA      31.184.236.0/24
      31.184.236.33   UA      31.184.236.0/24
      31.184.236.34   UA      31.184.236.0/24
      31.184.236.36   UA      31.184.236.0/24
      31.184.238.71   RU      31.184.238.0/24
      46.109.177.176  LV      46.109.0.0/16
      46.116.43.68    IL      46.116.0.0/15
      46.21.154.66    US      46.21.144.0/20
      58.191.154.41   JP      58.191.154.40/29
      58.215.12.42    CN      58.208.0.0/12
      59.108.108.100  CN      59.108.0.0/16
      61.187.206.148  CN      61.187.206.148  (CIDR 4 not available)
      61.190.172.2    CN      61.190.0.0/16
      64.124.203.73   US      64.124.0.0/15
      66.87.2.132     US      66.87.0.0/16
      68.15.157.99    US      68.0.0.0/12
      68.58.54.214    US      68.32.0.0/11
      69.133.106.156  US      69.132.0.0/14
      69.26.37.39     US      69.26.32.0/19
      69.73.163.187   US      69.73.128.0/18
      72.29.84.41     US      72.29.64.0/19
      72.44.90.84     US      72.44.80.0/20
      74.217.148.71   US      74.217.0.0/16
      74.217.148.72   US      74.217.0.0/16
      77.207.110.229  FR      77.192.0.0/12
      77.247.181.165  NL      77.247.176.0/21
      77.52.112.197   UA      77.52.64.0/18
      77.92.224.110   GE      77.92.224.0/24
      77.92.233.198   GE      77.92.233.0/24
      81.170.143.96   SE      81.170.128.0/17
      81.25.46.137    BY      81.25.32.0/20
      82.193.117.23   UA      82.193.96.0/19
      85.192.15.82    RU      85.192.0.0/20
      85.88.195.35    IT      85.88.192.0/19
      86.101.224.233  HU      86.101.0.0/16
      86.127.192.137  RO      86.120.0.0/13
      88.40.179.242   IT      88.40.0.0/15
      89.107.227.210  TR      89.107.227.0/24
      89.252.58.228   UA      89.252.0.0/18
      89.28.124.238   MD      89.28.0.0/17
      89.97.190.170   IT      89.97.0.0/16
      91.135.235.139  GB      91.135.224.0/20
      91.207.6.154    UA      91.207.6.0/24
      91.207.6.174    UA      91.207.6.0/24
      91.207.6.82     UA      91.207.6.0/24
      91.214.45.239   LU      91.214.44.0/22
      91.224.160.126  NL      91.224.160.0/23
      91.224.160.127  NL      91.224.160.0/23
      91.224.161.127  NL      91.224.160.0/23
      91.226.165.164  RU      91.226.164.0/22
      92.113.10.162   UA      92.113.0.0/18
      93.182.36.82    RU      93.182.32.0/20
      93.84.116.216   BY      93.84.0.0/15
      94.24.152.61    RU      94.24.128.0/17
      Country list:
      GB = 1; GE = 2; NL = 4; TW = 2; LV = 1; BY = 2; SA = 1; KR = 5; RU = 10; MD = 1;
       TR = 1; JP = 8; CA = 2; SE = 1; CN = 12; DE = 2; LU = 1; CO = 1; RO = 1; SG = 1
      ; IT = 4; UA = 17; US = 21; IL = 1; HU = 1; FR = 1; 
      
    9. Apache denial list 重複削除
    10. ipwhois.pl が出力した denial list denylist から CIDR が重複するものを一つに纏める。
      % cat denylist | sort | uniq > denylist.txt
      
    11. Apache httpd.conf への反映
    12. 完成した denial list denylist.txt を Apache のコンフィグレーション(httpd.conf あるいは,これからインクルードされる定義ファイル)中のドキュメントルート Directory ディレクティブのところに,テキストエディタを使って反映する。以下はその例。このあと定義文をチェックし(apachectl -t),問題なければ,Apache を再起動(apachectl restart)して,作業は完了である。
      <VirtualHost *:80>
          ServerName yasuda.homeip.net
          DocumentRoot /usr/local/www/apache22/data
          <Directory />
              Options ExecCGI Includes FollowSymLinks
              AllowOverride None
          </Directory>
          <Directory "/usr/local/www/apache22/data">
              Options ExecCGI Includes FollowSymLinks
              AllowOverride AuthConfig
              Order allow,deny
              Allow from all
              # 以下 denial list 内容の CIDR を指定する
              deny from 113.59.0.0/17
              deny from 113.64.0.0/11
              deny from 119.254.0.0/15
              deny from 121.128.0.0/11
              deny from 121.160.0.0/11
              deny from 121.8.0.0/13
              deny from 140.112.0.0/16
              deny from 173.162.64.0/18
              deny from 173.245.70.0/24
              deny from 175.177.100.0/24
      ...
      

    さて,ipwhois.pl の実行結果から,我が家のサーバ・アタック杯ヤクザ国家の発表です。アタック元 IP の国コードとして多いのは,順に,一位:米国 21 ポイント,二位:ウクライナ 17 ポイント,三位:中国 12 ポイント,四位:ロシア 10 ポイント,五位:日本 8 ポイント,六位:韓国 5 ポイント,七位:オランダ,イタリア 4 ポイントでした。米国,中国,ロシアが上位に来るのは予想がつきましたが,ウクライナの二位は意外でした。もちろんこの国コードはサーバから見ての要求元なので,中国のヤクザが米国のプロクシ経由で攻撃したものは米国に分類されているはずである。

    ipwhois.pl コードは以下のとおり。コピペして使っていただいてもよい(無保証)。whois の項目を調べるための Perl モジュール Net::Whois::IPNet::CIDR が必要なので,動かすには事前に cpan コマンドでシステムにモジュールをインストールしておく必要がある。
     

    #!/usr/bin/perl -w
    # -*- coding: utf-8; mode: cperl; -*-
    # Get ip-route and generate deny list from whois info
    # $Id: ipwhois.pl 5 2011-12-11 12:18:13Z isao $
    # 2011 (c) isao yasuda.
     
    use strict;
    use Net::Whois::IP qw(whoisip_query);
    use Net::CIDR qw(:all);
    use Getopt::Std;    # command line processing
    use File::Basename; # get file basename
    binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8");
     
    # command line.
    my %opts = ('o' => "0");
    Getopt::Std::getopts('o:', \%opts) ||
        die "Usage: " . basename($0) . " -o denylist \< (stdin)\n";
    my %ccode; # 国コード集計カウンタ配列
    if ($opts{'o'}) { # deny list open
        open(DL, ">> $opts{'o'}") || die "$opts{'o'} open failed.\n";
    } else { # 指定がなければ標準出力
        open(DL, ">-");
    }
    # IP 毎の処理
    while (<STDIN>) {
        chomp($_); $_ =~ s/\s+//g;
        # Whois 情報取得
        my $ip = $_;
        my $response = whoisip_query($ip);
        # 国コード取得
        my $country;
        if ($response->{country}) {
            $country = $response->{country};
        } else {
            $country = $response->{Country};
        }
        $country =~ s/\s+//g; $country =~ tr/a-z/A-Z/;
        # CIDR 取得 なければ IP をセット
        my $cidr; my $comm = "";
        if ($response->{route}) {
            $cidr = $response->{route};
        } else {
            if ($response->{CIDR}) {
                $cidr = $response->{CIDR};
            } else { # IP 範囲から CIDR を計算
                my $range = $response->{inetnum}; $range =~ s/\s+//g;
                $cidr = Net::CIDR::range2cidr($range);
                unless ($cidr =~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+$/) {
                    $comm = "\t(CIDR $cidr not available)";
                    $cidr = $ip;
                }
            }
        }
        # 複数 CIDR が得られた場合はじめの一つのみにする
        my @acidr = split(/,/, $cidr);
        if ($#acidr) {
            $cidr = $acidr[0]; $cidr =~ s/\s+//g;
            $comm = "\t(Some CIDRs found. Selected one)";
        }
        # 端末出力
        print STDERR "$ip\t$country\t$cidr$comm\n";
        # Apache 用 deny list 出力
        print DL "deny from $cidr\n";
        # 国コード集計
        if (exists($ccode{$country})) {
            $ccode{$country} += 1;
        } else {
            $ccode{$country} = 1;
        }
    }
     
    # 国コード集計出力.
    print STDERR "Country list:\n";
    foreach (keys %ccode) { print STDERR "$_ = $ccode{$_}; "; }
    print STDERR "\n";
    close(DL);
     
    
     

    追記:

    こんなことをやっていたために,クラブワールドカップ・柏レイソル vs 北中米カリブ海代表モンテレイの試合を見忘れてしまった。柏レイソルが PK で勝利した。すげぇ。ベスト 4 進出。次はブラジル王者サントスとの対戦。楽しみじゃ。
     

    12.12 追記:

    whois から複数の CIDR が返却される場合があるため,その場合一つだけ採用するよう ipwhois.pl を訂正した。

    麻雀点数集計プログラム

    |

    サッカー五輪予選を観ながら,Web 無料麻雀ゲームをした。われらが日本代表が一生懸命アウェイで頑張っているというのに,観ている方はなんと暢気なものか。半荘を 10 回もやり,珍しく一人勝ち。
     

    20111120-majan.jpg
     

    バブル時代の高レート勝負だったらいったいいくら稼いだのかちょっと知りたくなって,プログラムを書いて計算してみた。プログラムは mjcalc。Perl である。

    mjcalc -u [ウマ] -b [ビンタ] -k [レート] < 点数データ
    
    ここで,[ウマ] は "数字1-数字2" の形式で指定し,"数字1" は 3 位から 2 位への,"数字2" は 4 位から 1 位へのウマとなる。省略すると 10-20 が仮定される。[ビンタ], [レート] は数値を千単位で指定する。省略すると,それぞれ 100, 2 を仮定する。点数データは空白文字区切りの 4 つの整数値である。

    計算の考え方は次のとおり。25000 点持ちの 30000 点返し。オカは 20000 点とする。入力データは,1000 点 1 ポイントとして換算された各プレイヤの点数が空白文字で区切られているものととする。入力の点数を合計し,0 の場合はすでにウマ,オカが反映されているものとしてレート計算のみ行う。入力点数の合計が 100 の場合は,半荘終了時の持点として,指定されたウマ,オカを計算に反映する。指定されたビンタ,レートで金額計算を行う。ビンタとは下位のプレイヤが上位者に支払う差ウマのことで,ビンタ 10 万の取り決めならば,上位者に 10 万,自分が配給原点以下の場合,配給原点以上の上位者にはその倍額の 20 万を支払わなければならない。独り沈みの 4 位者ならば 60 万のマイナスというわけである。

    さて,mjcalc で計算してみる。デカリャンピン (1000 点 2 千),ビンタ 10 万の気違いレート。データは上に示した 10 回のデータ (point.txt) である。この Web 麻雀ゲームの集計値はすでに 10-20 のウマ,20 ポイントのオカ,30 ポイントとの差分が集計済みである。

     
    isolde:/Users/i-yasuda/src/tools[1125] % cat point.txt 
    60      -20     2       -42
    18      -21     -52     55
    63      9       -20     -52
    52      -21     3       -34
    11      -16     42      -37
    -29     67      6       -44
    10      -32     -21     43
    63      -21     2       -44
    40      5       -28     -17
    70      -30     -41     1
    isolde:/Users/i-yasuda/src/tools[1126] % ./mjcalc -u 10-20 -b 100 -k 2 < point.txt 
    ウマ1: 10; ウマ2: 20; オカ: 20(以上pt); ビンタ: 100(千円); レート: 2(千円/1pt)
    ----------------------------------------------------------------------
      Input Points     ;  Calculated Points ;   Amount of Money (k-en)   ;
       A    B    C    D;    A    B    C    D;      A      B      C      D;
    ----------------------------------------------------------------------
     +60  -20   +2  -42;  +60  -20   +2  -42;   +720   -240     +4   -484;
     +18  -21  -52  +55;  +18  -21  -52  +55;   +336   -342   -604   +610;
     +63   +9  -20  -52;  +63   +9  -20  -52;   +626   +318   -340   -604;
     +52  -21   +3  -34;  +52  -21   +3  -34;   +704   -242     +6   -468;
     +11  -16  +42  -37;  +11  -16  +42  -37;   +322   -332   +584   -574;
     -29  +67   +6  -44;  -29  +67   +6  -44;   -358   +634   +312   -588;
     +10  -32  -21  +43;  +10  -32  -21  +43;   +320   -564   -342   +586;
     +63  -21   +2  -44;  +63  -21   +2  -44;   +726   -242     +4   -488;
     +40   +5  -28  -17;  +40   +5  -28  -17;   +580   +310   -556   -334;
     +70  -30  -41   +1;  +70  -30  -41   +1;   +740   -260   -482     +2;
    ----------------------------------------------------------------------
    +358  -80 -107 -171; +358  -80 -107 -171;  +4716   -960  -1414  -2342;
     
    

    うん。半荘 10 回で 471.6 万を獲得したというわけである。でも,なんでこんな意味のないことをやっているのか。で,プログラムを書きながら計算の確認のため,やっぱりこの麻雀ゲームをやっていたら,なんと四暗刻を,しかもピンズ一色・嶺上開花(意味無し)でツモってしまいました。
     

    20111124-suanko.jpg
     

    一応,プログラム mjcalc のコードを掲げておきます。ヒマなときにこんなことしかしない,私のような方のために。

     
    #!/usr/bin/perl -w
    # -*- coding: utf-8; mode: cperl; -*-
    #  Mahjong Points Calculator
    #  -------------------------
    #  Usage: mjcalc -u 10-20 -b 100 -k 2 < data
    #    -u 10-20: ウマ 10-20 (ワンツー)
    #    -b 100: ビンタ 100K円
    #    -k 2: レート 1000点 2K円
    #  2011(c) isao yasuda.
     
    use strict;
    use utf8;
    use Getopt::Std;    # command line processing
    use File::Basename; # get file basename
    binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8");
     
    # command line
    my %opts = ('u' => "0", 'b' => 0, 'k' => 0);
    Getopt::Std::getopts('u:b:k:', \%opts) ||
        die "Usage: " . basename($0) .
        " [ -u uma-range -b binta-k -k k-en ] \< (stdin)\n";
    my $umai1 = 10; my $umai2 = 20; my $okai = 20;
    my $uma1 = 0; my $uma2 = 0; my $oka = 20; my $kaesi = 30;
    my $binta = 100; $binta = $opts{'b'} if ($opts{'b'});
    my $krate = 2;   $krate = $opts{'k'} if ($opts{'k'});
    if ($opts{'u'}) {
        unless ($opts{'u'} =~ /^[0-9]+-[0-9]+$/) {
            die "-u operand invalid.\n";
        }
        ($umai1, $umai2) = split(/-/, $opts{'u'}, 2);
        $uma1 = $umai1; $uma2 = $umai2;
    }
    if (($uma1 == 0) && ($uma2 == 0)) {
        $oka = 0; $kaesi = 0;
    }
    if ($oka != 0) {
        print STDERR "ウマ1: $uma1; ウマ2: $uma2; オカ: $oka(以上pt); ";
    }
    print STDERR "ビンタ: $binta(千円); レート: $krate(千円/1pt)\n";
    my %totalpt = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # ポイント総計
    my %totalmn = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # 金額総計
    my %totalin = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # 入力ポイント総計
    print
        "----------------------------------------------------------------------\n";
    print
        "  Input Points     ;  Calculated Points ;   Amount of Money (k-en)   ;\n";
    print
        "   A    B    C    D;    A    B    C    D;      A      B      C      D;\n";
    print
        "----------------------------------------------------------------------\n";
     
    # Main
    while (<STDIN>) {
        utf8::decode($_); chomp($_);
        my @tensu = split(/\s+/, $_, 4);
        my $sowa = $tensu[0] + $tensu[1] + $tensu[2] + $tensu[3];
        unless (($sowa == 100) || ($sowa == 0)) {
            print STDERR "点数総和 $sowa は矛盾します。無視します。\n";
            next;
        } else {
            if ($sowa == 100) { # 持ち点
                $oka = $okai; $uma1 = $umai1; $uma2 = $umai2; $kaesi = 30;
            } else {            # すでにオカ・ウマ計算済
                $oka = $uma1 = $uma2 = $kaesi = 0;
            }
        }
        # プレーヤ-点数 配列
        my %plpt = ('A'=>$tensu[0],'B'=>$tensu[1],'C'=>$tensu[2],'D'=>$tensu[3]);
        # input print
        printf("%+4d %+4d %+4d %+4d; ",
               $plpt{'A'}, $plpt{'B'}, $plpt{'C'}, $plpt{'D'});
        $totalin{'A'} += $tensu[0];
        $totalin{'B'} += $tensu[1];
        $totalin{'C'} += $tensu[2];
        $totalin{'D'} += $tensu[3];
        my @ptord; # 点数昇順リスト (last, pt4, pos3, pt3, .., top, pt1)
        foreach my $key (sort { $plpt{$a} <=> $plpt{$b} } keys %plpt) {
            push(@ptord, ($key, $plpt{$key}));
        }
        # ビンタ計算
        my @bbuff = (0, 0, 0, 0); # 点数やり取りバッファ
        # 4
        if ((($sowa == 100) && ($ptord[1] < 25)) ||
            (($sowa == 0) && (($ptord[1] + $umai2) < -5))) {
            # to 3
            if ((($sowa == 100) && ($ptord[3] < 25)) ||
                (($sowa == 0) && (($ptord[3] + $umai1) < -5))) {
                $bbuff[1] -= $binta; $bbuff[3] += $binta;
            } else {
                $bbuff[1] -= ($binta * 2); $bbuff[3] += ($binta * 2);
            }
            # to 2
            if ((($sowa == 100) && ($ptord[5] < 25)) ||
                (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
                $bbuff[1] -= $binta; $bbuff[5] += $binta;
            } else {
                $bbuff[1] -= ($binta * 2); $bbuff[5] += ($binta * 2);
            }
            # to 1
            if ((($sowa == 100) && ($ptord[7] < 25)) ||
                (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
                $bbuff[1] -= $binta; $bbuff[7] += $binta;
            } else {
                $bbuff[1] -= ($binta * 2); $bbuff[7] += ($binta * 2);
            }
        } else {
            # to 3
            $bbuff[1] -= $binta; $bbuff[3] += $binta;
            # to 2
            $bbuff[1] -= $binta; $bbuff[5] += $binta;
            # to 1
            $bbuff[1] -= $binta; $bbuff[7] += $binta;
        }
        # 3
        if ((($sowa == 100) && ($ptord[3] < 25)) ||
            (($sowa == 0) && (($ptord[3] + $umai1) < -5))) {
            # to 2
            if ((($sowa == 100) && ($ptord[5] < 25)) ||
                (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
                $bbuff[3] -= $binta; $bbuff[5] += $binta;
            } else {
                $bbuff[3] -= ($binta * 2); $bbuff[5] += ($binta * 2);
            }
            # to 1
            if ((($sowa == 100) && ($ptord[7] < 25)) ||
                (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
                $bbuff[3] -= $binta; $bbuff[7] += $binta;
            } else {
                $bbuff[3] -= ($binta * 2); $bbuff[7] += ($binta * 2);
            }
        } else {
            # to 2
            $bbuff[3] -= $binta; $bbuff[5] += $binta;
            # to 1
            $bbuff[3] -= $binta; $bbuff[7] += $binta;
        }
        # 2
        if ((($sowa == 100) && ($ptord[5] < 25)) ||
            (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
            # to 1
            if ((($sowa == 100) && ($ptord[7] < 25)) ||
                (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
                $bbuff[5] -= $binta; $bbuff[7] += $binta;
            } else {
                $bbuff[5] -= ($binta * 2); $bbuff[7] += ($binta * 2);
            }
        } else {
            # to 1
            $bbuff[5] -= $binta; $bbuff[7] += $binta;
        }
        # ポイント,ビンタ,レート計算
        # プレーヤ-点数 配列
        my %ptrslt = ($ptord[0]=>0, $ptord[2]=>0, $ptord[4]=>0, $ptord[6]=>0);
        # 4
        $ptord[1] -= $uma2; $ptord[1] -= $kaesi;
        $ptrslt{$ptord[0]} = $ptord[1];
        $ptord[1] = $ptord[1] * $krate; $ptord[1] += $bbuff[1];
        # 3
        $ptord[3] -= $uma1; $ptord[3] -= $kaesi;
        $ptrslt{$ptord[2]} = $ptord[3];
        $ptord[3] = $ptord[3] * $krate; $ptord[3] += $bbuff[3];
        # 2
        $ptord[5] += $uma1; $ptord[5] -= $kaesi;
        $ptrslt{$ptord[4]} = $ptord[5];
        $ptord[5] = $ptord[5] * $krate; $ptord[5] += $bbuff[5];
        # 1
        $ptord[7] += $uma2; $ptord[7] -= $kaesi; $ptord[7] += $oka;
        $ptrslt{$ptord[6]} = $ptord[7];
        $ptord[7] = $ptord[7] * $krate; $ptord[7] += $bbuff[7];
     
        # プレーヤ-金額 配列
        my %mnrslt = ($ptord[0] => $ptord[1], $ptord[2] => $ptord[3],
                      $ptord[4] => $ptord[5], $ptord[6] => $ptord[7]);
        printf("%+4d %+4d %+4d %+4d; ",
               $ptrslt{'A'}, $ptrslt{'B'}, $ptrslt{'C'}, $ptrslt{'D'});
        printf("%+6d %+6d %+6d %+6d;\n",
               $mnrslt{'A'}, $mnrslt{'B'}, $mnrslt{'C'},$mnrslt{'D'});
        foreach my $key (keys %ptrslt) {
            $totalpt{$key} += $ptrslt{$key}; $totalmn{$key} += $mnrslt{$key};
        }
    }
     
    # 総計出力
    print
        "----------------------------------------------------------------------\n";
    printf("%+4d %+4d %+4d %+4d; ",
           $totalin{'A'}, $totalin{'B'}, $totalin{'C'}, $totalin{'D'});
    printf("%+4d %+4d %+4d %+4d; ",
           $totalpt{'A'}, $totalpt{'B'}, $totalpt{'C'}, $totalpt{'D'});
    printf("%+6d %+6d %+6d %+6d;\n",
           $totalmn{'A'}, $totalmn{'B'}, $totalmn{'C'}, $totalmn{'D'});
     
    

    ※ 11.27 付記
    最初の版には,ウキの判定とビンタ計算のバグがありました。訂正しました。

    あと一点。同点者がいることを考慮してません。一局目の一巡目でいきなり役満放銃で一発ハコ一人沈みみたいな場合でない限り,こんなことはなかろうと思い,無視しました。

    ハガキ宛名差込印刷

    |

    あと少しすればもう年末。年賀状を書かなければならない時期が来る。住所録から LaTeX を用いてハガキ宛名印刷用 PDF を生成するツールを作ってみた。これは昔 UNIX 系の雑誌にあった記事の工夫なので,ネットで探せば同様のツールが見つかるかも知れないが,私自身の好みでレイアウトをカスタマイズできるよう,スクラッチから(すべてを一から)自作してみた。mkpostaddr-201111102344.tar.gz アーカイブを掲載しておくので,関心のある方はダウンロードしてお使いください。無保証です。

    処理方式は次のとおり。住所録 CSV ファイル(address.csv),LaTeX プリアンブル・ファイル(preamble.tex),ハガキ差込フォーム・ファイル(form.tex),ハガキレイアウト定義ファイル(post.conf)を読込んで,住所録の宛名の数だけハガキ宛名組版の LaTeX ソースコードを出力する。この際,日本語は OTF パッケージを用いて Adobe-Japan1-6 コードの広範囲の漢字を出力出来るようにする。この処理は Perl スクリプト mkpostaddr で行う。出来上がった TeX ファイルを eplatex 及び dvipdfmx で処理して PDF を生成する。これらのファイルはすべて UTF-8 エンコーディングでなければならない。サンプルの住所録 CSV ファイルで組版した結果 PDF 画像を以下に示す。
     

    20111110-hagaki.jpg
     

    住所録 CSV ファイルは,項目をカンマ区切りで記述した,いわゆる CSV ファイルである。このツールでは,1:宛名-姓, 2:宛名-名, 3:宛先郵便番号, 4:宛先住所(番地まで), 5:宛先住所(ビル,マンション情報), 6:会社名, 7:部署名, 8:宛名連名-姓, 9:宛名連名-名 の 9 項目からなるデータを前提としている。5 〜 9 は無ければ空データもしくは空白としておいてよいが,カンマは必ず全部で 8 必要である。Microsoft Excel などで住所録を作成し,区切り文字カンマ,文字コード UTF-8 指定で CSV 出力すればよい。このデータはハガキ差込フォーム・ファイルで記述された所定の変数位置に埋め込まれ,ハガキ宛名 LaTeX コードとなる。郵便番号の出力位置と差出人情報をハガキレイアウト定義ファイルに書いておく。郵便番号の出力位置については,日本郵便社お年玉年賀ハガキ向けにすでに調整してある。掲載ファイルにはサンプルが記述されているが,差出人は変更するか,不必要なら空にしておく。

    住所に英文字以外の外国語,たとえばロシア語を使いたい場合は,{\selectlanguage{russian}キリル文字} と書いておけばよい。両端のブレースは必須である。Babel パッケージをプリアンブル・ファイルに宣言してあり,仏・独・露語が使用可能である。もちろん利用者で追加・訂正してよい。

    少し長大になるが,各ファイル内容を掲載しておく。
     

    住所録 CSV ファイル(サンプル)

    あくまでサンプル。必ず,自分用に CSV 形式で作成してください。

    # -*- coding: utf-8; mode: text; -*-
    # 住所録CSVファイル(サンプル)
    # 1:宛名姓,2:宛名名,3:郵便番号,4:住所1,5:住所2(建物),
    #     6:会社名,7:部署名,8:宛名(連名)姓,9:宛名(連名)名
    \CID{13706}田,太郎,1020082,東京都千代田区一番町一—二—三,一番ビルヂング地下一階,株式会社 ヨシダ商事,営業部,,花子
    鈴木,花子,1020072,東京都千代田区飯田橋四—五—六,,,,,
    高橋,一郎,2100808,神奈川県川崎市川崎区旭町一—二三—四,大日本マンション一〇一号,,,,\hspace*{.5zh}桜\hspace*{.5zh}
    
     

    ハガキ差込フォーム・ファイル

    LaTeX コードにおいて住所録の数だけ繰返し出力される宛名印字頁整形用の雛形である。:数字: の部分が,住所録 CSV ファイルの情報で書換えられる。印字の位置,文字の大きさなど,気に入らなければ適宜修正ください。基本的には弄る必要はない。

    % -*- coding: utf-8; mode: latex; -*-
    % ハガキ差込フォーム・ファイル(TeX form 原稿)
    % $Id: form.tex 9 2011-11-10 13:41:03Z isao $
    % 2011 (c) isao yasuda.
    \begin{picture}(100,148)(3,3)
     
        %% 宛先郵便番号
        \Huge
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:1:}}%第1桁
        \advance\cntTOX by \cntTOW
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:2:}}%第2桁
        \advance\cntTOX by \cntTOW
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:3:}}%第3桁
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:4:}}%第4桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:5:}}%第5桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:6:}}%第6桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:7:}}%第7桁
     
        %% 住所
        \put(70,30){%
          \makebox(20,94)[rt]{%
          %\framebox(20,94)[rt]{%
            \begin{minipage}<t>[t]{94mm}
                \Large
                :8:\\%住所1
                \vspace*{1mm}%
                \hspace*{5mm}%
                :9:%住所2
            \end{minipage}
          }%
        }%
     
        %% 会社名・部書名・連名
        \put(45,30){%
          \makebox(30,88)[rt]{%
          %\framebox(30,88)[rt]{%
            \begin{minipage}<t>[t]{88mm}
                \hspace*{3mm}%
                \vspace*{-7mm}%
                {\large :10:\\}%会社名
                \hspace*{7mm}%
                \vspace*{-3mm}%
                {\large :11:\\}%部署名
                \vspace*{-3mm}%
                \def\lastname{:00:}%
                \settowidth{\namelen}{\lastname}\addtolength{\namelen}{1zw}%
                \makebox[\namelen][l]{\lastname}:01:~様\\%宛名
                %\hspace*{20mm}%
                \makebox[\namelen][l]{:12:}%
                :13:%連名
            \end{minipage}
          }%
        }%
     
        %% 差出人住所・氏名
        \put(10,32){%
          \makebox(30,58)[rt]{%
          %\framebox(30,58)[rt]{%
            \small
            \begin{minipage}<t>[t]{58mm}
              {\large :20:}\par%住所1
              \vspace*{3mm}%
              {\Large
              \hspace*{5mm}%
              \makebox[3.5zw][l]{:21:}\qquad :22:\\[2pt]%差出人姓名
              \hspace*{5mm}%
              \makebox[3.5zw][l]{:23:}\qquad :24:}\par%連名姓名
              \vspace*{3mm}%
              \hfill{\large :25:}%
            \end{minipage}
          }%
        }%
     
        %%% 差出人郵便番号
        \large
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:30:}}%第1桁
        \advance\cntFOX by \cntFOW
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:31:}}%第2桁
        \advance\cntFOX by \cntFOW
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:32:}}%第3桁
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:33:}}%第4桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:34:}}%第5桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:35:}}%第6桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:36:}}%第7桁
     
    \end{picture}
    
     

    ハガキレイアウト定義ファイル(サンプル)

    Perl プログラムの変数定義の形式で記述する。差出人情報は必ず修正しなければならない。宛名面に差出人情報を印字したくない場合は,ごっそり削除すればよい。郵便番号を出力する位置は,ミリ単位,整数で指定しないといけない。すでに日本郵便社お年玉年賀ハガキ向けに私が調整してあるので,修正する必要はないと思う。

    # -*- coding: utf-8; mode: cperl -*-
    # ハガキレイアウト定義ファイル
    # $Id: post.conf 9 2011-11-10 13:41:03Z isao $
    # 2011 (c) isao yasuda.
    # 宛先郵便番号
    $POSTNUMT1POSX =  46;  # 1桁目X位置
    $POSTNUMT1POSY = 131;  # 1-3桁目Y位置
    $POSTNUMT1WIDTH =  7;  # 1-3桁目送り幅
    $POSTNUMT2POSX =  67;  # 4桁目X位置
    $POSTNUMT2POSY = 131;  # 4-7桁目Y位置
    $POSTNUMT2WIDTH =  7;  # 4-7桁目送り幅
    # 差出人郵便番号
    $POSTNUMF1POSX =  10;  # 1桁目X位置
    $POSTNUMF1POSY =  26;  # 1-3桁目Y位置
    $POSTNUMF1WIDTH =  4;  # 1-3桁目送り幅
    $POSTNUMF2POSX =  23;  # 4桁目X位置
    $POSTNUMF2POSY =  26;  # 4-7桁目Y位置
    $POSTNUMF2WIDTH =  4;  # 4-7桁目送り幅
    # 差出人
    $FROMADR = '横浜市港北区綱島一—二—三'; # 住所
    $FROMPCD = '1234567';   # 郵便番号
    $FROMLN1 = '田 中';     # 姓
    $FROMFN1 = '達夫・律子';  # 名
    $FROMLN2 = ' ';        # 連名・性
    $FROMFN2 = '一郎・次子'; # 連名・名
    $FROMOTH = '012-345-6789'; # その他(下寄せ)
    
     

    LaTeX プリアンブル・ファイル

    生成されるハガキ宛名印刷 LaTeX コードの先頭に出力されるコードである。もしフォントなどを変更したい場合は,パッケージ,デフォルト・ローマン書体の指定を追加すればよい。Babel パッケージが指定されているが,外国語が不要なら削ってもよい。

    % -*- coding: utf-8; mode: latex; -*-
    % LaTeX プリアンブル・ファイル(TeX preamble 原稿)
    % $Id: preamble.tex 6 2011-11-10 01:44:50Z isao $
    % 2011 (c) isao yasuda.
    \documentclass{jsarticle}
    \special{papersize=100mm,148mm}%はがき大きさページサイズ
    \usepackage[T2A,T1]{fontenc}%
    \usepackage[utf8x]{inputenc}%
    \usepackage{plext}%pTeX縦組ツール
    \usepackage[russian,english,french,german,japanese]{babel}%多国語
    \usepackage[deluxe,expert,multi]{otf}%
    \setlength{\textwidth}{100mm}%ハガキ幅
    \setlength{\textheight}{148mm}%ハガキ高
    \setlength{\oddsidemargin}{-25.4mm}%左横マージン0
    \setlength{\topmargin}{-25.4mm}%上マージン0
    \setlength{\headheight}{0mm}%
    \setlength{\headsep}{0mm}%
    \setlength{\unitlength}{1mm}%picture環境単位mm
    \pagestyle{empty}%ページ番号なし
    \newlength{\namelen}%宛名幅用
    \parindent=0pt\relax
    
     

    mkpostaddr はがき印刷用住所差込 Perl コード

    Perl プログラム。現在の Perl 処理系ならモジュール追加なしに実行できるはずである。よほど印刷様式が気に入らない限り,修正は不要のはずである。

    #!/usr/bin/perl
    # -*- coding: utf-8; mode: cperl; -*-
    #
    #  mkpostaddr はがき印刷用住所差込
    #  $Id: mkpostaddr 9 2011-11-10 13:41:03Z isao $
    #  2011 (C) isao yasuda, All Rights Reserved.
    #
    #  SUMMARY
    #  ------
    #  アドレスCSVファイルを読込み,はがき宛名印刷用TeXファイルを生成する
    #
    #  DESCRIPTION
    #  -----------
    #  1. preamble ファイルを読込み,出力する
    #  2. コンフィグファイルを読込み,TeX 形式で出力する
    #  3. 明細フォームファイルを読込み,form 配列に行展開する
    #  4. アドレスcsvファイルを一行ずつ読込み,form パターンに差込み,出力する
    #
    #  USAGE
    #  -----
    #  1. address.csv, post.conf, preamble.tex, form.tex は UTF-8 であること
    #  2. コマンドラインは以下のとおり
    #     % mkpostaddr -c post.conf -f form.tex -p preamble.tex \
    #       < address.csv > hagaki.tex
    #     % eplatex hagaki.tex
    #     % dvipdfmx hagaki.dvi
    #
    use strict;
    use utf8;
    use Getopt::Std;
    use File::Basename;
     
    binmode(STDOUT, ":utf8");
    # コマンドライン引数処理
    my %opts = (
                'c' => 'post.conf',    # configuretion
                'f' => 'form.tex',     # form.tex
                'p' => 'preamble.tex'  # preamble.tex
               );
    Getopt::Std::getopts('c:f:p:', \%opts) || usage();
     
    # プリアンブル出力
    open(INP, "<:utf8", $opts{'p'}) ||
        die $opts{'p'} . " file not found.\n";
    print $_ while (<INP>); close(INP);
     
    # コンフィグ初期値 単位 mm
    # 宛先郵便番号
    my $POSTNUMT1POSX =  48; # 1桁目X位置
    my $POSTNUMT1POSY = 132; # 1-3桁目Y位置
    my $POSTNUMT1WIDTH =  6; # 1-3桁目送り幅
    my $POSTNUMT2POSX =  68; # 4桁目X位置
    my $POSTNUMT2POSY = 132; # 4-7桁目Y位置
    my $POSTNUMT2WIDTH =  6; # 4-7桁目送り幅
    # 差出人郵便番号
    my $POSTNUMF1POSX =  10; # 1桁目X位置
    my $POSTNUMF1POSY =  16; # 1-3桁目Y位置
    my $POSTNUMF1WIDTH =  4; # 1-3桁目送り幅
    my $POSTNUMF2POSX =  24; # 4桁目X位置
    my $POSTNUMF2POSY =  16; # 4-7桁目Y位置
    my $POSTNUMF2WIDTH =  4; # 4-7桁目送り幅
    # 差出人
    my $FROMADR = ''; # 住所
    my $FROMPCD = ''; # 郵便番号
    my $FROMLN1 = ''; # 姓
    my $FROMFN1 = ''; # 名
    my $FROMLN2 = ''; # 連名・性
    my $FROMFN2 = ''; # 連名・名
    my $FROMOTH = ''; # その他(下寄せ)
     
    # コンフィグ定義読込
    open(INC, "<:utf8", $opts{'c'}) ||
        die $opts{'c'} . " file not found.\n";
    eval($_) while (<INC>); close(INC);
     
    print <<"EOM";
    \\newcount\\cntTOX \\cntTOX=$POSTNUMT1POSX
    \\newcount\\cntTOY \\cntTOY=$POSTNUMT1POSY
    \\newcount\\cntTOW \\cntTOW=$POSTNUMT1WIDTH
    \\newcount\\cntTTX \\cntTTX=$POSTNUMT2POSX
    \\newcount\\cntTTY \\cntTTY=$POSTNUMT2POSY
    \\newcount\\cntTTW \\cntTTW=$POSTNUMT2WIDTH
    \\newcount\\cntFOX \\cntFOX=$POSTNUMF1POSX
    \\newcount\\cntFOY \\cntFOY=$POSTNUMF1POSY
    \\newcount\\cntFOW \\cntFOW=$POSTNUMF1WIDTH
    \\newcount\\cntFTX \\cntFTX=$POSTNUMF2POSX
    \\newcount\\cntFTY \\cntFTY=$POSTNUMF2POSY
    \\newcount\\cntFTW \\cntFTW=$POSTNUMF2WIDTH
    \\begin{document}
    EOM
     
    my @frompcd = ("", "", "", "", "", "", "");
    if ($FROMPCD) {
        @frompcd = split(//, $FROMPCD);
    }
     
    # フォーム入力/行展開
    open(INF, "<:utf8", $opts{'f'}) ||
        die $opts{'f'} . " file not found.\n";
    my @form = <INF>; close(INF);
     
    # 住所CSVファイルを読み込み,行毎にはがき整形する
    while (<STDIN>) {
        utf8::decode($_);
        # コメント行(\#),空行は読み飛ばす
        next if /^#/; next if /^\s*$/; chomp;
        my @addr = split(/,/, $_);
        next if ($addr[0] eq "");
        my @postnum = split(//, $addr[2]);
        my @formwk = @form;
        foreach my $corln (@formwk) {
            # 宛名1姓00
            $corln =~ s/:00:/$addr[0]/;
            # 宛名1名01
            $corln =~ s/:01:/$addr[1]/;
            # 郵便番号 1-7
            for (my $i = 1; $i <= 7; $i++) {
                $corln =~ s/:$i:/$postnum[$i-1]/;
            }
            # 住所1: 番地まで
            $corln =~ s/:8:/$addr[3]/;
            # 住所2: ビル・アパート・部屋
            $corln =~ s/:9:/$addr[4]/;
            # 会社名
            $corln =~ s/:10:/$addr[5]/;
            # 部署名
            $corln =~ s/:11:/$addr[6]/;
            # 宛名2連名姓
            $corln =~ s/:12:/$addr[7]/;
            # 宛名2連名名
            my $ren = $addr[8];
            $ren .= "~様" if ($ren);
            $corln =~ s/:13:/$ren/;
            # 差出人
            $corln =~ s/:20:/$FROMADR/;
            $corln =~ s/:21:/$FROMLN1/;
            $corln =~ s/:22:/$FROMFN1/;
            $corln =~ s/:23:/$FROMLN2/;
            $corln =~ s/:24:/$FROMFN2/;
            for (my $i = 30; $i <= 36; $i++) {
                $corln =~ s/:$i:/$frompcd[$i-30]/;
            }
            $corln =~ s/:25:/$FROMOTH/;
            print $corln;
        }
        print "\\newpage\n";
    }
    print "\\end{document}\n";
     
    sub usage {
        my $prog = basename($0);
        die <<"EOM";
    Usage: $prog -c config-file -f form-file -p preamble-file
           \< address-csv-file \> postcard.tex
      -c   configuration file (default: post.conf)
      -f   form TeX file with variables (default: form.tex)
      -p   preamble definition file (default: preamble.tex)
    EOM
    }
    
     

    掲載したアーカイブには Makefile を添付しており,UNIX 系ユーザなら make とすればサンプル PDF が生成される。dvipdfmx OTF パッケージ用マップファイル otf-ptex-noembed.map も添付してあるが,これは日本語フォントを埋込まない。フォント埋込み PDF を生成したい場合は,ユーザで修正してほしい。コマンドラインを手入力する場合は,以下のとおりとすればよい。

    % ./mkpostaddr -c post.conf -f form.tex -p preamble.tex < address.csv > hagaki.tex
    % eplatex hagaki.tex
    % dvipdfmx -f otf-ptex-noembed.map hagaki.dvi
    

    宛名情報の出力様式(フォント,空き,出力位置)などをカスタマイズしたい場合は,form.tex を確認して,ご自分用に修正していただきたい。

    しばらく前に,会社の品質保証部のセキュリティ情報で Apache Web サーバの DoS 脆弱性が話題になり,現場に対し担当システムを確認するよう指示がまわっていた。担当顧客システムの調査/対応が一段落したので,自宅の Web サーバもチェックしてみることにした。

    この脆弱性のアドヴァイザリは「JVNTR-2011-05 Apache HTTPD サーバにサービス運用妨害 (DoS) の脆弱性 (CVE-2011-3192, JVNVU#405811)」。Apache 2.x 系すべてが対象とのこと。Range ヘッダ処理で Web サーバのみならずシステム全体のサービス不能を引き起こす問題である。

    自宅の FreeBSD 8.2-RELEASE 公開 Web サーバを確認すると,インストールした Apache22 の CHANGES(修正履歴ファイル)には Fix a regression introduced by the CVE-2011-3192 byterange fix in 2.2.20. とあるので,すでに対策済みの版だとわかった。OS を組込んだ 9 月には,FreeBSD Apache22 ports は対策版に更新されていたわけである。この問題を検証できる Perl コード Apache Killer - killapache.pl が公開されている。検証のため,公開サーバをこのプログラムで攻撃してみた。

    isolde:/Users/i-yasuda/var/ % perl killapache.pl beatrice
    Host does not seem vulnerable
    

    となって,確かに対策済みだとわかった。killapache.pl 攻撃コードは Perl IO::Socket 及び Parallel::ForkManager モジュールを必要とするのであらかじめ組込んでおく必要がある。ためしに,メインで使っている書斎の Mac OS X Tiger マシンを攻撃してみると,みるみるうちに CPU 利用率が 100% に跳ね上がってそれが延々と続き,操作がずしんと重くなった。

    この脆弱性は有名だし,攻撃コードが公開されているくらいだから,ご自分で Web サーバを運用されている方はぜひチェックして,未対応なら Apache を最新版に更新していただきたい。インストールした Apache のドキュメントを見て,CVE-2011-3192 アドヴァイザリが対策されていればよい。

    デニス・リッチー死去

    |

    先日スティーブ・ジョブズの死去がトップニュースで報道され,大きな話題になった。確かに,アップルの今後の動向は IT 業界に大きな影響を与えるので,当然の扱いである。今日,デニス・リッチー死去のニュースがひっそりと掲載されていた

    でも,私のような 1980 年代に計算機の素養を身に付けた者にとっては,デニス・リッチーのほうがスティーブ・ジョブズよりも,遥かに,何倍も,圧倒的に,ネームバリューが高いのではないだろうか。ブライアン・カーニハン,ケン・トンプソンなどとともに,デニス・リッチーの業績は,Programming language C,UNIX という,現代の計算機基盤の創造と直接に結びついているからである。スティーブ・ジョブズやビル・ゲイツが「ビジネス」にして大成功した,その礎を築いたのが彼らだからである。

    私にとって,デニス・リッチーは計算機「文化」の生けるレジェンドであった。彼の名著『プログラミング言語 C』に,どれだけ多くの人が C の文法のみならず「プログラミング書法」というものを教えられただろうか。私はこの本を読んで,プログラミングは間違いなく文化表現たりうるという確信をもったものだった(この本についてはここに記した)。そのレジェンドがとうとう歴史のなかに移行しようとしている。私には,敬愛する大作家が亡くなったような,そんな感慨がある。

    ruby マークアップ・ツール

    |

    昨日書いた泉鏡花『貧民倶樂部』の記事において,鏡花テクスト引用のために大量のルビをマークアップしなければならなかった。HTML で <ruby><rb></rb><rp></rp><rt>これ</rt><rp>)</rp></ruby> とマークアップすると,これ となる。<ruby> タグをサポートしていないブラウザならタグ以外のテキストがそのまま出力されて 此(これ)となる。LaTeX にも奥村先生のマクロ集に \ruby 命令があり,この場合は \ruby{此}{これ} とマークアップする。これらのルビ付加用マークアップ作業は,やっている最中に元の文章が分断されて判りにくくなりかつ極めて面倒なので,私は自分で書いた簡単なツールで一括整形している。今日はその整形ツール convertruby を紹介しておく。私は Mac OS X で使用しているが,FreeBSD, Linux でも動作するはずである。同じ必要に迫られた方はプログラム・コードをコピってお使いください。

    このツールは,標準入力から UTF-8 テキストを読み,此(これ)のように,漢字 + + 読み + のように書いた文字列に対して,マークアップを行う。デフォルトでは HTML タグを付加する。-l オプションを指定すると LaTeX 形式で整形する。対象テキストの括弧は全角でないといけない。そういう融通の利かないところがある。開括弧より前のテキストを後ろから逆方向に走査し,漢字以外が出現するまで漢字をスタックにプッシュし,あとでこのスタックからポップして得られる文字列(先入れ後出しにより文字の順番が戻る)を親文字と判定する。「漢字」は Unicode CJK 統合漢字に属するかを \p{InCJKUnifiedIdeographs} 正規表現文字クラスで判断している。Perl コードは以下の通り。

    #!/usr/bin/perl -w
    # -*- coding: utf-8; mode: cperl; -*-
    #
    #     convertruby: ルビ・マークアップ
    #     - ベタのテキストを <ruby> or \ruby シーケンスに整形する
    #     - 例: 此(これ) ["(", ")" は全角括弧]
    #       -> <ruby><rb>此</rb><rp>(</rp><rt>これ</rt><rp>)</rp></ruby> (default)
    #       -> \ruby{此}{これ} (with -l option)
    #     - usage: convertruby [-l] < stdin
    #          -l: for LaTeX; default: for HTML
    #
    use strict;
    use utf8;
    use Getopt::Std;
    use File::Basename;
    binmode(STDOUT, ":utf8");
    # コマンドライン・オプション処理
    my %opts = ('l' => 0);
    Getopt::Std::getopts('l', \%opts) ||
        die "Usage: " . basename($0) .
        " [-l] \< (stdin)\n   -l: for LaTeX; default: for HTML\n";
    # 行毎の主処理
    while (<STDIN>) {
        my $line = $_;
        utf8::decode($line); utf8::decode($_);
        my ($yomi, $leftt, $rightt);
        # 「(読み)」を含まない行はそのまま出力
        unless ($_ =~ /([^)]*)/gi) {
            print $line; next;
        }
        # 「(読み)」を走査し,これがある限りその前後を切り出す
        while ($line =~ /([^)]*)/gi) {
            $yomi = $&; $leftt = $`; $rightt = $';
            # 「(読み)」から括弧を外す
            $yomi =~ s|[()]||g;
            # 対象漢字部を後方から走査し,漢字スタックに push する
            my @kstack = (); my $k;
            my @istack = split(//, $leftt); # 「読み」の左側テキストの文字配列
            while ((@istack) &&
                   (($k = pop(@istack)) =~ /\p{InCJKUnifiedIdeographs}/g)) {
                push(@kstack, $k); $k = "";
            }
            # 最後に pop した非漢字を左側テキスト配列スタックに戻しておく
            push(@istack, $k) if ($k);
            # 漢字スタックから漢字部文字列を pop で復元
            my $kanji = "";
            while (@kstack) {
                $kanji .= pop(@kstack);
            }
            # 抽出した漢字部よりも前の文字列を出力
            if (@istack) {
                print $_ for @istack;
            }
            # ruby マークアップ部分を出力
            if ($kanji) {
                if ($opts{'l'}) {
                    # LaTeX (with -l option)
                    print '\ruby{' . $kanji . '}{' . $yomi . '}';
                } else {
                    # HTML (default)
                    print '<ruby><rb>' . $kanji .
                        '</rb><rp>(</rp><rt>' . $yomi . '</rt><rp>)</rp></ruby>';
                }
            } else {
                # 「漢字(読み)」のパターンでないものはそのまま出力
                print "($yomi)";
            }
            # 走査対象文字列に右側テキスト(残り)文字列を格納して,繰り返し
            $line = $rightt;
        }
        # 残り文字列を出力
        print $rightt if ($rightt);
    }
    
    GNU Emacs からは,以下を .emacs に追加すれば,利用できるようになる。ここでは convertruby/usr/local/bin 下にインストールされているものとしている。この定義を Emacs に評価させた後,テキスト・リージョンを指定し,M-x convert-ruby-html, M-x convert-ruby-latex を実行すると,当該リージョンがそれぞれ HTML, LaTeX のルビ・マークアップ整形を施される。

    ;; convertruby for HTML
    (defun convert-ruby-html (start end)
      "Convert text to ruby HTML sequence."
      (interactive "r")
      (call-process-region
       start end
       "/usr/local/bin/convertruby"
       t (list t nil) nil
       "")
      )
    ;; convertruby for LaTeX
    (defun convert-ruby-latex (start end)
      "Convert text to ruby HTML sequence."
      (interactive "r")
      (call-process-region
       start end
       "/usr/local/bin/convertruby"
       t (list t nil) nil
       "-l")
      )
    

    泉鏡花『貧民倶樂部』の引用に際しては,新字・新仮名遣いで引用文を作成し(図 1),自作のソフトで旧字・旧仮名遣いに変換し,次に,ルビを付けたい漢字に「(よみ)」を付加し(図 2),最後に Emacs 上で convertruby を実行して <ruby> タグをマークアップした(図 3)。ただし,図 3-1 は HTML 整形の,図 3-2 は LaTeX 整形の結果例である。
     

    20111012-ruby-1.jpg図 1. ルビなしで新字・新仮名遣いで記述
    20111012-ruby-2.jpg図 2. 表記変換後,ルビを付加
    20111012-ruby-3.jpg図 3-1. HTML 整形実行結果
    20111012-ruby-4.jpg図 3-2. LaTeX 整形実行結果

    misima 漢詩作成支援: 平仄音韻分析・詩語検索 misimakansiservlet を使っている方からメールをいただいた。そこで思い出したように,久しぶりに辞書のメンテナンスを行った。平仄などのデータを格納した辞書=漢字データベースは SQLite3 で作成したのだけど,SQLite3 オペレーションを中心とするメンテナンス手順を忘れてしまっていた。思い出したのを整理し,ここにも備忘録を残しておく。以下,beatirce:/home/isao[nnnn] % は misima サーバ機端末の tcsh プロンプト,nnnn はコマンド履歴番号を示す。

    misimakansiservlet において,漢詩分析デーモン misimakansiserver は漢字データベース KANJITBL で見つからなかった文字をログに出力するようになっている。私はこれを定期的にチェックし,それらの辞書にない文字(ユーザが入力したが辞書になかった漢字)を手作業で DB に追加する。ログのその部分を抽出する。

    beatirce:/home/isao[1000] % grep -e '\*\*\*' /var/log/kansiserver.log | sort | uniq
    ***** 㱕 が KANJITBL にありません.
    ***** 僲 が KANJITBL にありません.
    ***** 嵆 が KANJITBL にありません.
    ***** 扃 が KANJITBL にありません.
    ***** 涴 が KANJITBL にありません.
    ***** 疴 が KANJITBL にありません.
    ***** 裛 が KANJITBL にありません.
    ***** 髥 が KANJITBL にありません.
    beatirce:/home/isao[1001] % 
    

    というわけで,今回この 8 文字を追加。漢字字典を調べ,平仄,読み,語義などを DB のソースコード kanji.src にテキストエディタで追加する。完了したら,create table で漢字データベースファイル KANJI.db を生成する。そしてこれに kanji.src の内容を .import コマンドでロードする。kanji.src のデータ記述はカンマ区切りの CSV 形式なので,ロード実行には -separator , オプションを指定している。

    beatirce:/home/isao[1001] % cd misima
    beatirce:/home/isao/misima[1002] % sqlite3 KANJI.db < mkkanji.sql
    beatirce:/home/isao/misima[1003] % sqlite3 -separator , KANJI.db ".import kanji.src KANJITBL"
    beatirce:/home/isao/misima[1004] % 
    

    ここで,mkkanji.sql とは create 文で DB のスキーマ(データ項目とその属性)を定義する元ネタである。misimakansiservlet の場合は以下のようなものである。

    -- -*- coding: utf-8; mode: sql; -*-
    -- misimakansiservert 平仄音韻分析用漢字テーブルレイアウト
    -- 漢字 ji, 平仄 hs, 韻 in1, 韻 in2, 音読 yo, 訓読 yk, 備考 bk
    create table KANJITBL (
           ji text not null,
           hs text not null,
           in1 text not null,
           in2 text not null,
           yo text not null,
           yk text not null,
           bk text not null
    );
    

    これで漢字データベースはできた。データ内容を確認して問題なければ,/usr/local/etc/misima/ の下にコピーする。これで Web サーブレットから使えるようになる。以下,SQLite3 DB 操作のメモ。

    1. SQLite3 コマンドラインの起動
    2. 扱う DB を引数に指定する。
      beatrice:/home/isao/misima[1004] % sqlite3 KANJI.db
      SQLite version 3.7.7.1 2011-06-28 17:39:05
      Enter ".help" for instructions
      Enter SQL statements terminated with a ";"
      sqlite> 
      
    3. テーブル名の表示
    4. sqlite> .table
      KANJITBL
      
    5. DB スキーマの表示
    6. sqlite> .schema KANJITBL
      CREATE TABLE KANJITBL (
                 ji text not null,
                 hs text not null,
                 in1 text not null,
                 in2 text not null,
                 yo text not null,
                 yk text not null,
                 bk text not null
      );
      
    7. DB 件数の確認
    8. sqlite> select count(*) from KANJITBL;
      6929
      
    9. tcsh コマンドラインから直接 SQL を入力する
    10. sqlite3 [opt] [db-file] に続けてクオート(ダブルクオーテーションマークで括る)して SQL 文を書けばよい。SQLite3 は標準入力からも SQL を受け付けるので,テキストファイルに書いておきリダイレクトで読み込ませてもよい。
      beatrice:/home/isao/misima[1005] % sqlite3 KANJI.db \
      "select * from KANJITBL where ji='髥';"
      髥|◎|hs:14:塩|sk:29:艷|ぜん;ねん|-|ほおひげ;鬚髯:柔らかいあごひげとほおひげ;
      beatrice:/home/isao/misima[1006] % cat sql.txt
      select * from KANJITBL where ji='㱕';
      select * from KANJITBL where ji='僲';
      select * from KANJITBL where ji='嵆';
      select * from KANJITBL where ji='扃';
      select * from KANJITBL where ji='涴';
      select * from KANJITBL where ji='疴';
      select * from KANJITBL where ji='裛';
      select * from KANJITBL where ji='髥';
      beatrice:/home/isao/misima[1007] % sqlite3 KANJI.db < sql.txt
      㱕|○|hk:05:微|-|き|かえる;かえす|帰・歸の異体字;
      僲|○|hs:01:先|-|せん|-|仙人;僊の異体字;
      嵆|○|hk:08:齊|-|けい;げ|-|稽古の当て・古いことを集めて考える;嵆山:山の名;嵆康:姓の一;
      扃|○|hs:09:青|-|けい;きょう|-|かんぬき;とざす;
      涴|●|sj:13:阮|sk:21:箇|えん;おん;わ|-|涴演:川が曲がりまわって流れるさま;けがす・けがれる;
      疴|◎|hs:05:歌|sk:22:禡|あ;か;け|-|やまい;痾に同じ・痾疾;
      裛|●|sn:14:緝|sn:16:葉|ゆう;おう;よう|-|つつむ・香がまとわりつく;書物の覆い;しっとりと濡れる;衣の部;
      髥|◎|hs:14:塩|sk:29:艷|ぜん;ねん|-|ほおひげ;鬚髯:柔らかいあごひげとほおひげ;
      beatrice:/home/isao/misima[1008] % 
      

    これで追加した漢字の平仄分析ができるようになった。
     

    20111008-kanjituika.jpg

    FreeBSD メモリディスク

    |

    misima 旧仮名遣い・旧字変換サービスは大量の辞書を読み込む。高速化のためには出来る限りファイル入出力のオーバヘッドを減らしたい。そこでサイト運用において FreeBSD のメモリディスクに辞書などの misima リソースを格納して,インコアで処理できるようにしている。FreeBSD メモリディスクとはメモリに割り当てられたファイルシステム機能であり,昔の所謂 RAMDISK というのか,HDD に比べて高速なメモリにファイルを割り当てるものである。以下,そのオペレーションのメモ。

    misima の辞書等のリソースは一式 /usr/local/etc/misima ディレクトリ下に格納している。だいたい 5MB 程度である。この容量をもつファイルイメージでメモリファイルシステムを生成しマウントする。

    # [1] mkdir -p /usr/local/etc/misima
    # [2] cd /tmp
    # [3] dd if=/dev/zero of=newimage bs=1k count=5k
    # [4] mdmfs -F newimage -s 5m md0 /usr/local/etc/misima
    # [5] cd ~/src/misima/etc/
    # [6] tar cf - . | ( cd /usr/local/etc/misima; tar xvf -)
    

    [3] でまっさらな 5MB のファイル newimage を作成し(名前は任意。dd コマンドでゼロを 5MB = 1,024 x 5,120 バイト分書き込んでいるだけである),[4] でこれをメモリディスクデバイス md0 として /usr/local/etc/misima にマウントしている。もちろんこの時点で中味はまだ何もないので,[5][6] のように,開発ソースディレクトリから辞書などをコピーして準備完了となる。mdmfs コマンドの日本語マニュアルはここを参照。

    mount, df コマンドで確認すると,以下のような表示がされるはずである。

    # mount
    ......
    /dev/md0 on /usr/local/etc/misima (ufs, local, soft-updates)
    # df -k
    Filesystem  1024-blocks     Used     Avail Capacity  Mounted on
    ......
    /dev/md0           4718     3608       734    83%    /usr/local/etc/misima
    

    メモリ上のファイルシステムなので,当然ながらリブートすると跡形もなく消え去ってしまう。misima サービス起動時に上記オペレーションを自動的に実行するようなシェルスクリプトを書いて,運用するとよい。5MB くらいならキャッシュに入ってるって? ま,そうなんだけどね。

    FreeBSD 8.2-RELEASE HDD 増設

    |

    SATA-HDD を FreeBSD 8.2-RELEASE に追加してみた。disklabel コマンドが最近の FreeBSD では bsdlabel コマンドに変更されているなどのおかげで手間取ったが,何とか増設できた。備忘録を残しておく。ただし,以下はコマンドラインからのオペレーションによって増設 HDD をまるごと一ファイルシステムとして追加する方法である。FreeBSD 初期インストール時は,sysinstall (FreeBSD の標準インストーラ) で HDD を初期化するほうがよいと思う。

    増設 HDD がどのデバイスで認識されているか /var/run/dmesg.boot を確認する。私の場合,ad6 で認識されたようである。ちなみにブートパーティションのあるメイン HDD のデバイスは ad4 である。

    ad6: 476940MB <SAMSUNG HD501LJ CR100-10> at ata3-master UDMA100 SATA ...
    

    スーパユーザで,このデバイスを初期化し(dd),標準的ディスクラベルを書き込み(bsdlabel -w),ファイルシステムを生成し(newfs),最後にマウントする(mount)。マウンティングポイントは /disk としておく。

    # dd if=/dev/zero of=/dev/ad6 bs=1k count=1
    # bsdlabel -w ad6
    # newfs /dev/ad6a
    # mkdir -p /disk
    # mount /dev/ad6a /disk
    

    newfs コマンドで指定している ad6a/dev/ad6a% が存在していたので,これを使った。

    マウントがうまく行けば,/etc/fstab にもエントリを追加しておく。

    # Device                Mountpoint      FStype  Options         Dump    Pass#
    /dev/ad6a               /disk           ufs     rw              2       2
    

    NFS で他の PC からも参照するのであれば /etc/exports にもエントリを追加しておく。

    /disk  -alldirs  -network 192.168.1.0 -mask 255.255.255.0
    

    指定内容は割愛。man exports で確認してください。

    Burning DVD-R on FreeBSD

    |

    備忘録。

    /shared/dvd 下に焼きたいファイルを格納しておく。

    # cd /shared
    # mkisofs -U -R -o dvdimage.iso ./dvd
    # growisofs -dvd-compat -speed=4 -Z /dev/cd0=dvdimage.iso
    
     

    【注意事項】

    1. 容量は 4GB 程度以下にしておく。
     これを超えるデータは split で分割しておく。
     # split -b 4000000000 bigdata split-20110925-
     --> split-20110925-aa split-20110925-ab ... に分割
     復元: cat split-20110925-aa split-20110925-ab ... > origdata

    2. /usr/ports/sysutils/dvd+rw-tools 前提

    3. ATAPICAM モジュールを有効にしておく。
     kldstat で atapicam.ko があれば OK。
     なければ kldload atapicam でロードしておく。

    Subversion post-commit

    |

    FreeBSD サーバ beatrice 復旧がやっと終わったと思ったら,バージョン管理システム Subversion の commit がエラーになってしまった。サーバに Subversion のリポジトリがあり,クライアント PC から自作プログラムや Web ページのバージョン管理を行っている。Web ページについては commit (新バージョンの登録) 操作を行うと,Subversion の post-commit スクリプト(commit の延長で実行される Subversion の付加スクリプト。これを改造して独自処理を追加できる)により,新しい HTML を Web 公開ディレクトリにコピーし,namazu 検索インデックスを更新する,という一連の更新作業を自動的に行うようになっている。

    エラーはこの実行環境を復旧し忘れていたためだった。このリカバリには少し悩んだ。自分で組込んだのにその仕組みを完全に忘却してしまっていた。メモを残しておかないとこういうハメになる。そういうことで,改めてここで記しておくことにする。

    自動更新の手続きは次のようなものである。

    1. post-commit スクリプトは Web ページ更新シェルスクリプト webupdate スクリプト(自作)を呼び出す。
    2. webupdate は作業ユーザ user のホームディレクトリにある Subversion 管理下の Web サイトソースにおいて,svn update を実行し,新しい版を取り出す。
    3. webupdate は,前回 Web 更新実行時刻のタイムスタンプをもつタイムスタンプファイルよりも新しいファイル群だけを纏めて一時アーカイブファイルを作成する。
    4. webupdate は Apache22 ドキュメントルートにおいて一時アーカイブファイルを解凍し,公開 Web ドキュメントツリーを更新する。
    5. webupdate はタイムスタンプファイルを現在の時刻で更新する(touch コマンドによる)。
    6. webupdate は namazu 検索インデックス更新スクリプト mkwebidx(自作)を呼び出す。
    7. mkwebidx は,前回インデックス更新実行時刻のタイムスタンプをもつタイムスタンプファイルよりも新しく,かつインデックス作成対象のファイル群だけを,namazu インデックス更新用作業領域にコピーする。
    8. mkwebidx は,コピーしたファイルのうち UTF-8 エンコード文書を EUC-JP にコード変換する(namazu が EUC-JP 前提のため)。
    9. mkwebidx は namazu 検索インデックス更新プログラム mknmz コマンドで検索インデックスを更新する。
    10. mkwebidx はタイムスタンプファイルを現在の時刻で更新する(touch コマンドによる)。

    この手続きは Web 環境で実行されるため,ユーザ権限は www のパーミションである。しかし,一連の操作において user, root 権限が必要なオペレーションがあり,www がこれらに成り代わる仕掛けが必要になる。もちろん関連するリソースすべてを www の所有とすればよいのだけれども,その他運用事情で www 専用とするわけにも行かなかった。このため,スクリプト内で対話操作を実現するために expect を利用する。www ユーザが expect コマンドで他のユーザでシステムにログインしてその権限の必要なオペレーションを実行するわけである。expect は FreeBSD ports /usr/ports/lang/expectmake install clean を行い組込んでおく。

    ただし,このためには www のアカウント設定を通常の nologin からログイン可能ユーザに変更しておかなくてはならない。www アカウント設定(vipw で編集)を以下のようにした。

    www:パスワード:80:80::0:0:World Wide Web Owner:/nonexistent:/bin/tcsh
    

    /nonexistent はもともとダミーなんだけど,www オーナで同名ディレクトリを作成しておく。これで一度,www ユーザとして userslogin しておき,ssh が通るようにしておく。

    beatrice:/home/user % su -
    xxxxxxx
    # mkdir -p /nonexistent
    # chown -R www:www /nonexistent
    # exit
    beatrice:/home/user % su - www
    xxxxxxx
    > slogin user@beatrice 
    The authenticity of host 'beatrice (192.168.1.4)' can't be established.
    RSA key fingerprint is 62:35:a2:d5:e6:20:8f:91:6e:ce:e6:d2:a4:2b:5c:a7.
    Are you sure you want to continue connecting (yes/no)? yes
    Warning: Permanently added 'beatrice,192.168.1.4' (RSA) to the list of known hosts.
    Password: xxxxxx
    beatrice:/home/user % 
    

    post-commit スクリプトは,Web ページソースの Subversion リポジトリが /usr/local/var/svn/website/ だとすると,/usr/local/var/svn/website/hooks/ 下に格納する。

    以下に post-commit を示す。Subversion によって,引数としてリポジトリディレクトリ名とリビジョンが渡されて起動される。このスクリプトは /var/log/svnupdate.log に実行ログを出力するようになっているので,あらかじめ www オーナ属性でこのファイルを touch しておく。

    #!/bin/sh
    REPOS="$1"
    REV="$2"
    LOG=/var/log/svnupdate.log
    /bin/echo "*** post-commit STARTED at `/bin/date` $REPOS $REV" >> $LOG
    /home/user/bin/webupdate >> $LOG
    /bin/echo "*** post-commit ENDED at `/bin/date` $REPOS $REV" >> $LOG
    

    以下に,順次実行される webupdate, mkwebidx の両スクリプトも掲載しておく。

    #!/bin/sh
    #
    #   webupdate
    #
    #   * HTML install for Beatrice
    #   * will be called by post-commit of Subversion
    #   Copyright (c) 1998-2011, isao yasuda, All Rights Reserved.
    #
    DATADIR="/home/user/src/noxinsomniae"
    STAMP="$DATADIR/stamp"
    EXPECT="/usr/local/bin/expect"
    echo "*** Web install start at `date` ***"
    # 新しい HTML の取り出し
    $EXPECT -c '
        set timeout -1
        spawn slogin user@beatrice
        expect "Password:" 
        send "xxxxxx\r"
        expect "% " 
        send "cd /home/user/src/noxinsomniae\r"
        expect "% " 
        send "/usr/local/bin/svn update\r"
        expect "% " 
        puts "End"
        exit
        '
    # 更新ページのアーカイブ
    cd $DATADIR
    echo "*** $DATADIR archiving & Beatrice installing ..."
    find -L . \( -newer $STAMP \! -name .svn \! -name prop-base \! \
        -name text-base \! -name props \! -name tmp \! -name all-wcprops \
        \! -name entries \! -name "*svn-base*" -type f \) | \
        xargs tar zcf /tmp/webarc.tar.gz
    echo "*** archive gen done. *** "
    ls -las /tmp/webarc.tar.gz
    # 公開 Web ツリーへの展開
    $EXPECT -c '
        set timeout -1
        spawn slogin user@beatrice
        expect "Password:" 
        send "xxxxxx\r"
        expect "% " 
        send "su\r"
        expect "Password:" 
        send "xxxxxx\r"
        expect "# "
        send "tar zxvf /tmp/webarc.tar.gz -C /usr/local/www/apache22/data\r"
        expect "# " 
        puts "End"
        exit
        '
    echo "*** extraction done. ***"
    rm -f /tmp/webarc.tar.gz
    echo "*** removed archive. ***"
    echo "*** Web install ended at `date` ***"
    # タイムスタンプの更新
    touch $STAMP
    #
    # namazu 検索インデックスの更新
    #
    echo "*** Search index generation ***"
    $EXPECT -c '
        set timeout -1
        spawn slogin user@beatrice
        expect "Password:" 
        send "xxxxxx\r"
        expect "% " 
        send "su\r"
        expect "Password:" 
        send "xxxxxx\r"
        expect "# "
        send "/home/user/bin/mkwebidx\r"
        expect "# " 
        puts "End"
        exit
        '
    echo "*** done. ***"
    
     
    #!/bin/sh
    #
    #   mkwebidx
    #
    #   * namazu Web 検索インデックス更新
    #   Copyright (c) 1998-2011, isao yasuda, All Rights Reserved.
    #
    LANG=ja_JP.eucJP
    export LANG
    SRCDIR=/usr/local/www/apache22/data
    TMPIDX=/tmp/webidx
    MKNMZRC=/usr/local/etc/namazu/web/mkwebidxrc
    IDXDIR=/usr/local/var/namazu/index/web
    MKNMZ=/usr/local/bin/mknmz
    STAMP=/tmp/webidx/stamp
    LIST=/tmp/UTF8LIST
    if [ `whoami` != "root" ]; then
        echo "Invoke as a superuser, `whoami`."
        exit
    fi
    # 更新ページの抽出
    cd $SRCDIR
    find -L ./*.html ./bbs ./dl ./izhltndoc ./japlit ./lan \
        ./oldslav ./profile ./pushkin ./rus2 ./russify ./slavonic \
        ./tex ./misima ./cyr ./usconcord ./concordance  \
        \( -newer $STAMP \) -and \
        \( -name "*.html" -or -name "*.shtml" -or -name \
        "*.pdf" -or -name "*.txt" \) | \
        xargs tar cf - | \
        ( cd $TMPIDX; tar xvf - )
    # UTF-8 to EUC-JP コード変換
    find -L ./*.html ./bbs ./dl ./izhltndoc ./japlit ./lan \
        ./oldslav ./profile ./pushkin ./rus2 ./russify ./slavonic \
        ./tex ./misima ./cyr ./usconcord ./concordance \
        \( -newer $STAMP \) -and \
        \( -name "*.html" -or -name "*.shtml" \) | \
        xargs grep -H -e "charset=[Uu][Tt][Ff]-8" | \
        cut -d : -f 1 > $LIST
    for i in `cat $LIST`
    do
        iconv -c -f UTF-8 -t EUC-JP $i | \
        sed 's/charset=[Uu][Tt][Ff]-8/charset=EUC-JP/g' > $TMPIDX/$i
    done
    # インデックス更新
    $MKNMZ -f $MKNMZRC -a -O $IDXDIR -k $TMPIDX
    # タイムスタンプの更新
    touch $STAMP
    rm -f $LIST
    echo "*** done. ***"
    

    サーバが故障してリカバリに四苦八苦。しばらく触らないとすぐ設定方法を忘れる。また同じことが起こるはずなので,私自身のために書き残しておく。つまり,記述は私自身の運用環境に依存するので,不特定の方の参考にはならない可能性が高い。
     

    1. sendmail

    私のメール環境は,送信はプロバイダの SMTP サーバを用い,受信を自宅サーバで行い各ユーザのメールボックスに配信する運用になっている。SPAM メール対策に SpamAssassin を使い,少しでも機械的メール送信プログラム対策に資するよう Milter-Greylist を使っている。家族の PC で動くメールソフトから取りに来る受け口となる POP3 サーバ Qpopper, 着信メールの自動振分のための Procmail, Web アーカイブとして閲覧できるようにするための MHonArc, プロバイダ・アカウント宛に来たメールを取得する Fetchmail 等々,関連プログラムは少なくない。まずこれらのソフトウェアを ports でインストールする。ここでは Qpopper と SpamAssassin についてだけ少し。

    Qpopper とのコネクションは inetd が受け付けるようにしているので,/etc/inetd.conf 中の POP3 のエントリを以下の通りの内容で記述しておく。

    pop3  stream  tcp  nowait  root  /usr/local/libexec/qpopper  qpopper -s
    

    SpamAssassin については,FreeBSD 8.2-RELEASE の現時点での最新 ports /usr/ports/mail/p5-Mail-SpamAssassin/ から導入を試みると,ライブラリのバージョンで齟齬を起こしてうまく make できなかった。調べるのも面倒なので,日本語版 ports /usr/ports/japanese/p5-Mail-SpamAssassin/ を試してみるとうまくインストールできた。ただし,この日本語版 ports は日本語処理に MeCab を活用するパッケージになっていて,これを自動的にインストールしてくれるのだけど,UTF-8 で MeCab を調整するよう要求してくる。そこで以下のように MeCab を UTF-8 で導入したあとで SpamAssassin を組込むとよいと思う。

    # cd /usr/ports/japanese/mecab && make WITH_CHARSET=utf8 install clean
    # cd /usr/ports/japanese/mecab-ipadic && make WITH_CHARSET=utf8 install clean
    # cd /usr/ports/japanese/p5-MeCab && make install clean
    # cd /usr/ports/japanese/p5-Mail-SpamAssassin && make install clean
    

    メール環境の復旧は何より sendmail.cf 設定ファイルのリカバリが命である。これをバックアップしておけばよいのであるが — 愚かにも — 今回バックアップのなかに見当たらなかった。それで一から設定することに。

    最近の FreeBSD では sendmail.cf の生成は,/etc/mail 下で m4 マクロで元ネタ(mc ファイル)を記述し make することにより行うのが一般的のようである。私のサーバ yasuda.homeip.net 用の mc ファイルとして beatrice.yasuda.homeip.net.mc を以下の通り準備した。

    divert(0)
    VERSIONID(`...')
    OSTYPE(freebsd6)
    DOMAIN(generic)
    MASQUERADE_AS(`yasuda.homeip.net')
    MASQUERADE_DOMAIN(`$j')
    EXPOSED_USER(``daemon news usenet postmaster MAILER-DAEMON'')
    FEATURE(`limited_masquerade')
    FEATURE(`masquerade_envelope')
    FEATURE(`allmasquerade')
    FEATURE(access_db, `hash -o -T /etc/mail/access')
    FEATURE(blacklist_recipients)
    FEATURE(local_lmtp)
    FEATURE(mailertable, `hash -o /etc/mail/mailertable')
    FEATURE(virtusertable, `hash -o /etc/mail/virtusertable')
    define(`confCW_FILE', `-o /etc/mail/local-host-names')
    define(`confMAX_MIME_HEADER_LENGTH', `256/128')
    define(`confNO_RCPT_ACTION', `add-to-undisclosed')
    define(`confPRIVACY_FLAGS', `authwarnings,noexpn,novrfy')
    dnl j,{if_addr},{cert_subject},i,{auth_authen} are already enabled by default
    define(`confMILTER_MACROS_HELO', confMILTER_MACROS_HELO``, {verify}'')
    define(`confMILTER_MACROS_ENVRCPT', confMILTER_MACROS_ENVRCPT``, {greylist}'')
    INPUT_MAIL_FILTER(`greylist', `S=local:/var/milter-greylist/milter-greylist.sock, F=T, T=R:30s')
    MAILER(local)
    MAILER(smtp)
    

    私自身の備忘録なので細かいことは省略する。最後行から 3--5 行は Milter-Greylist のための記述である。一般的な記述は freebsd.mc に書かれているので,ドメイン名などを自分用に修正すれば基本的によいはずである。書き方の詳細は /usr/share/sendmail/cf/README に記載されている。

    mc ファイルができたら,sendmail.cf を make する。

    # cd /etc/mail
    # make SENDMAIL_MC=/etc/mail/beatrice.yasuda.homeip.net.mc all install
    

    さらに /etc/rc.confsendmail_enable="YES" を追加する。これを行わないと,sendmail は localhost 以外のコネクションを拒否する設定で起動するので注意。詳細は man rc.sendmail を参照。これで,リブートすれば壊れる前と同様に sendmail が動作する。

    スパムの踏み台にならないか一応チェックしておくとよい。http://verify.abuse.net/relay.html などで不正な第三者中継のセキュリティホールを診断できる。このサイト・フォームの Address to test: にメールサーバ名を入力して test for relay ボタンをクリックすれば診断を開始する。All tests performed, no relays accepted. が最後に出力されればよいと思う。
     

    2. Movable Type

    本ブログの管理システム Movable Type 4.1 (以下 MT) のリカバリについては,ブログ情報を格納した SQLite3 DB と Web MT data ツリー,cgi-bin ツリーすべてが,壊れたディスクから復元できた。このため,リカバリ作業はこれらのツリーの復元(バックアップアーカイブの解凍),SQLite3 のインストール,そしてMT 動作のために必要な Perl モジュールの追加ということになる。

    まず Web サーバ Apache22 環境を構築しておく。その後バックアップから MT ツリーを復元する。SQLite3 がインストールされているか確認し,なければインストールしておく。そのあとでブラウザから mt-check.cgi にアクセスすると,MT が前提とする Perl モジュールの検出状況が出力される。不足しているものを ports で追加するか,cpan コマンドもしくは perl -MCPAN -e shell でインストールする。再度 mt-check.cgi を実行して動作準備完了が確認できれば,mt.cgi にアクセスする。以前の通り使用可能になるはずである。

    自宅サーバ復旧

    |

    土曜日,自宅のサーバが故障した。NFS マウントしたビデオデータを Mac OS から観ていると頻繁に停止する。挙げ句,サーバ接続が切断される。FreeBSD サーバのログを確認すると,LBA read に失敗した旨のエラーメッセージが大量に出力されている。南無三宝,ディスクがぶっ飛んだらしい。サーバをリブートしたところ,ハードウェアの probe のあと fsck で回復不能エラーを検知して起動が停止してしまった。「エッチな映画ばかり観てるからバチが当たったのよ」とは妻。正しい。教訓その1:NFS サーバにエッチな映画を入れて楽しむのはやめよう。

    ディスクを買って来て,交換し,FreeBSD をインストールし,Apache やらなにやら大量のプログラムをインストールし,サーバ環境を整え,... と考えると気が遠くなった。しかしメールサーバが使えなくなるとやっかいか,ブログもパーか,などなど考えるにつけ,しようがないなーとサーバ復旧に着手したのである。おかげでこの連休はほとんどパーになってしまった。

    日曜日,娘の学園祭を見に行ったあと,川崎駅前ヨドバシカメラで Hitachi Global Storage Technology 社製の SATA2 7200 rpm 1TB HDD バルク品を 5,000 円足らずで購入。帰宅して,サーバ筐体を開けて HDD を交換しようとしたら,なんと壊れた既設の HDD が取り外せない。このベアボーンは,HDD を装着したネジを覆うように,特殊な方法で電源を固定していたのである。「じゃ増設」と考えたんだけど,予備の SATA 用電源コネクタがひとつも空きがない。しかも,SATA 用ケーブルもない。私の知らないうちに(PC を自分で組立てるなんてもう 15 年くらいやっていない) PC の世界も当然ながら様変わりしているわけで,内蔵 HDD の電源コネクタはいまや 4pin ならず,マザーボードとの接続も IDE ケーブルならぬ SATA ケーブルなんである。昔悩んだ IDE プライマリ/セカンダリのジッパ設定から解放されたのは嬉しかったんだけど。ふたたびヨドバシカメラに逆戻りして,4pin-SATA 電源変換ケーブル,SATA ケーブルを追加購入しなければならなかった。教訓その2:内臓周辺機器を増設するときは,あらかじめ筐体内部を確認し,追加の必要なケーブル類を洗い出しておこう。

    20110920-pcsata.jpg
    4pin-SATA 電源変換ケーブル(上)/SATA コネクタ(下)

    取得してあったシステムのバックアップは何ヶ月か前のものであり,ブログをはじめ日々更新されるデータは最新状態ではない。教訓その3:バックアップはこまめに取得し DVD-R などに焼いておこう。ディスクを「増設」するついでに,壊れた HDD 内のまだ生きているデータを漁って,使えるものは使おうということにした。今日はその苦肉の方法のメモを残しておく。

    まずは SATA のプライマリソケットに再度旧 HDD をつないでブートする。当然,fsck で悲鳴を上げて,次のメッセージとともに停止する。

    Automatic file system check failed; help!
    ERROR: ABORTING BOOT (sending SIGTERM to parent)!
    Sep 18 16:03:45 init: /bin/sh on /etc/rc terminated abnormally, going to user mode
    Enter full pathname of shell or RETURN for /bin/sh:
    

    help! は機械のお前じゃなくこっちのセリフじゃ! これに対し,Enter キーを押して,シングルユーザモードでログインする。自作プログラム,文書,ブログ記事など大事なデータを格納しているディスク・パーティションをマウントしてみる。/etc/fstab を参照し,デバイス名とマウンティングポイントを特定する。これらは利用者のインストールの仕方によって変わるので注意。

    # cat /etc/fstab
    # Device  Mountpoint FStype Options Dump Pass#
    /dev/ad4s1b  none    swap   sw      0    0
    /dev/ad4s1a  /       ufs    rw      1    1
    /dev/ad4s1g  /home   ufs    rw      2    2
    /dev/ad4s1h  /shared ufs    rw      2    2
    /dev/ad4s1f  /tmp    ufs    rw      2    2
    /dev/ad4s1d  /usr    ufs    rw      2    2
    /dev/ad4s1e  /var    ufs    rw      2    2
    /dev/acd0 /cdrom  cd9660 ro,noauto  0    0
    linprocfs /compat/linux/proc linprocfs rw 0 0
    

    私の目的とするデータは /usr/home にあるので,この二つだけをもう一度 fsck でチェックし,そのあとで mount してみる。幸いにもこれらパーティションは無事だったようである。vi などのツールは /usr/bin にあるので,それらツールを使う場合もマウントしておく必要がある。

    # fsck -y /dev/ad4s1d
    # fsck -y /dev/ad4s1g
    # /sbin/mount /dev/ad4s1d /usr
    # /sbin/mount /dev/ad4s1g /home
    

    必要なデータをアーカイブする(ここでは省略)。上記マウントができれば当該ファイルシステムは書き込みも可能なはずである。ルートパーティションはおそらく書き込み不可なので,アーカイブはマウントしたファイルシステムに格納する。アーカイブデータを別のコンピュータにコピーするには,ネットワークが使えないといけない。この時点ではネットワーク通信の準備ができていないので,次にこれを行い,取得したデータを scp (ftp でもよい) で別マシンに転送する。以下では anotherpc のユーザ user のホームディレクトリにコピーしている。anotherpc 上でも ssh が動作可能でなければならない。Mac OS など UNIX 系 OS なら OK である。

    # ifconfig msk0 inet 192.168.1.4 netmask 255.255.255.0
    # scp mydata-archives.tar.gz user@anotherpc:/Users/user/
    

    msk0 はネットワークインタフェース名であり,利用する LAN カードによって変わって来る。忘れてしまっていたら,/etc/rc.conf 中に ifconfig_XXX=... なる行があるはずなので,その XXX をネットワークインタフェース名に指定する。IP アドレスは使っていたものをそのまま使うのがよい。詳細は ifconfig のマニュアルを参照。

    必要なデータをすべて別マシンに転送し終わったら,ほっと一息。煙草で一服し,コーヒーでも飲もう。シャットダウンし,電源コードを抜く。新しく追加した HDD の SATA ケーブルをプライマリに,旧い HDD のケーブルを DVD ドライブの次のソケットあたりに繋ぎ直す。ついでに筐体内部に溜まりまくったホコリをエアスプレーで吹き払っておく。とくに冷却ファンの近辺を念入りに。これで PC の騒音がピタリと止む。

    ここからは新しい HDD に最新バージョンの FreeBSD を導入する作業となる。私は Mac で,FreeBSD 8.2-RELEASE の DVD イメージをダウンロードし,ディスクユティリティで DVD-R に書き込んで,このメディアでインストールを行った。とにかく真っ先に sendmail,POP 環境を整えた。その後,上で取得したコピーデータを再度新環境に転送してからもろもろの復旧を行った。まだまだ入れ込めていないプログラムもたくさんあってうんざりである。もう二度とやりたくない。けれども,3 年に一度はやるはめになる。次はもうやる気力が起こらないかも知れない。教訓その4:自宅でサーバ運用なんてやめたほうがよい。

    最後に。警告その1:上記方法がいつもうまく行くとは限らない。 
     

    P.S.

    復旧中に,misima 旧字旧仮名変換支援サービスを使ってくれている友人から「つかえなくなっちゃったけど,どうしたの? 体調でも悪くしてサーバ運用やめちゃったの?」とのメールをもらった。ありがとう。四苦八苦しながらもリカバリしましたよ! 教訓その5:へたに自作プログラムの公開などしないほうがよい。

    FreeBSD kern.maxdsiz

    |

    全文検索エンジン msearch で,うちのメールアーカイブをインデックス対象として 20000 通を越える大量データを処理したところ,Out of memory during request for xxxxxx bytes, total sbrk() is ... 云々のメッセージを出力して,インデックス作成が abend してしまった(abend は「夕べ」という意味のドイツ語ではなく,Abnormal End = 異常終了というコンピュータ用語である。為念。昔,夕方になると一日の業務のゴミが原因で abend するシステムバグの対策をさせられたことがあり,Abend abend と呼んでいた。関係ありませんけど)。

    これは FreeBSD の管理するプロセスのデータセグメントのサイズの制限を超過したときに出るメッセージで,プログラムが自分の仮想メモリに大量データを抱え込んでいると,この事態に陥る可能性が出て来る。ま,制限値を増やせばいいのだけれど,20000 文書程度で出てしまうのもちょっとなんとかならないかなぁと思う。文書数が増えるといずれまた再発するのは確実だからである。Google は 20 億文書インデックスを達成したとき誇らしげにその広告を出していたが,確かに誇るべき数字である。とはいえ,情報検索の世界では数千万件のデータベースは最近では常識的な規模である。高々 20000 文書で OS のメモリ制限に引っ掛かるのは改善できないものか。

    とにもかくにも対策。sysctl -a | grep maxdsiz で現状値を確認すると,536870912 だと知れ,約 500MB。だいたいその 2 倍くらいにチューンしてみる。/boot/loader.conf に次の 1 行を書き足してリブートする。

    kern.maxdsiz="1024M"
    

    sysctl で再確認すると,1073741824 に値が拡張されていた。これで msearch genindex.pl がうまく通るようになった。

    Moon Calendar

    Profile

    ISAO YASUDA。システムエンジニア。神奈川県在住。昭和 30 年代を懐かしむオヤジ。ロシアに興味があります。
    [more], [About our site]

    Notice

    この文書はフィクションであり,実在する個人,団体等とは一切関係ありません。

    R-18 指定サイトです。そのうち「18 歳以上ですか」の認証を入れる予定です。

    文書の記述内容は無保証です。不適切な表現があればコメントにてご指摘ください。

    コメント,トラックバックは,現在,運用を停止しています。ご意見等ありましたら isao@yasuda.homeip.net 宛電子メールにてお願いします。

    Links

    About this archive

    All Entries of Category FreeBSD

    Previous: Emacs

    Next: Linux

    Recent Entries in Main Index.
    All Entries in Archive Index.

    February 2012

    Sun Mon Tue Wed Thu Fri Sat
          1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29      
    Powered by Movable Type 4.1 blog counter