Open Source WEB




2006-05-31 [Haskell] I/O も Lazy ゆえに ...

Haskell では遅延評価「必要にならないと評価されない」が基本で, 入出力も例外ではない.

humuhumu.txt を読み出し,それを(関数 foo で)加工して元の humuhumu.txt ファイル に書きもどすプログラムを以下のように書いたとする.

import Data.Char
import System.IO

main = do { r <- openFile "humuhumu.txt" ReadMode
          ; s <- hGetContents r
          ; w <- openFile "humuhumu.txt" WriteMode
          ; hPutStr w (foo s)
          ; hClose w
          }

foo = map toUpper

humuhumu.txt の内容が

humuhumu is a fish.

だったとすると,実行後は

HUMUHUMU IS A FISH.

になるはずである. 実行してみると

% runhaskell foo.hs
*** Exception: humuhumu: openFile: resource busy (file is locked)

クローズされていないファイルをWriteModeでオープンしようとして 例外が発生したらしい.hGetContents は EOFを読むとファイルハンドルを クローズしてくれるはずなのだが...

明示的にクローズしてみよう.

import Data.Char
import System.IO

main = do { r <- openFile "humuhumu.txt" ReadMode
          ; s <- hGetContents r
          ; hClose r
          ; w <- openFile "humuhumu.txt" WriteMode
          ; hPutStr w (foo s)
          ; hClose w
          }

foo = map toUpper

実行してみると,

% runhaskell foo.hs
%

エラーはでないが結果は悲惨なことになっている.

% ls -l humuhumu.txt
-rw-r--r--  1 nobsun kahua 0 2006-05-31 16:29 humuhumu.txt

がーーーん.からっぽになっちゃったぁ orz

クローズする前に s は必要とされていなかったので,何も入力されていない. つまり,s は空文字だった.それを上書きしてしまった.

クローズするまえに s を評価する方法はないだろうか. 実はある.seq という特別な関数を使う.

import Data.Char
import System.IO

main = do { r <- openFile "humuhumu" ReadMode
          ; s <- hGetContents r
          ; s `seq` hClose r
          ; w <- openFile "humuhumu" WriteMode
          ; hPutStr w (foo s)
          ; hClose w
          }

foo = map toUpper

seq x y を評価すると,x を評価してから y を評価してその値を返す.

もういちど,humuhumu.txt の内容を

humuhumu is a fish.

にもどして,実行してみよう.

% runhaskell foo.hs
% cat humuhumu.txt
HUMUHUMU IS A FISH.

やった!!

とよろこぶのはまだ早い... (to be continued)

--nobsun


Name:
Comment:

There is no comment.


2006-05-30 [Tips] ログローテーション(2)

前回は他のプログラムが出力したログを ローテーションするプログラムを紹介した。 今回は、ログ出力そのものを担いつつログローテーションを行うプログラムを紹介する。

rotatelogs

Apache HTTP serverに同梱されているログプログラム。 パイプ経由で受けた情報をそのままログに吐き出す。つまり、タイムスタンプは付加しない。 以下は、Apache HTTP serverの設定ファイル内でrotatelogsを使ってログ出力を行う際の 設定例だ:

CustomLog "|bin/rotatelogs /var/logs/access_log 5M" common

この時、実際のログファイル名は access_log.1149202800 のように、 指定した名前に、最初のログエントリが書き出された時刻のエポックからの秒数が 付加されたものとなる。なお、ログファイル名に%が含まれている場合、 それを strftime(3) で処理したものが実際のファイル名として使用される。

ErrorLog "|bin/rotatelogs -l /var/logs/error_log.%Y-%m-%d-%H:%M:%S 86400"

ログファイル名は error_log.2006-05-30-01:10:15 のようになる。 第2引数はローテーションの条件を表し、数値にMがついている場合はログファイルのサイズが この値を超えた場合(単位はMB)に、単なる数値の場合は最初のログ出力からこの秒数を超えた場合に、 その時点で出力対象としていたログファイルを閉じ、次のファイルへの出力を開始する。 -lとあるのは、時刻をUTCではなく、ローカルタイムで判断するためのオプションだ このオプションは、httpd 2.0.51以降に付属のものでのみ使用できる。 また、第3引数にUTCからのオフセットを分で指定することもできる。 古いログファイルの圧縮機能や削除機能は含まれていないので、 必要なら別途crond+shスクリプトなどで対応する必要があるだろう。

cronolog

rotatelogsを基に、さらに高機能化したログプログラム。 基本的な動作はrotatelogsと同じだが、ローテーションサイクルを 明示しなくても、 ログファイル名のフォーマットから適切にローテーションを行ってくれる。 例:

CustomLog "|/usr/sbin/cronolog /www/logs/apache/access.log.%Y-%m-%d" common

この場合、例えば2006年05月29日から30日に日付が変わった瞬間に access.log.2006-05-29は閉じられ、 以降最初の出力時にaccess.log.2006-05-30が作成されてログ出力される。

また、必要なディレクトリを自動で作成したり、

CustomLog "|/usr/sbin/cronolog /www/logs/apache/%Y/%m/%d/access.log" common

最新のログファイルへのハードリンクやシンボリックリンクを作成したり といった便利な機能を備えている。 特にawstatsなどのログアナライザで日毎の解析を行いたいような場合は、 このプログラムを使った方がよいだろう。 ログ出力にタイムスタンプは付加せず、古いログの圧縮機能や削除機能が含まれていないのは rotatelogsと同様である。

multilog

