Perlモジュールが入っているか調べる2つの簡単なやり方

 Perlのモジュールがインストールされているかを調べる方法の中でも初心者向けのものを書き残しておきます。


 Perlでスクリプトを書いているとよく自分が使おうとしているモジュールがインストールされているのか調べたくなる時がやってきます。
 僕の知人にも最近Perlを使い始めた人が居まして、彼から今日まさにこのことについて質問されたので、彼がまた同じことを聞いてきた時に・或いは彼以外の人間がPerlを使い始めた時に答えやすいようにここに記録を残しておきたいと思います。


 さて、本題に入りましょう。Perlのモジュールがインストールされているか調べる方法は幾つかありますが、ここでは2つ挙げておきます。

  • 忙しい人向け
  • インストールされているモジュールの.pmファイルのパスを調べたい人向け


 この2つですね。他にも色々と手はありますが、まあ初心者向けの比較的簡単な手ということでひとつ。そうそう、今回の方法の中でも後者の方はFreeBSDLinux向けです。Windowsでは利用できないと思いますので悪しからず。

忙しい人向け

 「もうとにかく大急ぎでモジュールがあるか調べたい!」或いは「待ち時間があるなんて死んでも嫌!!」という人向けです。
 シェル上で以下を実行してください。

perl -Mモジュール名 -e ''

 はい、これで「モジュール名」で指定したモジュールが@INCにインストールされているか調べる事が出来ます。判別の方法もシンプルです。

何もメッセージが出なければモジュールが入っている
エラーメッセージが出ればモジュールは入っていない

 とても解りやすいですね。僕は単にモジュールが入っているかだけ知りたい時には良くこの手を使っています。他にも

perldoc -m モジュール名

 とperldocを実行して、