daemontoolsに含まれるログプログラム。 標準入力から読み込んだデータをログとして吐くという動作は上記の2つの プログラムと同様だが、以下のような機能と特徴を備える。

  1. ログの書き出し先をファイル名ではなくディレクトリ名で指定する (ファイル名は決まっている)。
  2. tai64n形式によるタイムスタンプ付加機能
  3. パターンマッチによる入力行の選択
  4. 単一ファイルへの上書き更新
  5. 複数ログへの出力(の振り分け)
  6. コマンドの呼び出し
  7. ローテーションサイクルはサイズでのみ指定

通常はsuperviseやsvscanと組み合わせて使うが、単独で使うこともできる。 Apache HTTP serverのログ出力にmultilogを使うには、

CustomLog "|/command/multilog s16777215 n20 /www/logs/access" common

こうすると、/www/logs/access ディレクトリの下に一連のログファイルを作成する。 現在出力中のログファイル名はcurrentであり、16MB(s16777215)を超えるタイミングで、 その時刻をtai64n形式で表した文字列に".s"を付加したファイル名にリネームし、 新たにcurrentファイルを作成してログ出力を続ける。ログファイルはcurrentを 含めて20個まで(n20)で、それを超えると古いログファイルを削除する。 ディスクフルなどでログファイルの出力ができない場合は 入力をバッファリングしつつ出力を遅延する。 このため入力が失われることはない代わりに、 出力側のプログラム(この場合はApache HTTP server)をブロックする可能性がある。

ログの管理は、サーバ運用の重要な要素であるだけに、 他にも様々なプログラムがあると思う。この記事を書いている最中にも、 LogSplitterというパッケージを見つけた。 「LogSplitter is a log handler for Apache which combines the features of rotatelogs, splitlog and (in part) cronolog.」 だそうだ。

--び


Name:
Comment:

There is no comment.


2006-05-29 [Tips] ログローテーション(1)

サーバを管理していると必ずと言っていいほど必要になるのが 「ログローテーション」。 今時のたいていのOSで標準的な機能となっているため、 案外意識せずに使っている場合が多いのではないか。

ということで、 ログローテーションを実現している様々なプログラムを紹介してみる。

savelog

Debian GNU/Linux で syslogd、klogd が吐き出すログのローテーションに使われている。 debianutils パッケージに含まれているところを見ると、Debian固有のコマンドらしい。 コマンドラインオプションで全ての挙動を制御する単純なプログラムで、主にcrondが 呼び出す。機能としては

  1. 通番付加によるログローテーション
  2. ローテーションサイクルの指定(デフォルトで7世代)
  3. 古いログの圧縮(ローテーション第1世代は圧縮しない)
  4. ローテーションサイクルを超えたファイルの削除
  5. オーナー、グループ、パーミッションの指定

/etc/cron.daily/sysklogd からの抜粋:

cd /var/log
for LOG in `syslogd-listfiles`
do
   if [ -s $LOG ]; then
      savelog -g adm -m 640 -u root -c 7 $LOG >/dev/null
   fi
done

for LOG in `syslogd-listfiles --auth`
do
   if [ -f $LOG ]; then
      chown root:adm $LOG
      chmod o-rwx $LOG
   fi
done

/etc/init.d/sysklogd reload-or-restart > /dev/null

ログを吐いているプロセスにシグナルを投げる機能はないので、 別途スクリプトの中で行っているのがわかる。

機能的にはほぼ標準的なものは揃っているが、おそらくDebianでしか 使えない(他のLinuxディストリビューションについては不明)。

logrotate

たぶん一番有名なログローテーションプログラム。 一次配布元はredhatのようだが、 たいていのLinuxディストリビューションには当たり前のように入っている。 動作の制御は設定ファイルから行い、savelogの機能に加えて、

  1. ローテートを行う閾値となるログのサイズを指定
  2. ログ内容をメールで送信
  3. ローテーションの前後に起動するコマンドを指定 といった機能を備えている。manからの設定ファイルの抜粋:
"/var/log/httpd/access.log" /var/log/httpd/error.log {
      rotate 5
      mail www@my.org
      size 100k
      sharedscripts
      postrotate
          /usr/bin/killall -HUP httpd
      endscript
}

この設定ファイルをコマンドラインに指定してcrondが呼び出す。

0 4 * * *       /usr/sbin/logrotate /etc/logrotate.conf

Linuxディストリビューションで意識してログローテーションを行う際には これを使うのが普通のやり方になると思う。

newsyslog

*BSDで標準的に使われているログローテーションプログラム。 機能的にはsavelogとlogrotateの中間くらいか。 メールの送信機能やコマンドの呼び出し機能はないが、 シグナルの送出機能はある。動作の制御は設定ファイルで行い、 crondが呼び出す。設定ファイル /etc/newsyslog.conf の例:

/var/log/aculog         uucp:dialer     640  7    *    24   Z
/var/log/authlog                        600  5    100  *    Z
/var/log/cron           root:wheel      600  3    100  *    Z
/var/log/kerberos.log                   640  7    *    24   ZN
/var/log/lpd-errs                       640  7    100  *    Z
/var/log/maillog                        600  7    *    24   Z
/var/log/messages                       644  10   250  *    Z
/var/log/wtmp           root:utmp       664  7    *    168  ZBN
/var/log/wtmpx          root:utmp       664  7    *    168  ZBN
/var/log/xferlog                        640  7    250  *    Z
/var/log/httpd/access_log               644  20  4096  *    Z   /var/run/httpd.pid
/var/log/httpd/error_log                644  20  1024  *    Z   /var/run/httpd.pid

いかにも昔ながらのUNIXという風情の設定ファイルだ。 NetBSDの場合、標準的には毎正時に呼び出している。

0 * * * *       /usr/bin/newsyslog

*BSDユーザは当たり前のように使っていると思うが(筆者もNetBSDなサーバではこれを使っている)、 Linuxユーザはおそらく目にする機会もあまりないのではないか。 昔 Slackware に newsyslog が含まれていたようなかすかな 記憶があるのだが、勘違いかもしれない。

他にもきっとあると思うのだが、筆者が実際に使ったことがあるのはこのくらいだ。 面白いもの(有益なもの)があればぜひ教えて欲しい。

次回はログ出力そのものの面倒を見つつログローテーションを実現するプログラムを紹介する。

--び


Name:
Comment:

There is no comment.


2006-05-26 [HTML] HTMLでツールチップを表示する

JavaScriptを使う方法もあるが、最もシンプルなのはtitle属性を使う方法だ。

<img src="humuhumunukunukuapuaa.gif" alt="モンガラカワハギ" title="モンガラカワハギ"> 

任意の範囲に表示したければspanを使えば良い。

<p>任意の<span title="ここの部分だけにツールチップが表示されます">範囲</span>に表示したければspanを使えば良い。</p>

--yasuyuki


Name:
Comment:

There is no comment.


2006-05-25 [MacBook Pro] MacBook Pro 15インチで、2本指による右クリックを実現する

MacBook 13.3インチから可能になった2本指による右クリック。MacBook Pro 15インチでもこれを可能にする方法がある。ただし自己責任にてお願いします。

http://forum.osx86project.org/index.php?showtopic=17685&pid=118022&st=20&

上記からMachineSettings.framework.zipをダウンロードして展開するとMachineSettings.frameworkフォルダが得られる。

これを/System/Library/PrivateFrameworks/MachineSettings.frameworkフォルダにすべて上書きして再起動すれば良い。

注意: /System/Library/PrivateFrameworks/MachineSettings.frameworkをどこかにバックアップしておかないと元にもどせなくなる。

--yasuyuki


Name:
Comment:
び: (Thu Jun 29 07:46:54 2006 )
Mac OS X 10.4.7から、MacBook Proでもこの機能が使えるようになったらしいです。
自分のはProじゃないので確認はしていませんが。


2006-05-24 [Tips] 安全にファイルを更新する

いつ参照されるかわからないファイルを安全に(つまり、inconsistentな状態を見せることなく)更新したい。 ついでにバックアップも取っておきたい。そんな時の定番。更新したいファイルがhogeだとすると、

% ln hoge hoge.old && mv hoge.new hoge

とするのが正しい。気にしないでけっこう直接書き換えちゃったりするものだが、 タイミングによっては痛い目にあう。他のデーモンから呼ばれるコマンドファイルは特に危険。

--び


Name:
Comment:

There is no comment.


2006-05-23 [GCC] 有効なマクロ定義をダンプする

ややこしいマクロ定義や #if 〜 #endif がネストしまくっているようなCコードを追っていると、 いったいどのマクロ定義が有効なのか、その値はいくつなのか追い切れなくなる時がある。 そんな時は gcc に -c の代わりに -E -dM をわたしてみるとよい。

% gcc -E -dM hoge.c 
#define __used __attribute__((__used__))
#define __DBL_MIN_EXP__ (-1021)
#define __weak_extern(sym) __asm__(".weak " _C_LABEL_STRING(#sym));
#define __CUSERID_DECLARED 
[中略]
#define __FLT_MANT_DIG__ 24
#define __VERSION__ "3.3.3 (NetBSD nb3 20040520)"
#define ___RENAME(x) __asm__(___STRING(_C_LABEL(x)))
#define _I386_INT_TYPES_H_ 

特定のコンパイル条件下ではなく、単に「システムではどんなマクロが定義されているのか」 を知りたいだけなら、これでよい。

% echo|gcc -E -dM -
#define __DBL_MIN_EXP__ (-1021)
#define __FLT_MIN__ 1.17549435e-38F
#define __CHAR_BIT__ 8
#define __WCHAR_MAX__ 2147483647
#define __DBL_DENORM_MIN__ 4.9406564584124654e-324
#define __FLT_EVAL_METHOD__ 2
#define __i386__ 1
#define __SIZE_TYPE__ unsigned int
#define __ELF__ 1
#define __DBL_MIN_10_EXP__ (-307)
#define __FINITE_MATH_ONLY__ 0
#define __GNUC_PATCHLEVEL__ 3
#define __FLT_RADIX__ 2
#define __LDBL_EPSILON__ 1.08420217248550443401e-19L
#define __SHRT_MAX__ 32767
#define __LDBL_MAX__ 1.18973149535723176502e+4932L
#define __LDBL_MAX_EXP__ 16384
#define __LONG_MAX__ 2147483647L
#define __SCHAR_MAX__ 127
#define __DBL_DIG__ 15
#define __USER_LABEL_PREFIX__ 
#define __STDC_HOSTED__ 1
#define __LDBL_MANT_DIG__ 64
#define __FLT_EPSILON__ 1.19209290e-7F
#define __NetBSD__ 1
#define __LDBL_MIN__ 3.36210314311209350626e-4932L
#define __WCHAR_TYPE__ int
#define __FLT_DIG__ 6
#define __FLT_MAX_10_EXP__ 38
#define __INT_MAX__ 2147483647
#define __FLT_MAX_EXP__ 128
#define __DECIMAL_DIG__ 21
#define __DBL_MANT_DIG__ 53
#define __WINT_TYPE__ int
#define __GNUC__ 3
#define __LDBL_MIN_EXP__ (-16381)
#define __LDBL_MAX_10_EXP__ 4932
#define __DBL_EPSILON__ 2.2204460492503131e-16
#define __DBL_MAX__ 1.7976931348623157e+308
#define __DBL_MAX_EXP__ 1024
#define __FLT_DENORM_MIN__ 1.40129846e-45F
#define __LONG_LONG_MAX__ 9223372036854775807LL
#define __FLT_MAX__ 3.40282347e+38F
#define __GXX_ABI_VERSION 102
#define __FLT_MIN_10_EXP__ (-37)
#define __FLT_MIN_EXP__ (-125)
#define i386 1
#define __GNUC_MINOR__ 3
#define __DBL_MAX_10_EXP__ 308
#define __LDBL_DENORM_MIN__ 3.64519953188247460253e-4951L
#define __DBL_MIN__ 2.2250738585072014e-308
#define __PTRDIFF_TYPE__ int
#define __tune_i386__ 1
#define __LDBL_MIN_10_EXP__ (-4931)
#define __REGISTER_PREFIX__ 
#define __LDBL_DIG__ 18
#define __NO_INLINE__ 1
#define __i386 1
#define __FLT_MANT_DIG__ 24
#define __VERSION__ "3.3.3 (NetBSD nb3 20040520)"