モジュールに関する説明が出てくればインストール済み
エラーメッセージが出てくればインストールされていない

 という調べ方もあります。こちらはモジュールがインストールされていた時にそのモジュールの説明が出てくるので、「もうとにかく大急ぎでモジュールがあるか調べたい!」という人みたいな焦っている人には向かないかも知れません(笑

インストールされているモジュールの.pmファイルのパスを調べたい人向け

 「モジュールのソースコードがインストールされたファイルパスが知りたい」人向けです。小見出しと言う事が被ったな(笑 まあいいや。こちらは少々時間が掛かります。
 シェル上で以下を実行してください。

find `perl -e 'print "@INC";'` | less


 こちらは@INC*1に置いてあるファイル一覧をfindで検索、全表示しています。findのオプションを上手く使えば特定の名前を含むモジュールだけを調べる事が出来ますので、そちらも試してみてください。
 例えばFile::Findのソースコードがどこに入っているか確認する時には……

# find `perl -e 'print "@INC";'` -name "Find.pm"|less 
/usr/local/lib/perl5/site_perl/5.8.9/Pod/Find.pm
/usr/local/lib/perl5/site_perl/5.8.9/Module/Install/Admin/Find.pm
/usr/local/lib/perl5/site_perl/5.8.9/Module/Find.pm
/usr/local/lib/perl5/site_perl/5.8.9/PPI/Find.pm
/usr/local/lib/perl5/site_perl/5.8.9/Feed/Find.pm
/usr/local/lib/perl5/5.8.9/File/Find.pm
/usr/local/lib/perl5/5.8.9/Pod/Find.pm
#

 この様に、::で区切られた最後のブロックを検索対象とします。例の場合では太字の行がFile::Findのソースコードのパスになります。

 ただし、ひとつだけ注意しておかなければなりません。@INCの検索対象はカレントディレクトリも含むため、コマンドを実行するカレントディレクトリ次第ではfindが延々と続いてしまう可能性があります。

 この方法を使う場合にはマシンのリソースとカレントディレクトリにご注意ください。

# perl -e 'map { print "$_\n"; } @INC;'
/usr/local/lib/perl5/5.8.9/BSDPAN
/usr/local/lib/perl5/site_perl/5.8.9/mach
/usr/local/lib/perl5/site_perl/5.8.9
/usr/local/lib/perl5/5.8.9/mach
/usr/local/lib/perl5/5.8.9
. <--- カレントディレクトリが入っている!

おわりに

 最初にも言いましたが、今回取り上げた2つの方法は比較的簡単なやり方です。Perlのモジュールのインストール先を調べる方法は他にもまだまだありますので、ある程度Perlに慣れたら他の方法なんかも調べてみるとPerlのことがもっとよく理解出来てくるようになると思います。

 なにやらここ最近未熟ながらも仕事やらプライベートやらで他人にPerlを教える機会が増えて来たので、今後も似たような防備録的・資料的な意味でこういう記事を書く事になりそうです。もっと気が利いた事や技術的に説得力がある事を書ければいいんですけど、如何せんそうも行かないのが悩みどころですね。精進しないと……

*1:Perlライブラリを検索する対象になるディレクトリ。コマンドにおける$PATH環境変数と概ね同じ

dhcpdをインストールする


……というわけで、PS3を設置する時にDHCPでアドレスを取得するように設定してみたところ
なぜかブロードバンドルータDHCPサーバが上手く機能していないことが発覚したので、
面倒になって自宅BSDサーバにdhcpdをインストールすることにしました。
ちなみに上記現象はDHCPサーバの設定自体を間違えていたとかそういう次元ではなく、
工場出荷時状態に戻したところで同様の問題が発生しているという事態に陥っていたため、
この際だからここの記事のネタにしてしまえと今回の作業に至った次第です。


今回は超手堅くisc-dhcpdをインストールすることにします。
いざというときのリファレンスも多いし、ほぼデファクトスタンダードですしね。
というわけで早速インストール開始。
まずはportsからisc-dhcpdを捜し出してきてインストールします。

# cd /usr/ports/net/isc-dhcpd31-server
# make install clean

makeする際のオプションは

の3つを指定することにします。個人的にはPARANOIAとJAILは被ってるんじゃないか……
と思っていたんですが、rc.confに書き込む設定項目の具体的な機能を調べるに当たって
ソースコードを軽く読み流したところ、どうやらJAILはFTPDなんかによくあるchrootのことのようです。
つまり或る特定のディレクトリをさも/であるかのように見せかける……というアレです。


さてさて。インストール時に「rc.confにこれを加えておけ」と幾つか表示されるので、
次はこれらを/etc/rc.confに追加します。

#
#### dhcpd settings
#
dhcpd_enable="YES"                          # dhcpd enabled?
dhcpd_flags="-q"                            # command option(s)
dhcpd_conf="/usr/local/etc/dhcpd.conf"      # configuration file
dhcpd_ifaces=""                             # ethernet interface(s)
dhcpd_withumask="022"                       # file creation mask
#
#### dhcpd chroot settings
#
dhcpd_chuser_enable="YES"                   # runs w/o privileges?
dhcpd_withuser="dhcpd"                      # user name to run as
dhcpd_withgroup="dhcpd"                     # group name to run as
dhcpd_chroot_enable="YES"                   # runs chrooted?
dhcpd_devfs_enable="YES"                  # use devfs if available?
#dhcpd_makedev_enable="YES"                # use MAKEDEV instead?
dhcpd_rootdir="/var/db/dhcpd"               # directory to run in
#dhcpd_includedir=""               # directory with config-files to include
#dhcpd_flags="-early_chroot"                 # needs full root

部分的に僕が入れたコメントも混じってます。
とりあえず意図的に外したのは

#dhcpd_makedev_enable="YES"                # use MAKEDEV instead?
#dhcpd_includedir=""               # directory with config-files to include
#dhcpd_flags="-early_chroot"                 # needs full root

の3つ。dhcpd_devfs_enableとdhcpd_makedev_enableはどちらか片方のみを
受け入れる事になっているようなので、今回はmakedev_enableを外すことにしました。
残る2つ……dhcpd_includedirはchroot時にデフォルト以外に
chrootされたディレクトリの下にコピーするディレクトリを指定する項目です。
dhcpd_flagsはchrootする際に設定するフラグを書き込んでおく項目で、
デフォルトの"early_chroot"はdhcpd_rootdirが定義されている場合に、
起動時にオプションの解釈を終えたら即chrootするようになるようです。


さて、次はdhcpdの設定ファイルを書き換えます。
今回の方針は

  • 192.168.2.100-200を提供する。サブネットマスクは24ビット
  • 静的に提供するアドレスはなし

……という極めて単純なものだったので、特に説明も入れずにサクッと紹介。
そうそう、インストール直後は/usr/local/etc/dhcpd.conf.sampleしかないので
これを/usr/local/etc/dhcpd.confにコピーしてから作業を行います。

# dhcpd.conf
#
# Sample configuration file for ISC dhcpd
#

# option definitions common to all supported networks...
option domain-name "local.privatenet";
option domain-name-servers 192.168.2.254;

default-lease-time 3600;
max-lease-time 86400;

# Use this to enble / disable dynamic dns updates globally.
ddns-update-style none;

# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
#authoritative;

# ad-hoc DNS update scheme - set to "none" to disable dynamic DNS updates.
#ddns-update-style ad-hoc;

# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
log-facility local0;

# No service will be given on this subnet, but declaring it helps the 
# DHCP server to understand the network topology.

# A slightly different configuration for an internal subnet.
subnet 192.168.2.0 netmask 255.255.255.0 {
  range 192.168.2.100 192.168.2.200;
  option domain-name-servers 192.168.2.1
  option domain-name "local.privatenet";
  option routers 192.168.2.1;
  option broadcast-address 192.168.2.255;
  default-lease-time 3600;
  max-lease-time 86400;
}

# Hosts which require special configuration options can be listed in
# host statements.   If no address is specified, the address will be
# allocated dynamically (if possible), but the host-specific information
# will still come from the host declaration.

# You can declare a class of clients and then do address allocation
# based on that.   The example below shows a case where all clients
# in a certain class get addresses on the 10.17.224/24 subnet, and all
# other clients get addresses on the 10.0.29/24 subnet.

#class "foo" {
#  match if substring (option vendor-class-identifier, 0, 4) = "SUNW";
#}
#
#shared-network 224-29 {
#  subnet 10.17.224.0 netmask 255.255.255.0 {
#    option routers rtr-224.example.org;
#  }
#  subnet 10.0.29.0 netmask 255.255.255.0 {
#    option routers rtr-29.example.org;
#  }
#  pool {
#    allow members of "foo";
#    range 10.17.224.10 10.17.224.250;
#  }
#  pool {
#    deny members of "foo";
#    range 10.0.29.10 10.0.29.230;
#  }
#}

こんな感じ。実を言うと後半の'class'のあたりからは丸ごと削除してしまっても構いません。
個人的に興味がある設定項目だったのでコメントとして残してあるだけです。


後は起動させて動作確認すれば設定完了です。

# /usr/local/etc/rc.d/isc-dhcpd start
Starting dhcpd.
#

これで起動完了。動作確認は実際にIPアドレスを取得してみればはっきりします。
Windows端末でIPアドレスを自動的に取得するように設定した上で……

>ipconfig /renew

Windows IP Configuration


Ethernet adapter ローカル エリア接続:

        Connection-specific DNS Suffix  . : local.privatenet
        IP Address. . . . . . . . . . . . : 192.168.2.100
        Subnet Mask . . . . . . . . . . . : 255.255.255.0
        Default Gateway . . . . . . . . . : 192.168.2.1

>ipconfig /all

 --------------------------- 略 -------------------------------

Ethernet adapter ローカル エリア接続:

        Connection-specific DNS Suffix  . : local.privatenet
        Description . . . . . . . . . . . : Realtek RTL8168C(P)/8111C(P) PCI-E Gigabit Ethernet NIC
        Physical Address. . . . . . . . . : 00-24-1D-8D-02-DC
        Dhcp Enabled. . . . . . . . . . . : Yes
        Autoconfiguration Enabled . . . . : Yes
        IP Address. . . . . . . . . . . . : 192.168.2.100
        Subnet Mask . . . . . . . . . . . : 255.255.255.0
        Default Gateway . . . . . . . . . : 192.168.2.1
        DHCP Server . . . . . . . . . . . : 192.168.2.254
        DNS Servers . . . . . . . . . . . : 192.168.2.1
                                            192.168.2.254
        Lease Obtained. . . . . . . . . . : 2009年9月28日 0:06:56
        Lease Expires . . . . . . . . . . : 2009年9月28日 1:06:56

無事にアドレスが取れたようです。
これで今後は携帯ゲーム機・据え置きゲーム機は全てDHCPIPアドレスを取得してくれば
大いなる無駄な手間は省けそうです。良かった良かった(笑

"Bad fd number"について詳細に調査してみた


色々あって急に空き時間が出来たので、
"Bad fd number"の検索結果で訪れてくる方が以外に多い事もあって
折角なのでこの問題を詳細に調査してみることにしました。


さて、まずはこのメッセージを出しているのが一体誰なのかを確認してみましょう。
エラーがエラーだし、リダイレクトする時点でエラーを吐いてる気がするので
多分/bin/shか、普段使っている/usr/local/bin/bash辺りではないかと思うのですが……

# cd /usr/src
# grep -R "Bad fd number" *
bin/sh/parser.c:                        synerror("Bad fd number");

# cd /usr/ports/shells/bash
(ソースコードがなかったので一端拾ってくる)
# make
# cd work/bash-4.0
# grep -R "Bad fd number" *
#

ふーむ……やっぱり/bin/shが吐いてたエラーのようですね。
それじゃあどの辺でエラーを吐いているかトレースしていくことにします。
幸運にもshにはデバッグモードに応じてトレースログを出力する機能があるようなので、
これをそのまま借りてくることにします。
/usr/src/bin/shの中身を以下のように変更して……

diff -aur sh/shell.h sh.tmp/shell.h
 --- sh/shell.h  2009-04-15 12:14:26.000000000 +0900
 +++ sh.tmp/shell.h      2009-09-19 00:10:14.000000000 +0900
@@ -50,7 +50,7 @@
 
 
 #define        JOBS 1
 -/* #define DEBUG 1 */
 +#define DEBUG 2
 
 /*
  * Type of used arithmetics. SUSv3 requires us to have at least signed long.
diff -aur sh/show.c sh.tmp/show.c
 --- sh/show.c   2009-04-15 12:14:26.000000000 +0900
 +++ sh.tmp/show.c       2009-09-19 00:06:34.000000000 +0900
@@ -394,7 +394,7 @@
                strcat(s, "/trace");
        }
 #else
 -       scopy("./trace", s);
 +       scopy("/tmp/trace", s);
 #endif /* not_this_way */
        if ((tracefile = fopen(s, "a")) == NULL) {
                fprintf(stderr, "Can't open %s: %s\n", s, strerror(errno));

さらに

# cd /usr/src/bin/sh
# make
# make install

……でインストールします。ちなみにshow.cの方の変更は必須ではありませんが、
トレースログが取られる位置として/usr/src/bin/sh/TOURに書いてあった
"$HOME/trace"をファイルパスとして指定する方の処理に進まなかったので、
面倒くさくなって/tmpに突っ込むことにしてます。


さて、これで/bin/shをシェルとして使った時にトレースログが取れるようになりました。
早速前回問題になったコマンドを実行してみましょう。
ちなみに実行するコマンドは下記の通りです。

# perl -e "system('/bin/ls >& /dev/null');"

これによって取得できたトレースログはこちら。

Tracing started.
Shell args:  "sh" "-c" "/bin/ls >& /dev/null"
token word /bin/ls
pipeline: entered
reread token word /bin/ls
reread token word /bin/ls
reread token word /bin/ls
reread token word /bin/ls
reread token word /bin/ls
reread token word /bin/ls
token redirection 
token word /dev/null
Fix redir /dev/null 0
token end of file 
reread token end of file 
reread token end of file 
reread token end of file 
evaltree(0x2820524c: 1) called
evalcommand(0x2820524c, 0) called
Fix redir /dev/null 1
exverror(1, NULL) pid=73635
exitshell(2) pid=73635


これで材料が揃ったのでいよいよソースコードを探索していきます。
まずはエラーメッセージ"Bad fd number"を出力している近辺で何が発生しているのか見てみます。
先ほどgrepで検索した結果、parser.c内で件のメッセージを出力している事が解ったので
該当行近辺を見ることにしてみましょう……

void fixredir(union node *n, const char *text, int err)
{
        TRACE(("Fix redir %s %d\n", text, err));
        if (!err)
                n->ndup.vname = NULL;

        if (is_digit(text[0]) && text[1] == '\0')
                n->ndup.dupfd = digit_val(text[0]);
        else if (text[0] == '-' && text[1] == '\0')
                n->ndup.dupfd = -1;
        else {

                if (err)
                        synerror("Bad fd number");
                else
                        n->ndup.vname = makename();
        }
}

この関数の中でエラーを吐き出しているようですね。
トレースログの中にあった"Fix redir /dev/null 1"はこの関数の中で出力している事が解ります。

shにおけるファイルのリダイレクトは実を言うと書式に従ってdup2()を実行しているだけなので、
詰まるところロジック的に正しいファイルディスクリプタ(1桁の数値)が取得できないので
"Bad fd number"と言われているようです。
そう考えて見るととても的を射ているエラーメッセージですね。
ちなみに実際にdup2()を呼び出しているのはredir.cの中です。

STATIC void
openredirect(union node *redir, char memory[10])
{
/* 筆者注:記事短縮のため省略 */
        case NTOFD:
        case NFROMFD:
                if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
                        if (memory[redir->ndup.dupfd])
                                memory[fd] = 1;
                        else
                                dup2(redir->ndup.dupfd, fd); /* 筆者注: fixredir()で取得したdupfdを使っている */
                } else {
                        close(fd);
                }
                break;
/* 筆者注:後略 */


ということは、これは単なるコマンド書式ミス?
もしかしてbashとかshではこういう風に標準出力・標準エラー出力
両方ともリダイレクトするんじゃなかったっけ?


こういう時にはいつもお世話になっているgoogle先生に伺ってみましょう。

cshtcsh の場合
標準出力と標準エラー出力を両方リダイレクトしたい場合は、

  • % command >& file.txt
  • % command |& command2

とする。

--------------------- 筆者注:中略 ----------------------

●sh・bash の場合
まず、

ということを覚えてほしい (ちなみに標準入力は 0 番)。

標準出力を file1 に、標準エラー出力を file2 に、などと振り分けるには、

  • % command 1>file1 2>file2

とする。

  • % command 1>/dev/null
  • % command 2>/dev/null

と、どちらかだけを表示することも簡単に指定できる。上記の find の例だと、

  • % find / 2>/dev/null

とすればよい。

標準出力と標準エラー出力を両方まとめて他のコマンドに渡すには

  • % command 2>&1 | less

とし、標準出力と標準エラー出力をまとめて file に書き出す場合は

  • % command >file 2>&1

とする。ここで順番を逆にして

  • % command 2>&1 >file (誤り!)

としてはうまくいかないことに注意。

http://x68000.q-e-d.net/~68user/unix/pickup?%A5%EA%A5%C0%A5%A4%A5%EC%A5%AF%A5%C8


……えーと、つまるところこういう事でした。
Perlはsystemを使う時に/bin/shを呼び出しているし& /dev/null"'という行がある">*1、シェル上で'/bin/ls >& /dev/null'を実行した時に使ったシェルはbashでした。
つまりcshとsh・bashの使い方を混同していたために発生しているエラーメッセージのようです。

というわけで、上記引用元の情報に基づいてコマンドを修正して実行してみます。

# perl -e 'system("/bin/ls > /dev/null 2>&1"); print "$?\n";'
0

エラーもなく終了しました。気合いを入れてトレースログまで取って探索した割にはしょうもない理由……

*1:トレースログ参照。'Shell args: "sh" "-c" "/bin/ls >& /dev/null"'という行がある

"syntax error: bad fd number"ってなんだ?


2009/09/19:追記:表題のエラーについて詳細に調査してみました。こちら


ほっとんどプログラミングの話題を扱っていなかったので、たまにはプログラミングの話題でも。
サーバにインストールしているFFmpeg::CommandのバージョンをPorts経由でバージョンアップしたところ、
このモジュールが稼働しなくなってしまった。何か異常があったのかと調査を始めたところ、

Syntax error: Bad fd number

との記録がApacheのerror.logに記載されているのを発見(CGIでモジュールを使っています)。
さらに試行錯誤を重ねた結果、どうやらこのモジュールに限らず

system("/bin/ls >& /dev/null");

上記のようにシェル上で/dev/nullへリダイレクトしようとすると須く同じエラーが発生するようだ。
FreeBSD7.2にするまでは……というよりつい最近までこんなエラーは出ていなかったはずなのだけど、
とりあえず根本的な原因を調査する時間はさほど用意出来なさそうなのでFFMpeg::Commandの問題の方は

    my $pid;

    if ($pid = fork()) {
        ;
    } elsif (!defined $pid) {
        close STDOUT;
        open STDOUT, ">& /dev/null" or exit -1;
        exec("$self->{ffmpeg} -version");
    }

    wait;

    if ($? != 0) {
        carp "Can't find ffmpeg command.";
        exit 0;
    }

逃げることにした(笑
diffると下記のような感じ。

 --- FFmpeg-Command-0.11/lib/FFmpeg/Command.pm   2009-06-07 15:57:29.000000000 +0900
 +++ /usr/local/lib/perl5/site_perl/5.8.9/FFmpeg/Command.pm      2009-07-20 21:14:35.000000000 +0900
 @@ -38,7 +38,19 @@
          timeout     => 0,
      };

 -    if ( system("$self->{ffmpeg} -version >& /dev/null") != 0 ) {
 +    my $pid;
 +
 +    if ($pid = fork()) {
 +       ;
 +    } elsif (!defined $pid) {
 +       close STDOUT;
 +       open STDOUT, ">& /dev/null" or exit -1;
 +       exec("$self->{ffmpeg} -version");
 +    }
 +
 +    wait;
 +
 +    if ($? != 0) {
          carp "Can't find ffmpeg command.";
          exit 0;
      }

折を見て根本的な原因を探っておかないと……

今回までのあらすじ1−クライアントPCの乱


6月の終わりからまたしても1ヶ月以上日記を書いていない時期が過ぎ去ってしまいました。
どうにも記録を残すという作業が自分の体に定着しない悪癖があるようで、
億劫がっているわけではないのだけれど気がついたら記録を残すことなく
ずるずると日々を過ごしてしまうようです。
まあ趣味でやってることだからかまわないのでしょうけど……


それはさておき。
前回の最後に記録したクライアントPCのストレージ問題ですが、
結局のところHDDの異常ではなかったようです。
S.M.A.R.T.がエラーを吐いたような気もしたのですが、
チェックツールにかけたところとりわけ問題があるようでもなく……
というわけで使っていたHDDはとりあえず新品に差し替えはしたものの、
以前から使っていたディスクはサーバのバックアップに転用出来るかも。


さあこれで作業は終わりだ、とHDDを載せ替えて電源を投入したところ、
今度はOSがブルーバックで落ちる始末!
前回の記録を見ていただければ解ると思いますが、
障害発生時にはOS自体は落ちていなかったのでつまるところ事態が悪化してるわけです(笑
本当に次から次へと問題が起こってくれること……


とりあえずブルーバックになった以上エラーメッセージが出ているはずなので、
それの確認を行うことに。
あ、ちなみに幾度か話題に上げたと思いますが、私がクライアントPCで使っているOSはWindows XPです。
……で確認してみた結果、(この辺うろ覚えだけど)どうやら
「KERNEL STACK INMEMORY云々」というエラーが出ているようです……
ということはメモリじゃないか!*1
ストレージHDDがおかしな事になったように見せかけられたのもこれのせいか!?


とりあえず行きつけのsofmapでUMAXのメモリを2G*2枚購入して事態を収拾させました。
新品のHDDを用意する前にブルーバックで落ちてくれさえすれば
メモリの購入だけで収集できたんだけどなぁー……そうそう上手くは行かないもんです。

*1:賢明な読者諸兄はご存じかと思われるが、普通落ちるような事がないプロセスや カーネルのモジュールが落ちたりするような事がある時には大抵メモリが疑わしいのである

バックアップサーバからのメールが不達になった

記事にするのはもっと後でも良いかとは思ったが、よくよく考えたら
時間が空きすぎてしまうと情報を引っ張り出すのが少々面倒なので
ここら辺で記事にしておこうと思う。まあそれくらいちょっとした話。


現在自宅に設置してある全てのサーバのperiodicやcronで回した処理の結果を
メインサーバに転送するように設定してあるのだが、
今朝確認を行ったところメインサーバ以外からのメールが一切不達になっていた。
ネットワーク的な問題が発生した痕跡もないしサーバを落とした覚えもないので
早速原因究明に映る。


……と思いきや、メインサーバのroot宛(正確に言えばrootのaliasに設定したアドレス)に
下記のようなメールが届いているのを早速発見。

Transcript of session follows.

 Out: 220 mail.local.server ESMTP Postfix
 In:  EHLO backup.local.server
 Out: 250-mail.local.server
 Out: 250-PIPELINING
 Out: 250-SIZE 10240000
 Out: 250-VRFY
 Out: 250-ETRN
 Out: 250-AUTH NTLM LOGIN PLAIN GSSAPI DIGEST-MD5 CRAM-MD5
 Out: 250-ENHANCEDSTATUSCODES
 Out: 250-8BITMIME
 Out: 250 DSN
 In:  MAIL FROM: SIZE=5508
 Out: 452 4.3.1 Insufficient system storage
 In:  RCPT TO: ORCPT=rfc822;root
 Out: 503 5.5.1 Error: need MAIL command
 In:  DATA
 Out: 503 5.5.1 Error: need RCPT command
 In:  RSET
 Out: 250 2.0.0 Ok
 In:  QUIT
 Out: 221 2.0.0 Bye

やや、容量オーバーとな……どうやらメインサーバの/varか/homeが溢れているらしい。
早速上記メールと同じ日付のメインサーバのdaily run outputを見てみると……

/dev/ad8s1d      19G     19G   -1.2G   107%    /var

やっぱり/varが溢れていたようだ。というわけで/varの中身を整理して、
メールが不達だったサーバ上で

# postsuper -r ALL

queueを押し出してやって問題はあっさり解決。
あ、そうそう。言ってなかった気がするけどうちの自宅サーバ群のMTAは全てPostfixである。

もっと簡単なRSSの作成方法があった


以前RSSの作成方法としてXML::RSSPlaggerを組み合わせる方法を記述したが、
実はこれよりももっと容易にフィードを作成・編集出来るモジュールの存在を発見してしまった。
XML::FeedPPを使えばRSS/ATOMフィードは簡単に作成できてしまうのだ。こんな便利なツールがあるならもっと早く知っておくべきだった……
とはいえ前回のアレはPlaggerの動作確認も兼ねていたのである程度はやむを得ないが。


というわけで早速使ってみる。まずは新規作成……

#!/usr/bin/perl

use strict;
use XML::FeedPP;
use Jcode;

my $file = "/var/html/feeds/rss-test.xml";
my $rss;
my $item;
my $now = time;
my $title = "ほげほげ";
my $podcast;

$rss = XML::FeedPP::RSS->new();
$rss->title('generate test');
$rss->description('ganerate by XML::FeedPP.');
$rss->language('ja');
$rss->link('http://www.server/');
$rss->pubDate($now);

$item = $rss->add_item("http://www.server/test/video1.mp4");

$title = jcode($title)->utf8;

$item->title($title);
$item->pubDate($now);
$podcast = {
    'enclosure@url' => "http://www.server/test/video1.mp4",
    'enclosure@length' => (stat("/var/html/test/video1.mp4"))[7],
    'enclosure@type' => 'video/mp4',
};
$item->set(%$podcast);

$rss->to_file($file);

exit 0;


これで追加は完了。次はアイテムを追加してpubDate順に降順ソートする。

#!/usr/bin/perl

use strict;
use XML::FeedPP;
use Jcode;

my $file = "/var/html/feeds/rss-test.xml";
my $rss;
my $item;
my $now = time;
my $title = "ほげほげ";
my $podcast;

$rss = new XML::FeedPP($file);

$rss->pubDate($now);

$item = $rss->add_item("http://www.server/test/video2.mp4");

$title = jcode($title)->utf8;

$item->title($title);
$item->pubDate($now);
$podcast = {
    'enclosure@url' => "http://www.server/test/video2.mp4",
    'enclosure@length' => (stat("/var/html/test/video2.mp4"))[7],
    'enclosure@type' => 'video/mp4',
};
$item->set(%$podcast);

$rss->sort_item();
$rss->to_file($file);

exit 0;


アイテムの追加はこんな感じ。
以前掲示したXML::RSSとコードの構成的には殆ど代わりはないが、
煩わしいUTF8フラグ関連の問題と難関であるフィードのソートをモジュール側が担ってくれるのでとても楽だ。
今後RSS作成・追加関連の処理はXML::FeedPPを使って書いていこう。