OSやアーキテクチャを識別するためのマクロや、 数値型の最大値/最小値などを調べるのに便利である。

この元ネタ、どこかで教わったのだと思うのだが、 どこでだったかどうしても思い出せない。 gccのマニュアルを見ても

-dM
-fdump-rtl-mach
 Dump after performing the machine dependent reorganization
 pass, to file.35.mach.

などと書いてあって、とうてい自分で見つけたとは思えないのだけど。

--び


Name:
Comment:

There is no comment.


2006-05-22 [Gauche] Gauche で簡易 split を書く

Mac OS Xには標準的にUNIX系コマンドがひとそろい含まれているのだが、 思わぬ落とし穴にハマることがある。あるとき、 10GB超の単一ファイルを4GBごとに分割する必要に迫られた。 UNIX使いなら普通splitを使うだろう。ところが...

% split -b 4096m somelarge.file somelarge.
split: too many files.

調べてみるとなぜか250KB前後のファイルに分割されてしまう。 そりゃtoo manyになりますよ。

急いでいたこともあって、仕方なく以下のスクリプトをでっち上げた。

#!/usr/bin/env gosh
;;; -*- mode: scheme; coding: utf-8-unix -*-

(use gauche.uvector)
(use util.match)

(define (copy-port-partially in out size . args)
  (let* ((buffer (get-optional args (make-u8vector 65536)))
         (bufsize (u8vector-length buffer)))
    (let loop ((total 0)
               (rsize (read-block! buffer in 0 (min size bufsize))))
      (cond ((= total size) total)
            ((> total size) (errorf "size ~s required but got total %~s" size total))
            ((eof-object? rsize) (if (> total 0) total rsize))
            (else
             (write-block buffer out 0 rsize)
             (loop (+ total rsize)
                   (read-block! buffer in 0 (min (- size total rsize) bufsize))))))))

(define (split-in-bytes size in base . args)
  (let loop ((num 0))
    (let* ((fname  (format "~a.~v,'0d" base 6 num))
           (ret (call-with-output-file fname
                  (lambda (out)
                    (with-port-locking out
                      (lambda ()
                        (apply copy-port-partially in out size args)))))))
      (if (eof-object? ret)
          (sys-unlink fname)
          (loop (+ num 1))))))

(define (main args)
  (let1 buf (make-u8vector 131072)
    (match args
      ((_ size infile base)
       (call-with-input-file infile
         (lambda (in)
           (with-port-locking in
             (lambda ()
               (split-in-bytes (string->number size) in base buf))))))))
  0)

1個余分に0byteのファイルが出来てしまうため、 最後にそれを消しているあたりがどうにも香ばしい。

さて、ここからは余談。 とりあえずこのスクリプトでその場は凌げたのだが、どうも納得がいかない。 幸い、ソースコードは公開されているので(ADCの登録が必要)、調べてみた。 splitコマンドのコードはtext_cmds-47プロジェクトに含まれている。

long     bytecnt;                       /* Byte count to split on. */

ちなみに /usr/include/ppc/limits.h ではLONG_MAXは

#define __LONG_MAX__ 2147483647L

となっている。 つまり、Mac OS X標準のsplitは2GBを超えるファイルサイズに分割することができない。

この制限、64bitファイルシステムを持つOSとしてはあんまりだと思うので、 NetBSDから/usr/src/usr.bin/split/split.c を持ってきてコンパイルし、 /usr/local/bin に入れてしまった。上記のスクリプトでもよかったのだが、 やっぱり遅いし、真っ当なsplitコマンドを入れておきたかったのだ。 ちなみに NetBSD版split ではbytecntはoff_tで、符号付き64bit整数だから、 64bitファイルシステムで許容される最大サイズまで正常に扱える(はず)。

--び


Name:
Comment:

There is no comment.


2006-05-19 [Tips] launchdでデーモンを起動する

先日の記事の最後で、 launchdでsvscanを起動してみる、と書いたのだが、その後いろいろ調べていくと、 どうやらsvscanを起動する手段として launchd はあまり的確ではない。 manに含まれる関連マニュアルやAppleのドキュメントを読む限り、launchd は init+cron+inetd といったプログラムのようだから、svscan の下で動かすデーモン類を起動することを考えた方が適切だと思われる。

ということではなはだ言い訳がましいが、急遽

  • 非特権ユーザ "kahua" の権限で
  • Kahuaをデーモンとして起動する

ことを試してみる。

launchd はシステムの起動から終了まで動作する通常のデーモンプログラムの他に、 ユーザがログインする時に起動し、ログアウトする時に終了するデーモンプログラムも 扱うことができる。今回は前者なので、設定ファイルを /Library/LaunchDaemons もしくは ~/Library/LaunchDaemons に置くことになる。設定ファイルは Mac OS X ではおなじみの XML 形式のプロパティリストファイルである。 システム付属の Property List Editor を使って作成してもよいし、 この程度なら使い慣れたエディタの方が楽かもしれない。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>UserName</key>
    <string>kahua</string>
    <key>GroupName</key>
    <string>kahua</string>
    <key>Label</key>
    <string>org.kahua.Kahua</string>
    <key>OnDemand</key>
    <false/>
    <key>Program</key>
    <string>/usr/local/kahua/bin/kahua-spvr</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/local/kahua/bin/kahua-spvr</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>ServiceDescription</key>
    <string>Kahua Supervisor Daemon</string>
    <key>StandardOutPath</key>
    <string>/usr/local/kahua/var/kahua/logs/kahua-stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/usr/local/kahua/var/kahua/logs/kahua-stderr.log</string>
  </dict>
</plist>

これを /Library/LaunchDaemons フォルダに org.kahua.Kahua.plist というファイル名で置き、

% sudo launchctl load /Library/Launch/Daemons/org.kahua.Kahua.plist

とするとKahuaが起動してくる。

% sudo launchctl list
com.apple.KernelEventAgent
com.apple.mDNSResponder
com.apple.nibindd
com.apple.periodic-daily
com.apple.periodic-monthly
com.apple.periodic-weekly
com.apple.portmap
com.apple.syslogd
com.vix.cron
org.postfix.master
org.xinetd.xinetd
org.kahua.Kahua

org.kahua.Kahua というラベル名で起動しているのがわかる。 これで、OS起動時にKahuaも同時に起動することができるようになる。 rc系の起動スクリプトに慣れている人間にとっては何だか回りくどい方法だが、 Mac OS Xでは今後これが主流になるようなので、慣れておくのも一興だろう。

--び


Name:
Comment:

There is no comment.


2006-05-18 [IMAP] IMAPで、Message-Idが同じメールを削除する

前提: IMAPサーバーの同じディレクトリーに、同じMessage-Idのメールが複数存在する。重複したぶんだけ削除したい。

解法: Mozilla ThunderbirdのRemove Duplicate Messagesプラグインを使う。

実はこの用途のためだけにMozilla Thunderbirdをインストールしていたりして...

--yasuyuki


Name:
Comment:

There is no comment.


2006-05-17 [IMAP] POPで受信したMH形式のメールをIMAPサーバーにアップロードする

前提: POPで受信した大量のメールが、ローカルディスクにMH形式で保存されている。これを新たなIMAPサーバーにアップロードして保存したい。

解法: MHとIMAP両方に対応しているMUAを使う。ex. Sylpheed

Ubuntu Linux 5.1に付属のSylpheed 2.2.0beta1だとなぜかMHからIMAPへのコピーができなかったので、 Windows版Sylpheed 2.2.4を使った。

(筆者の約8年分のメールの総容量は1GBほどであった)

--yasuyuki


Name:
Comment:

There is no comment.


2006-05-16 [Tips] Mac OS X で svscan を自動起動する

Mac OS X のrcの仕組みは他の UNIXen に比べてかなり独特で、 デーモンを自動起動するためには Startup Item Bundle なるものを作成することになる。

まずは /Library/StartupItems フォルダ(なければ作る)に、Svscan フォルダを掘る。

% sudo mkdir -p /Library/StartupItems/Svscan

ここに以下のようなshスクリプトをフォルダと同じ名前で置く。もちろん、実行ビットを立てておく。

#!/bin/sh

##
# Svscan (from daemontools).
##
PKG_BIN=/command
SVSCAN_CMD=${PKG_BIN}/svscan
SVC_CMD=${PKG_BIN}/svc
SERVICE_DIR=/service

. /etc/rc.common

StartService ()
{
    if ! pid=$(GetPID svscan); then
        ConsoleMessage "Starting svscan"
        env PATH=${PKG_BIN}:${PATH} ${SVSCAN_CMD} ${SERVICE_DIR} \
                & echo $! >/var/run/svscan.pid
    fi
}

StopService ()
{
    if pid=$(GetPID svscan); then
        ConsoleMessage "Stopping svscan"
        kill -TERM "${pid}" && rm -f /var/run/svscan.pid
        ${SVC_CMD} -dx ${SERVICE_DIR}/* ${SERVICE_DIR}/*/log 2>/dev/null
    else
        echo "svscan is not running."
    fi
}

RestartService ()
{
    StopService
    StartService
}

RunService "$1"

やっていることは昨日のNetBSD版のrcファイルと同じだ。

もうひとつ、同じフォルダに以下のようなファイルを StartupParameters.plist という名前で配置する

{
    Description = "Svscan daemon manager"; 
    Provides = ("Svscan"); 
    Uses = ("Network"); 
}

このあたりのファイル構成については、Appleの 「Creating a Startup Item」 にざっと説明されている。また、システム関係の Startup Item Bundle が /System/Library/StartupItems にあるので、 これをいろいろ眺めてみるのも参考になるだろう。

これで自動起動/終了の準備は完了だ。コマンドラインから確かめてみる。 Startup Item Bundleを手動で起動するには、SystemStarterコマンドを使う。

% sudo SystemStarter start Svscan
% sudo SystemStarter stop Svscan

なお、2番目の引数として渡すのは、StartupParameters.plist の Provides に渡している 値のどれかである(上記の場合はSvscan)。ここにStartup Bundleフォルダと違う値を入れ、 SystemStarterの第2引数にStartup Item Bundleのフォルダ名を渡して「なぜ起動しない」と悩まないように。

さて、実はMac OS X 10.4からは、デーモンの起動終了を管理するために、 別の新しい仕組みがある... らしい。 筆者は上記のStartup Item Bundleを10.2の頃から使い回しているので、 10.4からの新しい仕組み "launchd" についてはよく知らないのだ。 せっかくなので、次回はlaunchdを使ってsvscanを起動してみよう(このネタまだ引っ張るのか...)。

--び


Name:
Comment:

There is no comment.


2006-05-15 [Tips] NetBSD で svscan を自動起動する

daemontools は慣れるとけっこう便利なのだが、 svscan は自分ではバックエンドに行ってくれないし、 プロセスIDを /var/run の下に書き出すような親切もしてくれないため、 OS起動時に svscan を自動的に起動するにはそれなりの準備がいる。 例えば、筆者は運用しているNetBSDマシンで、以下のようなrcファイルを書いている。

#!/bin/sh
#
#
#
# PROVIDE: svscan
# REQUIRE: NETWORKING
# BEFORE: ntpdate
#

. /etc/rc.subr

name="svscan"
rcvar="$name"
start_cmd="${name}_start"
stop_cmd="${name}_stop"

PATH=/command:${PATH}
svscan_start()
{
        echo Starting "${name}."
        sh -c '/command/svscan /service & echo $!>/var/run/svscan.pid'
}

svscan_stop()
{
        if [ -f /var/run/svscan.pid ]; then
                echo Stopping "${name}."
                kill -TERM `cat /var/run/svscan.pid` \
                && rm -f /var/run/svscan.pid \
                && /command/svc -dx /service/* /service/*/log 2>/dev/null
                /usr/bin/true
        fi
}

load_rc_config $name
run_rc_command "$1"

これを /etc/rc.d/svscan に置き(別の場所からこの名前でシンボリックリンクを張ってもよい)、 /etc/rc.conf に

svscan=YES

とすればNetBSD起動時に自動的にsvscanが起動する。手動で起動、終了したい時は、

# /etc/rc.d/svscan start
Starting svscan.
# /etc/rc.d/svscan stop
Stopping svscan.

とすればよい。

なぜREQUIRE: NETWORKINGで、BEFORE: ntpdateかと言えば、 このホストではsvscan配下でdnscacheやtinydnsなどを動かし、 ntpdateでプロバイダのNTPサーバと同期をとっているからだ。

なお、どういう順番でrcスクリプトが起動されるかを確認するには、

% rcorder -s nostart /etc/rc.d/*

とすればよい(実際 /etc/rc ではそうやって起動順序を決めている)。

--び


Name:
Comment:

There is no comment.


2006-05-12 [Tips] daemontools を使って Kahua を運用する

筆者はdjbのソフトウェアの愛好者なので、当然個人サイトの Kahua の運用には daemontools を使っている。 /usr/local/kahua の下に Kahua が、 /package の流儀に従って daemontools がインストールされていて、 kahua ユーザと savelog ユーザが存在することを前提として、 こんな感じになる。 なお、/usr/local/service/kahua は別の場所でもかまわない。

# mkdir -p /usr/local/service/kahua
# cat >/usr/local/service/kahua/run <<EOF
#!/bin/sh
exec 2>&1
exec env - PATH=/usr/local/kahua/bin/kahua:/command \
setuidgid kahua kahua-spvr
EOF
# chmod +x /usr/local/service/kahua/run
# mkdir -p /usr/local/service/kahua/log
# cat >/usr/local/service/kahua/log/run <<EOF
#!/bin/sh
exec /command/setuidgid savelog /command/multilog t ./main
EOF
# chmod +x /usr/local/service/kahua/log/run
# mkdir -p /usr/local/service/kahua/log/main
# chown savelog /usr/local/service/kahua/log/main

準備完了。 svscanが監視しているディレクトリに /usr/local/service/kahua へのシンボリックリンクを張ってやれば、 kahua-spvr と、その標準出力、標準エラー出力を受ける multilog とが 起動する。

# ln -s /usr/local/service/kahua /service

すぐ起動してくるはずなので、すかさず確認。

# /command/svstat /service/kahua /service/kahua/log
/service/kahua: up (pid 8563) 13 seconds
/service/kahua/log: up (pid 8564) 13 seconds

この方法の素敵なところは、

  • kahua-spvr.scm や kahua-server.scm が何かの理由で死んだ時、 運が良ければ標準エラー出力に gosh が吐いた悲鳴を、タイムスタンプ付きで確実にログに記録できる。
  • multilogの機能により、ログは自動的にローテートされる。
  • kahua-spvrが死んでも、自動的に再起動してくれる。
  • すでに daemontools を使っているサイトなら、 システムごとに微妙に流儀の違う /etc/rc に悩まされなくて済む。

上記の例ではやっていないが、ログの内容を判別してメールで知らせるような仕組みも multilog の機能でできるはず。

--び


Name:
Comment:

There is no comment.


2006-05-11 [Haskell] 簡易版 paste

Lisper である sakae さんからメールをいただいた.

Haskell本が出たというので、この連休にちょっと練習してみました。

とのこと.\(^o^)/

Haskellで書いた unix コマンド paste の簡易版のコードを送っていただいた. OSS-WEB で公開してもよいとのことだったので,literate comment 形式に 変更して載せます(プログラムコードそのものには変更を加えていません).

#!/usr/bin/env runhaskell

usage: paste.lhs file1 file2 ..

> module Main where
> import System
> import System.IO
> 
> main = do args <- getArgs
>           hl <- mapM (\f -> openFile f ReadMode) args
>           paste hl
>           mapM_ hClose hl
> 
> paste ::   [Handle] -> IO ()
> paste hl = do
>   s <- mapM fetchLine hl
>   if all (=="") s
>      then return ()
>      else do
>           putStrLn $ join "\t" s
>           paste hl
> 
> fetchLine :: Handle -> IO [Char]
> fetchLine h = do
>     eof <- hIsEOF h
>     if eof 
>        then return ""
>        else do
>             x <- (hGetLine h)
>             return x
> 
> join ::       String -> [String] -> String
> join t []     = ""
> join t (s:[]) = s
> join t (s:sc) = s ++ t ++ join t sc

で,私も書いてみた.

#!/usr/bin/env runhaskell

usage: paste2.lhs file1 file2 ...

\begin{code}
module Main where

import Data.List
import System

main :: IO ()
main =   getArgs
     >>= mapM readFile
     >>= putStr . unlines . map (concat . intersperse "\t") . zips "" . map lines

zips :: a -> [[a]] -> [[a]]
zips d xs | all null xs = []
          | otherwise   = case unzip $ map (hdtl d) xs of
                           (ys,zs) -> ys : zips d zs

hdtl :: a -> [a] -> (a,[a])
hdtl d []     = (d,[])
hdtl _ (x:xs) = (x,xs)

\end{code}

(2006-05-15):コード修正 (thanks [1..100]>>=pen さん)

--nobsun


Name:
Comment:
[1..100]>>=pen: (Thu May 11 14:24:03 2006 )
zips を短くしてみました。
http://hpcgi2.nifty.com/1to100pen/wiki/wiki.cgi?p=%CB%E8%C6%FCHaskell の 2006-05-11
ところで 「map (hdtl d) xs of」のところが「map (f d) xs of」になってます。


2006-05-10 [JavaScript] 漢字と英数の混在文字列から入力フィールドの桁数を得たい

HTMLフォームを動的に生成するときに、元の文字列がぴったり収まるように入力フィールド(<INPUT type="text" ...>)のサイズを決めたい。

条件: ループ禁止

JavaScriptによるアレゲな回答。

function getMBLength(str) {
    var rep = encodeURI(str.replace(/[\x20\x22\x25\x3c\x3e\x5b\x5c\x5d\x5e\x60\x7b\x7c\x7d]/g, '.'));
    return rep.replace(/%(\d|[A-Z0-9]){2}%(\d|[A-Z0-9]){2}%(\d|[A-Z0-9]){2}/g, "..").length;
}

1行目で1バイト文字のうちエスケープされる文字を'.'に置換しencodeURIでエスケープしておく。 encodeURIは漢字部分をUTF-8にエスケープする。

2行目でエスケープされた部分を'..'に置換して長さを調べる。

(サイズを調べてもプロポーショナルフォントだと意味ないorz)

--yasuyuki


Name:
Comment:

There is no comment.


2006-05-09 [quiz] 漢字と英数の混在文字列から入力フィールドの桁数を得たい

HTMLフォームを動的に生成するときに、元の文字列がぴったり収まるように入力フィールド(<INPUT type="text" ...>)のサイズを決めたい。

条件: ループ禁止

JavaScriptによるアレゲな回答は明日。

--yasuyuki


Name:
Comment:

There is no comment.


2006-05-08 [DNS] ルートサーバを調べる

ネットワークの管理をしていると、 時々DNSのルートサーバのアドレス一覧が欲しくなることがある。 正しくはwww.root-servers.orgを見るのだろうが、 djbdnsに含まれるコマンドを使うと、こんな風に手軽に一覧を得られる。

% dnsqr ns . 
2 .:
436 bytes, 1+13+0+13 records, response, noerror
query: 2 .
answer: . 99841 NS j.root-servers.net
answer: . 99841 NS k.root-servers.net
answer: . 99841 NS l.root-servers.net
answer: . 99841 NS m.root-servers.net
answer: . 99841 NS a.root-servers.net
answer: . 99841 NS b.root-servers.net
answer: . 99841 NS c.root-servers.net
answer: . 99841 NS d.root-servers.net
answer: . 99841 NS e.root-servers.net
answer: . 99841 NS f.root-servers.net
answer: . 99841 NS g.root-servers.net
answer: . 99841 NS h.root-servers.net
answer: . 99841 NS i.root-servers.net

参照しているDNSキャッシュサーバによっては、 additionalで各サーバのAレコードがいっしょに 得られることもある。 また、BINDに含まれるdigでも同じことができる。

% dig . ns

; <<>> DiG 9.2.2 <<>> . ns
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18657
;; flags: qr rd ra; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;.                              IN      NS

;; ANSWER SECTION:
.                       518400  IN      NS      A.ROOT-SERVERS.NET.
.                       518400  IN      NS      H.ROOT-SERVERS.NET.
.                       518400  IN      NS      C.ROOT-SERVERS.NET.
.                       518400  IN      NS      G.ROOT-SERVERS.NET.
.                       518400  IN      NS      F.ROOT-SERVERS.NET.
.                       518400  IN      NS      B.ROOT-SERVERS.NET.
.                       518400  IN      NS      J.ROOT-SERVERS.NET.
.                       518400  IN      NS      K.ROOT-SERVERS.NET.
.                       518400  IN      NS      L.ROOT-SERVERS.NET.
.                       518400  IN      NS      M.ROOT-SERVERS.NET.
.                       518400  IN      NS      I.ROOT-SERVERS.NET.
.                       518400  IN      NS      E.ROOT-SERVERS.NET.
.                       518400  IN      NS      D.ROOT-SERVERS.NET.

;; Query time: 204 msec
;; SERVER: 192.168.0.5#53(192.168.0.5)
;; WHEN: Thu May  4 13:39:01 2006
;; MSG SIZE  rcvd: 228

で、ここからが本題。 djbdnsに含まれるdnscacheが参照するルートサーバ一覧は $(ROOT)/servers/@ というファイルに記述されているのだが、 標準の tarball からインストールした場合、 古いアドレスが含まれたものが使われてしまう。 これを手軽に更新したい。

% dnsqr ns . |awk '$1~/^answer:/{print $5}'|xargs dnsip|tr -d ' '
128.63.2.53
192.36.148.17
192.58.128.30
193.0.14.129
198.32.64.12
202.12.27.33
198.41.0.4
192.228.79.201
192.33.4.12
128.8.10.90
192.203.230.10
192.5.5.241
192.112.36.4

こんなワンライナーでイケる。 ちなみにBINDで使うルートゾーンファイルは オフィシャルなもの が存在するのでそれをとってくればよい。

DNS関係の診断ツールとしては、digの方が標準的だとは思うが、 個人的にはdjbdnsに含まれるツール類の方が単純明快で好きだ。 dnscacheやtinydnsを使うのだけがdjbdnsを導入する理由ではない。

--び


Name:
Comment:

There is no comment.


2006-05-02 [Gauche] tr もどきをGaucheで書いてみた

2006-04-28 の tr もどきをGaucheで書いてみた. 短いですねぇ...

(use gauche.collection)
(define ((add-entry k v s) q) (if (char=? k q) v (s q)))
(define (tr set1 set2 str) (coerce-to <string> (map (fold add-entry identity set1 set2) str)))

あっ.add-entry の定義の記法は,Gauche-0.8.7 では

  • トップレベルでは使えるけど,内部定義には使えない
  • なにより,正式にサポートされている機能ではなく,将来にわたって使えることは保証されていない.

ので注意.

--nobsun


Name:
Comment:
shiro: (Sat May 6 18:40:23 2006 )
(use text.tr)
…では、趣旨に外れますかな。


2006-05-01 [Haskell] ポイントフリースタイル

関数を定義するとき,仮引数を使わずに既存の関数を組合せるだけで 関数を定義するスタイルのことをポイントフリースタイルといいます.

自明な例は

foo x = bar x

なら

foo = bar

です.また,

hoge x = huga (hage x)

なら

hoge x = (huga . hage) x

なので,

hoge = huga. hage

と書けます.

2006-04-28の tr ポイントフリースタイルに変換してみよう.

tr set1 set2 = map trans
             ↓ trans を定義で展開する
tr set1 set2 = map (foldl (flip $ uncurry add) id (zip set1 set2))
             ↓ まず set2 をはじきだす.
tr set1 set2 = (map . (foldl (flip $ uncurry add) id)) ((zip set1) set2)
             ↓ もう一息
tr set1 set2 = ((map . (foldl (flip $ uncurry add) id)) . (zip set1)) set2
             ↓ set2 を消す
tr set1      = (map . foldl (flip $ uncurry add) id) . (zip set1)
             ↓ つぎに set1 をはじきだす.関数合成演算子をセクションにして前置
tr set1      = (.) (map . foldl (flip $ uncurry add) id) (zip set1)
             ↓ もう一息
tr set1      = ((.) (map . foldl (flip $ uncurry add) id) . zip) set1
             ↓ set1 を消す
tr           = (.) (map . foldl (flip $ uncurry add) id) . zip

ポイントフリースタイルになった定義をぐっとにらんで何をやっているか すぐに理解できればあなたはもう上級者(何の!?).

--nobsun


Name:
Comment:
takatoh: (Wed May 10 02:26:21 2006 )
すっきり書けますねぇ。やってみました。
http://d.hatena.ne.jp/takatoh/20060509
nobsun: (Wed May 10 05:48:28 2006 )
set1 をはじき出すところで関数合成演算子をセクションにして
前置していますが,この変換は括弧のネストが深くならないように
するためです.括弧のネストを気にしないのなら,

x + y ==> (+) x y という変換のかわりに
x + y ==> (x +) y という部分適用セクションを使った変換ができます.

それを適用すると

tr set1      = (map . foldl (flip $ uncurry add) id) . (zip set1)
             ↓ セクション
tr set1      = ((map . foldl (flip $ uncurry add) id) .) (zip set1)
             ↓ 
tr set1      = (((map . foldl (flip $ uncurry add) id) .) . zip) set1
             ↓ set1 を消す
tr           = ((map . foldl (flip $ uncurry add) id) .) . zip


このサイトは、 IPA の「平成15年度オープンソフトウエア活用基盤整備事業」 の委託事業として開発されたKahuaで試験的に運用しております。

Copyright (c) 2004-2007 株式会社タイムインターメディア About Us