2016/11/21

画像の回転スクリプト

blog などに添付する画像を回転するためのスクリプト。
回転処理は ImageMagickrotate (1) コマンドで行うのだが、 一部のブラウザでは exif 情報に含まれる Orientation の値で画像を回転して表示するので、 回転した後で exif の Orientation の情報を修正している。
exif 情報の取得と設定は Perl モジュール Image-ExifTool を利用している。

getor: exif 情報から Orientation を取得する。

  1#!/usr/bin/perl -w
  2use Image::ExifTool ':Public';
  3my $file = shift or die "Please specify filename";
  4
  5$exifTool = new Image::ExifTool;
  6$exifTool->ExtractInfo($file);
  7
  8print $exifTool->GetValue('Orientation') . "\n";
    

setor: Orientation に水平 Horizontal (normal) を設定する。

  1#!/usr/bin/perl -w
  2use Image::ExifTool ':Public';
  3my $file = shift or die "Please specify filename";
  4
  5$exifTool = new Image::ExifTool;
  6$exifTool->SetNewValue('Orientation', 'Horizontal (normal)');
  7$exifTool->WriteInfo($file);
    

引数で指定されたファイルを回転して Orientation を設定する。

  1#!/bin/sh
  2for i in $*
  3do
  4    n=$(getor $i | awk '/^Rotate/{ print $2 }')
  5    if [ ${n:-0} -gt 0 ]
  6    then
  7        convert -rotate $n $i /tmp/_$$.jpg &&
  8        mv /tmp/_$$.jpg $i
  9        setor $i
 10    fi
 11done
    

2016/11/22 追記

以前作成した Exif 情報から GPS の緯度/経度情報を削除する を元に 全てシェルスクリプトで画像を回転するスクリプトを作ってみた。
dd (1) を利用してバイナリファイルの入出力を行い、 Exif 情報の取得と更新を実施している。
  1#!/bin/sh
  2# 画像を回転し Exif 情報の 'Orientation' を設定する
  3#
  4
  5#
  6# Endian を考慮して 2 バイト数字を取得する
  7#
  8#   $1: ファイル
  9#   $2: オフセット
 10#
 11get2Byte()
 12{
 13
 14    dd ${conv} if="${1}" bs=1 skip=$((${skip} + ${2})) count=2 2> /dev/null | od -d | awk '/^0000000/{ print $2 }'
 15
 16}
 17
 18#
 19# 任意の長さの文字列を取得する
 20#
 21#   $1: ファイル
 22#   $2: オフセット
 23#   $3: 文字列長
 24#
 25getString()
 26{
 27
 28    dd if="${1}" bs=1 skip=$((${skip} + ${2})) count=${3} 2> /dev/null
 29
 30}
 31
 32#
 33# 0th IFD データの解析処理
 34#
 35#   $1: ファイル
 36#   $2: 0th IFD のタグ数
 37#
 38ifd()
 39{
 40
 41    local   i offset
 42
 43    i=0
 44
 45    while [ ${i} -lt ${2} ]
 46    do
 47        # 0th IFD のタグ情報
 48        offset=$((${3} + 22 + (${i} * 12)))
 49        # Orientation の場合はオフセットを表示して終了
 50        if [ $(get2Byte "${1}" ${offset}) -eq 274 ]
 51        then
 52            echo ${offset}
 53            break
 54        fi
 55        i=$((i + 1))
 56    done
 57
 58}
 59
 60#
 61# Orientation 情報の取得
 62#
 63#   $1: ファイル
 64#   $2: Orientation IFD のオフセット
 65#
 66getOrient()
 67{
 68
 69    echo $(get2Byte "${1}" $((${2} + 8)))
 70
 71}
 72
 73#
 74# Orientation 情報のセット
 75#
 76#   $1: ファイル
 77#   $2: Orientation IFD のオフセット
 78#
 79setOrient()
 80{
 81
 82    echo -ne "\00\01" | dd conv=notrunc of="${1}" bs=1 seek=$((${2} + 8 + ${skip})) 2> /dev/null
 83
 84}
 85
 86#
 87# ファイルフォーマットから skip するバイト数を取得
 88#
 89#   $1: ファイル
 90#
 91getSkip()
 92{
 93
 94    if [ "$(getString "${1}" 6 4)" = "Exif" ]
 95    then
 96        # Exif 情報格納
 97        echo 0
 98    elif [ "$(getString "${1}" 24 4)" = "Exif" ]
 99    then
100        # JFIF 情報格納
101        echo 18
102    else
103        # Exif 情報なし
104        echo -1
105    fi
106
107}
108
109#
110# convert(1) のオプション取得
111#
112#   $1: ファイル
113#
114get_conv()
115{
116
117    offset=$(ifd "${1}" $(get2Byte "${1}" 20))
118    if [ ${offset:-0} -gt 0 ]
119    then
120        case $(getOrient "${1}" ${offset}) in
121            2 ) opt="-flip";;                       # 上下反転
122            3 ) opt="-rotate 180";;                 # 180度回転
123            4 ) opt="-flop";;                       # 左右反転
124            5 ) opt="-flop -rotate 90";;            # 上下反転+270度回転
125            6 ) opt="-rotate 270";;                 # 90度回転
126            7 ) opt="-flip rotate 270";;            # 上下反転+90度回転
127            8 ) opt="-rotate 90";;                  # 270度回転
128        esac
129        echo ${opt}
130    fi
131
132}
133
134# メイン処理
135for i in $*
136do
137    skip=$(getSkip "${1}")
138    if [ ${skip} -ge 0 ]
139    then
140        # Endian
141        test $(getString "${1}" 12 2) = "MM" && conv="conv=swab"
142        opt=$(get_conv $i)
143        if [ -n "${opt}" ]
144        then
145            # 画像を回転
146            convert ${opt} "${i}" /tmp/_$$.jpg && mv /tmp/_$$.jpg "${i}"
147            # conviert(1) により TFIF 情報が付加される
148            skip=$(getSkip "${i}")
149            setOrient "${i}" $(ifd "${i}" $(get2Byte "${i}" 20))
150        fi
151    fi
152done
    

2016/11/23 追記

convert (1) だけで出来る事が判明
$ convert -auto-orient 入力ファイル.jpg 出力ファイル.jpg 
    
更に identify (1) を利用すると Exif 情報も取得できるので ImageMagic が導入されている場合は以下のスクリプトで完結する。
  1#!/bin/sh
  2for i in $*
  3do
  4    test "`identify -verbose $i | awk '/exif:Orientation/{ print $2 }'`" != 1 &&
  5        convert -auto-orient $i /tmp/_$$.jpg &&
  6        mv /tmp/_$$.jpg $i
  7done
    

2016/05/11

行抜き出しの速度

某所で話題になったのでテキストファイルの行抜き出し速度を検証してみた。
検証用のデータは若干サイズ的に不足があるのだが、 以下のシェル芸で 24M のランダムなテキストデータを作成した。

$ for i in `seq 50000`; do printf "%05d:%s\n" $i `LC_CTYPE=C tr -c -d 'a-zA-Z0-9' < /dev/urandom | dd bs=501 count=1 2> /dev/null`; done > base
$ ls -l base
total 49616
-rw-r--r--  1 mitz  staff  25400000  5 11 12:15 base
    
作業は全て iMac 上で実施した。
$ uname -a
Darwin coredump.local 12.6.0 Darwin Kernel Version 12.6.0: Wed Mar 18 16:23:48 PDT 2015; root:xnu-2050.48.19~1/RELEASE_X86_64 x86_64
    

検証は以下の手段による行の抜き出しの100回繰り返しを10回実行した時の平均た時間を計測した。

  • tail(1) と head(1) の組み合わせ (入力ファイルを tail(1) の引数として直接指定)
  • tail(1) と head(1) の組み合わせ (入力ファイルを cat(1) を利用してパイプ経由で tail(1) に通知)
  • head(1) と tail(1) の組み合わせ (入力ファイルを head(1) の引数として直接指定)
  • head(1) と tail(1) の組み合わせ (入力ファイルを cat(1) を利用してパイプ経由で head(1) に通知)
  • sed 単体 (入力ファイルを sed(1) の引数として直接指定)
  • sed 単体 (入力ファイルを cat(1) を利用してパイプ経由で sed(1) に通知)
  • awk 単体 (入力ファイルを awk(1) の引数として直接指定)
  • awk 単体 (入力ファイルを cat(1) を利用してパイプ経由で awk(1) に通知)
  • sed 単体で検出後即時終了 (入力ファイルを sed(1) の引数として直接指定)
  • sed 単体で検出後即時終了 (入力ファイルを cat(1) を利用してパイプ経由で sed(1) に通知)
  • awk 単体で検出後即時終了 (入力ファイルを awk(1) の引数として直接指定)
  • awk 単体で検出後即時終了 (入力ファイルを cat(1) を利用してパイプ経由で awk(1) に通知)

結果は以下の通り。 自分の予想に反して head(1) と tail(1) の組み合わせが一番早くて驚き。
OS X の標準コマンドなので GNU 拡張された head(1) や tail(1) ではない筈なので、 そもそもオリジナルの head(1) の実装が良いのだろうか。

追記

twitter 及び当ブログのコメントで指摘を頂いたので sed(1) と awk(1) で 行を検出した場合は終了する場合の時間も計測してみました。
head(1) と tail(1) をパイプで繋いだ場合も、 2番目のコマンドで1行出力すると SIGPIPE が発生して 1番目のコマンドも終了しているのだろうか?

tail -n 4036 base | head -1
real0m0.843s
user0m0.542s
sys0m0.406s
cat base | tail -n 4036 | head -1
real0m16.681s
user0m16.071s
sys0m1.900s
head -n 4036 base | tail -1
real0m1.495s
user0m1.571s
sys0m0.351s
cat base | head -n 4036 | tail -1
real0m1.534s
user0m1.667s
sys0m0.512s
sed -n '4036p' base
real0m3.183s
user0m2.262s
sys0m0.835s
cat base | sed -n '4036p'
real0m2.485s
user0m2.240s
sys0m1.253s
awk '{ if(NR==4036) print $0 }' base
real0m19.430s
user0m18.357s
sys0m0.893s
cat base | awk '{ if(NR==4036) print $0 }'
real0m18.911s
user0m18.513s
sys0m1.369s
sed -n '4036{p;q;}' base
real0m0.467s
user0m0.270s
sys0m0.182s
cat base | sed -n '4036{p;q;}'
real0m0.456s
user0m0.347s
sys0m0.353s
awk '{ if(NR==4036){ print $0; exit;} }' base
real0m1.852s
user0m1.581s
sys0m0.199s
cat base | awk '{ if(NR==4036){ print $0; exit;} }'
real0m1.775s
user0m1.676s
sys0m0.355s

2016/04/06

文字列の読み方を表示する

自動生成したパスワードなどランダムな文字列をメイルなどで他人に伝える場合、 フォントによっては判別しずらい文字がある。
それらの判別しづらい文字を正確に伝えるために、 数字とアルファベットの読みを表示する簡単なプログラム。
標準入力から読み込んだ英数字の読みを表示し英数字以外は無視する。

  1#!/bin/sh
  2
  3awk 'BEGIN{
  4        p["0"] = "ぜろ";
  5        p["1"] = "いち";
  6        p["2"] = "に";
  7        p["3"] = "さん";
  8        p["4"] = "よん";
  9        p["5"] = "ご";
 10        p["6"] = "ろく";
 11        p["7"] = "しち";
 12        p["8"] = "はち";
 13        p["9"] = "きゅう";
 14        p["a"] = "エー";
 15        p["b"] = "ビー";
 16        p["c"] = "シー";
 17        p["d"] = "ディー";
 18        p["e"] = "イー";
 19        p["f"] = "エフ";
 20        p["g"] = "ジー";
 21        p["h"] = "エイチ";
 22        p["i"] = "アイ";
 23        p["j"] = "ジェイ";
 24        p["k"] = "ケー";
 25        p["l"] = "エル";
 26        p["m"] = "エム";
 27        p["n"] = "エヌ";
 28        p["o"] = "オー";
 29        p["p"] = "ピー";
 30        p["q"] = "キュー";
 31        p["r"] = "アール";
 32        p["s"] = "エス";
 33        p["t"] = "ティー";
 34        p["u"] = "ユー";
 35        p["v"] = "ブイ";
 36        p["w"] = "ダブリュー";
 37        p["x"] = "エックス";
 38        p["y"] = "ワイ";
 39        p["z"] = "ゼット";
 40    }
 41    {
 42        n = split(tolower($0), words, "");
 43        for(i=1; i<=n; i++){
 44            v = v g p[words[i]];
 45            g = " "
 46        }
 47        printf "%s (%s)\n", $0, v;
 48    }'
    

nkf (1) や kakashi (1) などで出来ないかとちょっとだけ探ったが、 出来ない様だったので適当にでっち上げた。
他にもっと良いものがあれば是非教えて下さい。

読みの部分を「アルファ」「ブラボー」「チャーリー」等とすれば フォネティックコードにも変換できる。

2015/10/01

Shuca (朱夏) - 日本語対応のサマライザ

MOONGIFTで紹介されていた Shuca という日本語に対応したサマライザを試してみた。
サマライザとは文章を解析し重要な部分だけを抜き出してくれる要約エンジンの事で、 この処理が自動で実施できると非常に便利になりそうなので 早速 CentOS 6.6 に Shucha を導入してみる。

Shuca は予め分かち書きや構文解析された文章を入力ソースとして受け付ける。 そのため、別途、形態素解析システムの JUMAN や 日本語構文・格・照応解析システムの KNP が必要となるので それらのツールも同時にインストールする。

Shuca のインストール
Shuca 本体は Python で記述されており GitHub でソースが公開されている。 基本的にインストール作業は不要で GitHub からダウンロードしたファイルを任意のディレクトリに展開すれば良い。
今回は /usr/local 以下に全てのファイルを展開し、 実行可能ファイルは /usr/local/bin 以下に設置した。
$ wget https://github.com/hitoshin/shuca/archive/master.zip
$ unzip master.zip
$ sudo mkdir -p /usr/local/{bin,libexec,dic}
$ sudo cp -p shuca-master/lib/* /usr/local/bin/.
$ sudo cp -p shuca-master/dic/* /usr/local/dic/.
$ sudo cp -p shuca-master/libexec/* /usr/local/libexec/.
            
インストールが終了したら同梱されているサンプルデータで動作を確認する。
$ Shuca.py < shuca-master/dat/sample.knp.txt 
JR東海は4月16日、山梨リニア実験線で同日に行ったL0系の高速有人走行試験において、590キロメートル毎時を記録したと発表した。
2003年12月2日に同社のMLX01形が記録した鉄道の世界最高速度、581キロメートル毎時を11年4ヶ月ぶりに9キロメートル毎時更新する形となった。
JR東海は、「今後も開業に向けさまざまな試験を行っていく」とコメントしている。
            
JUMAN のインストール
JUMAN は京都大学 大学院情報学研究科の黒崎・河原研究室が開発した日本語形態素解析システムで、 日本語の文章を形態素ごとに分かち書きし品詞などの情報を付加している。
$ wget http://nlp.ist.i.kyoto-u.ac.jp/nl-resource/juman/juman-7.01.tar.bz2
$ tar xvf juman-7.01.tar.bz2
$ cd juman-7.01
$ ./configure --prefix=/usr/local
$ make
$ sudo make install
            
KNP のインストール。
KNP も京都大学 大学院情報学研究科の黒崎・河原研究室が開発した日本語構文・格・照応解析システムで、 JUMAN で解析された結果から文節および基本句間の係り受け関係、格関係、照応関係を出力する。
KNP は zlib を利用しているので、予め zlib のインクルードファイル、ライブラリ等をインストールしておく。
$ sudo yum -y install zlib-devel
$ wget http://nlp.ist.i.kyoto-u.ac.jp/nl-resource/knp/knp-4.14.tar.bz2
$ tar xvf knp-4.14.tar.bz2
$ cd knp-4.14
$ ./configure --prefix=/usr/local --with-juman-prefix=/usr/local
$ make
$ sudo make install
            
動作の確認
これらがインストールできたら Shuca に同梱されているデータで動作の検証を実施する。
$ juman < shuca-master/dat/sample.snt.txt | knp 
# S-ID:1 KNP:4.14-CF1.1 DATE:2016/03/04 SCORE:-63.73058
                                JR──┐     
                                      東海は──┐ 
                  4──┐           │ 
                          月──┐       │ 
                                16──┐   │ 
                                        日、──┤ 
        山梨──┐               │ 
              リニア──┐           │ 
                        実験──┐       │ 
                                線で──┐   │ 
      同日に──┐           │   │ 
              行った──┐       │   │ 
        L0──┐   │       │   │ 
                系の──┤       │   │ 
高速──┐       │       │   │ 
        有人──┐   │       │   │ 
                走行──┤       │   │ 
                      試験に──┐   │   │ 
                            おいて、──┤   │ 
                      590──┐   │   │ 
                キロメートル──┤   │   │ 
                              毎時を──┤   │ 
                                  記録したと──┤ 
                                          発表した。
EOS
    :
    :
    :
            
これで必要なツール類のインストールは完了した。

ここまで動作が確認できたら php を利用して簡単な動作検証環境を作成する。
Shcha に附属されていたサンプルデータから KNP の実行時オプションは "-simple -normal" だと推察してみた。
この php スクリプトは入力された URL からコンテンツを取得して jUMAN で形態素解析を実施してKNP で構文解析を実施した上で Shcha を実行して要約を取得する。
下準備として改行コードの統一、HTML タグの除去、空行や空白のサプレス、 更に KNP の制限により半角文字を全角文字に変換している。
あくまでも動作サンプルのためのスクリプトなので、 エラー処理や html entity の処理など実施していない。

  1<html>
  2    <head>
  3        <title>
  4            shuca 要約
  5        </title>
  6    </head>
  7    <body>
  8        <center>
  9<?php
 10    mb_internal_encoding("UTF-8");
 11
 12    define("juman",     "/usr/local/bin/juman");
 13    define("knp",       "/usr/local/bin/knp -simple -normal -cf-cache");
 14    define("shuca",     "/usr/local/bin/Shuca.py -l 500 ");
 15
 16    if(strlen(($url = $_REQUEST["url"]))){
 17        print "$url<br><br>";
 18
 19        $start = gettime();
 20
 21        $content =
 22            mb_convert_kana(                                                            /* 半角文字を全角に変換 */
 23                preg_replace("/ +/", " ",                                               /* 複数のスペースを1個に置換 */
 24                    preg_replace("/^ *\n/m", "",                                        /* 空行を削除 */
 25                        strip_tags(                                                     /* html タグを削除 */
 26                            preg_replace('!<style.*?>.*?</style.*?>!is', '',            /* <style> タグとその内容を削除 */
 27                                preg_replace('!<script.*?>.*?</script.*?>!is', '',      /* <script> タグとその内容を削除 */
 28                                    str_replace("\r", "",                               /* CR を削除 */
 29                                        file_get_contents($url))))))), "ASKV");
 30
 31        $buf = "";
 32        $desc = array(
 33            0 => array("pipe", "rb"),                   /* stdin: pipe */
 34            1 => array("pipe", "wb"),                   /* stdout: pipe */
 35            2 => array("file", "/dev/null", "w"),       /* stderr: /dev/null */
 36        );
 37
 38        if($pp = proc_open(sprintf("%s | %s | %s", juman, knp, shuca), $desc, $pipe)){
 39            fwrite($pipe[0], $content);
 40            fclose($pipe[0]);
 41
 42            while(!feof($pipe[1]))
 43                $buf .= nl2br(fread($pipe[1], 1024));
 44            fclose($pipe[1]);
 45
 46            proc_close($pp);
 47
 48            print <<< EOF
 49                {$buf}
 50EOF
 51            ;
 52            printf("処理時間: %f 秒", gettime() - $start);
 53        }
 54    }
 55    else{
 56        print <<< EOF
 57                <form>
 58                    <input type='text' name='url' size='50'>
 59                    <input type='submit' value='要約'>
 60                </form>
 61EOF
 62        ;
 63    }
 64
 65function    gettime()
 66{
 67
 68    $t = gettimeofday();
 69    return((float)($t["sec"] + $t["usec"] / 1000000.0));
 70
 71}
 72
 73    
 74?>
 75        </center>
 76    </body>
 77</html>
    

Image: img_20151001.png

実行結果
KNP の実行に結構時間がかかっている様だが、 入力ソースを適切に編集する事で実用的な要約が取得できる様だ。

2015/09/03

国別フィルタの自動生成ツール (CentOS 版)

以前 FreeBSD 版の 国別フィルタの自動生成ツール を紹介したのだが、 最近 CentOS でもサーバを運用しているので CentOS 版を作成したので公開する事にした。 機能は FreeBSD 版同様 APNIC から IPアドレスの割当リストを取得して、 IPアドレスを CIDR 型式に修正した上で iptables (8) のコマンドラインパラメタを自動生成している。
今回は /etc/init.d/iptables を実行してフィルタを初期化した上で、 state RELATED,ESTABLISHED が含まれる行の次の行にルールの追加までを実行する。

  1#!/usr/bin/perl
  2# KRフィルタ
  3use Socket;
  4
  5# 拒否する国コード
  6@country = ('KR', 'CN');
  7# IPアドレス一覧取得URL
  8$url = "http://ftp.apnic.net/stats/apnic/delegated-apnic-latest";
  9
 10# iptables 初期化
 11system("/etc/init.d/iptables restart");
 12
 13# 挿入位置を取得 (接続済みパケットは許可する設定の次に挿入)
 14if(open(IN, "iptables -L INPUT --line-number|")){
 15    while(<IN>){
 16        if(/^(\d+).*RELATED,ESTABLISHED.*/){
 17            $rule = $1 + 1;
 18            last;
 19        }
 20    }
 21    close(IN);
 22}
 23
 24# 初期処理
 25foreach $i (@country){
 26    $country{$i} = 1;
 27}
 28
 29if(open(IN, "wget -q -O - $url|")){
 30    while(<IN>){
 31        if(/^apnic\|(..)\|ipv4\|(\d+.\d+.\d+.\d)\|(\d+)/){
 32            if($country{$1}){
 33                $table{inet_aton($2)} = $3;
 34            }
 35        }
 36    }
 37    close(IN);
 38
 39    # IPアドレス一覧を CIDR 型式に変換
 40    foreach $net (sort keys %table){
 41        $addr = unpack('N', $net);
 42        $num = $table{$net};
 43        while($num == $num[0] && ($addr ^ $addr[0]) == $num){
 44            shift @addr;
 45            shift @num;
 46            $addr &= ~$num;
 47            $num <<= 1;
 48        }
 49        unshift(@addr, $addr);
 50        unshift(@num, $num);
 51    }
 52
 53    # iptables 実行
 54    while (@addr){
 55        for($num = pop(@num), $mask = 32; $num > 1; $num >>= 1, $mask--){}
 56        $filt = inet_ntoa(pack('N', pop(@addr))) . "/$mask";
 57        system("iptables -I INPUT $rule -s $filt -j DROP");
 58        $rule++;
 59    }
 60}
 61
 620;
    

2015/08/26

CentOS 6 に rssh を導入する

rsshssh (1) を利用したアクセスのうち scp (1) や sftp (1) といった 特定のコマンドのみの実行を許可したり chroot 環境を提供するログインシェルで、 セキュアなファイル転送は提供したいがシェルアカウントへの ログインを拒否したい場合などに非常に有効なログインシェルだ。
scp (1) や sftp (1) のみの実行を許可する場合は アカウントのログインシェルを rssh に変更して 設定ファイルで実行を許可するコマンドを指定するだけで設定は完了する。
しかし chroot 環境を提供する場合はそのための環境設定が必要である。

rssh のインストール
CentOS 6 では yum (1) から導入可能
$ sudo yum -y install rssh
    :
    :
    :
インストール:
  rssh.x86_64 0:2.3.4-1.el6

完了しました!
            
設定ファイルの編集
ssp (1)、sftp (1) のアクセスを許可し、 chroot するディレクトリを指定する。
# This is the default rssh config file

# set the log facility.  "LOG_USER" and "user" are equivalent.
logfacility = LOG_USER

# Leave these all commented out to make the default action for rssh to lock
# users out completely...

allowscp                                # scpを許可
allowsftp                               # sftpを許可
#allowcvs
#allowrdist
#allowrsync

# set the default umask
umask = 022

# If you want to chroot users, use this to set the directory where the root of
# the chroot jail will be located.
#
# if you DO NOT want to chroot users, LEAVE THIS COMMENTED OUT.
chrootpath = /opt/chroot                 # chroot先ディレクトリ

# You can quote anywhere, but quotes not required unless the path contains a
# space... as in this example.
#chrootpath = "/usr/local/my chroot"

##########################################
# EXAMPLES of configuring per-user options
    :
    :
    :
            
chroot 環境の初期設定
rssh に附属のスクリプトで chrrot 環境の初期構築を行う。
いくつかエラーが表示されるがここでは気にしなくても大丈夫
$ sudo sh /usr/share/doc/rssh-2.3.4/mkchroot.sh /opt/chroot
NOT changing owner of root jail. 
NOT changing perms of root jail. 
setting up /opt/chroot/usr/bin
setting up /opt/chroot/usr/libexec/openssh
setting up /opt/chroot/usr/libexec
Copying libraries for /usr/bin/scp.
    (0x00007fff541ff000)
cp: cannot stat `(0x00007fff541ff000)': そのようなファイルやディレクトリはありません
    /usr/lib64/libcrypto.so.10
    /lib64/libutil.so.1
    /lib64/libz.so.1
    /lib64/libnsl.so.1
    /lib64/libcrypt.so.1
    /lib64/libresolv.so.2
    /lib64/libgssapi_krb5.so.2
    /lib64/libkrb5.so.3
    /lib64/libk5crypto.so.3
    /lib64/libcom_err.so.2
    /usr/lib64/libnss3.so
    /lib64/libc.so.6
    /lib64/libdl.so.2
    /lib64/libfreebl3.so
    /lib64/libkrb5support.so.0
    /lib64/libkeyutils.so.1
    /lib64/libpthread.so.0
    /usr/lib64/libnssutil3.so
    /lib64/libplc4.so
    /lib64/libplds4.so
    /lib64/libnspr4.so
    /lib64/libselinux.so.1
    /lib64/librt.so.1
Copying libraries for /usr/libexec/openssh/sftp-server.
    (0x00007fff577f1000)
cp: cannot stat `(0x00007fff577f1000)': そのようなファイルやディレクトリはありません
    /usr/lib64/libcrypto.so.10
    /lib64/libutil.so.1
    /lib64/libz.so.1
    /lib64/libnsl.so.1
    /lib64/libcrypt.so.1
    /lib64/libresolv.so.2
    /lib64/libgssapi_krb5.so.2
    /lib64/libkrb5.so.3
    /lib64/libk5crypto.so.3
    /lib64/libcom_err.so.2
    /usr/lib64/libnss3.so
    /lib64/libc.so.6
    /lib64/libdl.so.2
    /lib64/libfreebl3.so
    /lib64/libkrb5support.so.0
    /lib64/libkeyutils.so.1
    /lib64/libpthread.so.0
    /usr/lib64/libnssutil3.so
    /lib64/libplc4.so
    /lib64/libplds4.so
    /lib64/libnspr4.so
    /lib64/libselinux.so.1
    /lib64/librt.so.1
Copying libraries for /usr/bin/rssh.
    (0x00007fff00bff000)
cp: cannot stat `(0x00007fff00bff000)': そのようなファイルやディレクトリはありません
    /lib64/libc.so.6
Copying libraries for /usr/libexec/rssh_chroot_helper.
    (0x00007fff3e152000)
cp: cannot stat `(0x00007fff3e152000)': そのようなファイルやディレクトリはありません
    /lib64/libc.so.6
copying name service resolution libraries...
tar: メンバ名から先頭の `/' を取り除きます
tar: /lib/libnss_files*: stat 不能: そのようなファイルやディレクトリはありません
tar: /lib/libnss1_files*: stat 不能: そのようなファイルやディレクトリはありません
tar: 前のエラーにより失敗ステータスで終了します
Setting up /etc in the chroot jail
cp: omitting directory `/etc/ld.so.conf.d'
Chroot jail configuration completed.

NOTE: if you are not using the passwd file for authentication,
you may need to copy some of the /lib/libnss_* files into the jail.

NOTE: you must MANUALLY edit your syslog rc script to start syslogd
with appropriate options to log to /opt/chroot/dev/log.  In most cases,
you will need to start syslog as:

   /sbin/syslogd -a /opt/chroot/dev/log

NOTE: we make no guarantee that ANY of this will work for you... if it
doesn't, you're on your own.  Sorry!
            
/dev/null の作成
附属のスクリプトでメッセージに表示されている処理、 エラーで完結できなかった処理などを実行して環境を構築する。
まず chroot 環境に /dev/null を作成する。
$ ls -l /dev/null
crw-rw-rw-. 1 root root 1, 3  8月 26 10:58 2015 /dev/null
$ sudo mknod /opt/chroot/dev/null c 1 3
$ sudo chmod 666 /opt/chroot/dev/null
$ ls -l /opt/chroot/dev/null
crw-rw-rw-. 1 root root 1, 3  8月 26 11:59 2015 /opt/chroot/dev/null
            
ライブラリのコピー
次に エラーメッセージから ldd (1) で参照されたライブラリのうち いくつかがコピーできていない事が確認できる。
chroot 環境にコピーされるのは以下のコマンドなので、 それぞれのコマンドの ldd の結果を参照して 不足しているライブラリをコピーする。
  • /usr/bin/scp
  • /usr/libexec/openssh/sftp-server
  • /usr/bin/rssh
  • /usr/libexec/rssh_chroot_helper
ldd の出力結果からコピーが必要なライブラリを決定しているのだが linux-vdso.so.1ld-linux-x86-64.so.2 は 表示形式が通常と異なっているためにコピーできていない様。
$ ldd /usr/bin/scp
        linux-vdso.so.1 =>  (0x00007ffef53f0000)
        libcrypto.so.10 => /usr/lib64/libcrypto.so.10 (0x00007fbd8e06a000)
        libutil.so.1 => /lib64/libutil.so.1 (0x00007fbd8de67000)
        libz.so.1 => /lib64/libz.so.1 (0x00007fbd8dc50000)
        libnsl.so.1 => /lib64/libnsl.so.1 (0x00007fbd8da37000)
        libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fbd8d800000)
        libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fbd8d5e5000)
        libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007fbd8d3a1000)
        libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007fbd8d0ba000)
        libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007fbd8ce8d000)
        libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007fbd8cc89000)
        libnss3.so => /usr/lib64/libnss3.so (0x00007fbd8c94a000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fbd8c5b5000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fbd8c3b1000)
        libfreebl3.so => /lib64/libfreebl3.so (0x00007fbd8c1ae000)
        libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007fbd8bfa2000)
        libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007fbd8bd9f000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fbd8bb82000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fbd8e662000)
        libnssutil3.so => /usr/lib64/libnssutil3.so (0x00007fbd8b955000)
        libplc4.so => /lib64/libplc4.so (0x00007fbd8b750000)
        libplds4.so => /lib64/libplds4.so (0x00007fbd8b54c000)
        libnspr4.so => /lib64/libnspr4.so (0x00007fbd8b30d000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fbd8b0ee000)
        librt.so.1 => /lib64/librt.so.1 (0x00007fbd8aee5000)
$ ldd /usr/libexec/openssh/sftp-server
        linux-vdso.so.1 =>  (0x00007ffe2abcf000)
        libcrypto.so.10 => /usr/lib64/libcrypto.so.10 (0x00007fbca7520000)
        libutil.so.1 => /lib64/libutil.so.1 (0x00007fbca731d000)
        libz.so.1 => /lib64/libz.so.1 (0x00007fbca7106000)
        libnsl.so.1 => /lib64/libnsl.so.1 (0x00007fbca6eed000)
        libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fbca6cb6000)
        libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fbca6a9b000)
        libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007fbca6857000)
        libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007fbca6570000)
        libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007fbca6343000)
        libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007fbca613f000)
        libnss3.so => /usr/lib64/libnss3.so (0x00007fbca5e00000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fbca5a6b000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fbca5867000)
        libfreebl3.so => /lib64/libfreebl3.so (0x00007fbca5664000)
        libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007fbca5458000)
        libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007fbca5255000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fbca5038000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fbca7b18000)
        libnssutil3.so => /usr/lib64/libnssutil3.so (0x00007fbca4e0b000)
        libplc4.so => /lib64/libplc4.so (0x00007fbca4c06000)
        libplds4.so => /lib64/libplds4.so (0x00007fbca4a02000)
        libnspr4.so => /lib64/libnspr4.so (0x00007fbca47c3000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fbca45a4000)
        librt.so.1 => /lib64/librt.so.1 (0x00007fbca439b000)
$ ldd /usr/bin/rssh
        linux-vdso.so.1 =>  (0x00007ffffe781000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f823c297000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f823c630000)
$ ldd /usr/libexec/rssh_chroot_helper
        linux-vdso.so.1 =>  (0x00007fffb99e9000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fde8f0ff000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fde8f498000)
$ ls -1 /opt/chroot/lib64
libc.so.6
libcom_err.so.2
libcrypt.so.1
libdl.so.2
libfreebl3.so
libgssapi_krb5.so.2
libk5crypto.so.3
libkeyutils.so.1
libkrb5.so.3
libkrb5support.so.0
libnsl.so.1
libnspr4.so
libplc4.so
libplds4.so
libpthread.so.0
libresolv.so.2
librt.so.1
libselinux.so.1
libutil.so.1
libz.so.1
            
linux-vdso.so.1 は各プログラムのアドレス空間のみに常駐する Virtual Dynamic Shared Object なので実体は存在しなくて問題ない。 それ以外に不足しているライブラリ(今回はld-linux-x86-64.so.2) を chroot 環境にコピーする。
検証のための chroot 環境の構築
この後の作業は実際に chroot 環境で作業するとエラー内容が分かりやすいので chroot して解析作業ができる様最低限の環境を構築する。
なおここで設定したファイルは後に削除する事。
# 不足しているライブラリをコピー
$ sudo cp -p /lib64/ld-linux-x86-64.so.2 /opt/chroot/lib64
# /bin/bash の実行を可能に
$ sudo mkdir /opt/chroot/bin
$ sudo cp -p /bin/bash /opt/chroot/bin/
$ sudo cp -p /lib64/libtinfo.so.5 /opt/chroot/lib64
# /usr/bin/ldd の実行を可能に
$ sudo cp -p /usr/bin/ldd /opt/chroot/usr/bin
$ sudo cp -p /bin/cat /opt/chroot/bin
# /usr/bin/strace の実行を可能に
$ sudo cp -p /usr/bin/strace /opt/chroot/usr/bin
            
chroot 環境での動作確認
構築した chroot 環境で動作を確認する。
scp (1) でアカウント情報を正しく取得できていない事が確認できる。
strace (1) で動作を確認してみると /lib64/libnss_files.so.2open (2)エラーになっている事が判る。
# chroot /opt/chroot /bin/sh
# 必要なライブラリーを確認
# ldd /usr/bin/scp
        linux-vdso.so.1 =>  (0x00007fff00345000)
        libcrypto.so.10 => /usr/lib64/libcrypto.so.10 (0x00007f2b2ca5b000)
        libutil.so.1 => /lib64/libutil.so.1 (0x00007f2b2c858000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f2b2c641000)
        libnsl.so.1 => /lib64/libnsl.so.1 (0x00007f2b2c428000)
        libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f2b2c1f1000)
        libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f2b2bfd6000)
        libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007f2b2bd92000)
        libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007f2b2baab000)
        libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007f2b2b87e000)
        libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007f2b2b67a000)
        libnss3.so => /usr/lib64/libnss3.so (0x00007f2b2b33b000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f2b2afa6000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f2b2ada2000)
        libfreebl3.so => /lib64/libfreebl3.so (0x00007f2b2ab9f000)
        libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007f2b2a993000)
        libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007f2b2a790000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f2b2a573000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f2b2d053000)
        libnssutil3.so => /usr/lib64/libnssutil3.so (0x00007f2b2a346000)
        libplc4.so => /lib64/libplc4.so (0x00007f2b2a141000)
        libplds4.so => /lib64/libplds4.so (0x00007f2b29f3d000)
        libnspr4.so => /lib64/libnspr4.so (0x00007f2b29cfe000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f2b29adf000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f2b298d6000)
# /usr/bin/scp -t .
unknown user 0
# strace  /usr/bin/scp -t .
    :
    :
    :
open("/lib64/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/tls/x86_64/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/lib64/tls/x86_64", 0x7ffc3cabffb0) = -1 ENOENT (No such file or directory)
open("/lib64/tls/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/lib64/tls", 0x7ffc3cabffb0)      = -1 ENOENT (No such file or directory)
open("/lib64/x86_64/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/lib64/x86_64", 0x7ffc3cabffb0)   = -1 ENOENT (No such file or directory)
open("/lib64/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/lib64", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/usr/lib64/tls/x86_64/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/usr/lib64/tls/x86_64", 0x7ffc3cabffb0) = -1 ENOENT (No such file or directory)
open("/usr/lib64/tls/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/usr/lib64/tls", 0x7ffc3cabffb0)  = -1 ENOENT (No such file or directory)
open("/usr/lib64/x86_64/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/usr/lib64/x86_64", 0x7ffc3cabffb0) = -1 ENOENT (No such file or directory)
open("/usr/lib64/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/usr/lib64", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
munmap(0x7f86d27d9000, 13667)           = 0
write(2, "unknown user 0\r\n", 16unknown user 0
)      = 16
exit_group(255)                         = ?
+++ exited with 255 +++
            
附属のスクリプトでメッセージに表示されてる様に 必要な libnss_* をコピーする。
$ sudo cp -p /lib64/libnss_files.so.2 /opt/chroot/lib64
            

これで chroot された環境に scp (1)、sftp (1) での アクセスが可能となる。

正常な動作が確認できたら検証のためのコマンドを削除する。

$ sudo rm /opt/chroot/bin/bash
$ sudo rm /opt/chroot/lib64/libtinfo.so.5
$ sudo rm /opt/chroot/usr/bin/ldd
$ sudo rm /opt/chroot/bin/cat
$ sudo rm /opt/chroot/usr/bin/strace
    

2015/06/03

ランダムなパスワード生成

標準的なコマンドを使用してランダムなパスワードを生成する方法を検討。
自分で考えたのは以下のコマンド

$ cat /dev/random | strings | dd bs=11 count=1 2> /dev/null| tr -d '\n'
    
これでも一応ランダムなパスワード的な文字列が取得できる。

対して 上田会長のアイデアはこちら。

$ cat /dev/urandom | tr -dc 'a-z0-9A-Z' | fold -b10
    
tr (1) コマンドの -c オプションは思い浮かばなかった。 まさに目から鱗な気分です。
さすがシェル芸の家元だけあって目の付け所がシャープ

ちなみに Mac だと tr (1) や fold (1) の挙動が異なるので以下になります。

$ cat /dev/urandom | LC_CTYPE=C tr -dc 'a-z0-9A-Z' | fold -w 10
    

2014/06/19

Amazon AWS 上でホスト情報を取得する

Amazon AWS で ELB + AutoScaling を利用している場合など、 IP アドレスが頻繁に変わってしまうので、 グローバルIP、プライベートIP を取得して一覧表示するスクリプト。
ホスト名は Name タグの Value に格納されている必要がある。
aws コマンドは EC2 インスタンスで Amazon Linux を起動した場合は 標準でインストールされている筈。
EC2 インスタンスに適切な role が設定されていれば aws はそのまま動作するが、 role が設定されていない場合でも設定する事で利用可能になる。

引数でホスト名 (Name タグで指定された値) が指定された場合、 デフォルトではグローバル IP のみを出力するが -p オプションでプライベートIP のみを表示する。

  1#!/bin/sh
  2
  3# リージョン
  4region="--region ap-northeast-1"
  5# 出力はテキスト形式
  6output="--output text"
  7# 出力項目を指定 (ホスト名(=タグ)、インスタンスID、パブリックIP、プライベートIP
  8base="--query Reservations[*].Instances[*]."
  9# Name タグの値表示
 10name="Tags[?Key==\`Name\`].Value,"
 11# インスタンスID、パブリックIP、プライベートIP
 12default="${name}InstanceId,PublicIpAddress,PrivateIpAddress" 
 13# フィルタ指定 (running 状態のみ出力)
 14filter="--filter Name=instance-state-name,Values=running"
 15
 16# aws コマンド
 17aws="/usr/bin/aws ec2 describe-instances ${region} ${output}"
 18
 19# メイン関数
 20function    doit()
 21{
 22
 23    local   _filter _query
 24
 25    if [ -n "${1}" ]
 26    then
 27        _filter="${filter} Name=tag:Name,Values=${1}"
 28        _query=${base}[${3}${2}]
 29    else
 30        _filter=${filter}
 31        _query=${base}[${default}]
 32    fi
 33
 34    ${aws} ${_filter} ${_query}
 35
 36}
 37
 38# オプション解析
 39if opt=`getopt p $*`
 40then
 41    set -- ${opt}
 42    while [ -n "${1}" ]
 43    do  
 44        case "${1}" in
 45            -p )
 46                target=PrivateIpAddress;;
 47            -- )
 48                shift
 49                break;;
 50        esac
 51        shift
 52    done
 53else
 54    echo "Usage: ${0##*/} [-p][host [... host]" 1>&2
 55    exit 255
 56fi
 57
 58if [ $# -eq 1 ]
 59then
 60    doit ${1} ${target:-PublicIpAddress}
 61elif [ $# -gt 0 ]
 62then
 63    for i in $*
 64    do
 65        doit ${i} ${target:-PublicIpAddress} ${name}
 66    done | awk '{ if(NR % 2) ip = $0; else printf "%s   %s\n", $0, ip }'
 67else
 68    doit | awk '{ if(NR % 2) ip = $0; else printf "%s   %s\n", $1, ip }'
 69fi 
    

2013/11/23 追記

複数ホスト名が指定された場合はホスト名も表示する様変更。

2014/05/23

checkinstall で rpm パッケージの作成がエラーになる場合

rpm パッケージを手軽に作成するために checkinstall は非常に便利だが、 現行の checkinstall にはインストール対象となるソフトウェアに ファイルに対するシンボリックリンクが含まれると rpm パッケージが作成できなくなる不具合がある様だ。

この不具合は checkinstall が rpm パッケージ用の spec ファイルを生成する際に、 ターゲットとなるオブジェクトがディレクトリかシンボリックリンクなら 無条件にディレクトリと仮定して %dir と出力しているために発生している。 そのために、例えばインストール時に libhoge.so.X.Ylibhoge.so にシンボリックリンクする様なソフトウェアの rpm パッケージを作成しようとするとエラーとなってしまう。

そこでターゲットなるオブジェクトがシンボリックリンクの場合、 リンク先を確認してディレクトリだった場合のみ %dir を出力する様にするパッチを作成した。

*** /usr/sbin/checkinstall~ 2014-05-21 12:29:30.000000000 +0900
--- /usr/sbin/checkinstall  2014-05-23 23:51:09.131081380 +0900
***************
*** 2428,2434 ****
  # Prepare directories to be included in the .spec file
  mv ${TMP_DIR}/newfiles ${TMP_DIR}/newfiles.tmp
  cat ${TMP_DIR}/newfiles.tmp | while read line; do
!    [ -d "${BUILD_DIR}/${line}" -o -L "${BUILD_DIR}/${line}" ] && echo -n "%dir " >> ${TMP_DIR}/newfiles
     echo "\"/${line}\"" >> ${TMP_DIR}/newfiles
  done
  
--- 2428,2435 ----
  # Prepare directories to be included in the .spec file
  mv ${TMP_DIR}/newfiles ${TMP_DIR}/newfiles.tmp
  cat ${TMP_DIR}/newfiles.tmp | while read line; do
!    [ -d "${BUILD_DIR}/${line}" ] && echo -n "%dir " >> ${TMP_DIR}/newfiles
!    [ -L "${BUILD_DIR}/${line} -a -d `ls -l ${BUILD_DIR}/${line} | awk '$0=$NF'` ] && echo -n "%dir" >> ${TMP_DIR}/newfiles
     echo "\"/${line}\"" >> ${TMP_DIR}/newfiles
  done
    

/usr/sbin/checkinstall にこのパッチを適用する事で シンボリックリンク先がディレクトリの場合のみ %dir を出力する様に変更される。
シンボリックリンクがネストしている場合は正しく動作しないので あくまでも暫定的なパッチだが、 取りあえず rpm パッケージを作成した場合にはそれなりに有用だと思う。
一応 checkinstall の配布元の bugzilla にはパッチを添えて報告してある。

ちなみに checkinstall で rpm の作成がエラーになる場合は 殆どが自動生成された spec ファイルが問題な場合なので、 --review-spec オプションを指定して checkinstall を実行する事で、 rpm パッケージの作成処理を実行する前に 自動生成された spec ファイルを編集できるので問題を回避できる事が多い。

# checkinstall --review-spec</i> 
    

2014/05/22

Amazon Linux AMI 2014.03.1 (64bit) への checkinstall 導入

Amazon AWS の EC2 インスタンスで動作している Amazon Linux AMI 2014.03.1 (Linux ****** 3.10.40-50.136.amzn1.x86_64 #1 SMP Tue May 13 21:35:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux) への checkinstall の導入時にエラーとなった場合の対処方法。

Amazon AWS の EC2 インスタンスに checkinstall コマンドを導入しようとして、 ネットなどの情報に基づいて以下のコマンドを実行した所コンパイルエラーが発生した。

$ git clone http://checkinstall.izto.org/checkinstall.git
$ cd checkinstall
$ vi Makefile checkinstall checkinstallrc-dist installwatch/Makefile
$ make
    :
    :
make -C installwatch
make[1]: ディレクトリ `/home/mitz/checkinstall/installwatch' に入ります
gcc -Wall -c -D_GNU_SOURCE -DPIC -fPIC -D_REENTRANT -DVERSION=\"0.7.0beta7\" installwatch.c
installwatch.c:2960:5: error: conflicting types for ‘readlink’
 int readlink(const char *path,char *buf,size_t bufsiz) {
     ^
In file included from installwatch.c:41:0:
/usr/include/unistd.h:831:16: note: previous declaration of ‘readlink’ was here
 extern ssize_t readlink (const char *__restrict __path,
                ^
installwatch.c:3098:5: error: conflicting types for ‘scandir’
 int scandir( const char *dir,struct dirent ***namelist,
     ^
In file included from installwatch.c:49:0:
/usr/include/dirent.h:255:12: note: previous declaration of ‘scandir’ was here
 extern int scandir (const char *__restrict __dir,
            ^
installwatch.c:3714:5: error: conflicting types for ‘scandir64’
 int scandir64( const char *dir,struct dirent64 ***namelist,
     ^
In file included from installwatch.c:49:0:
/usr/include/dirent.h:278:12: note: previous declaration of ‘scandir64’ was here
 extern int scandir64 (const char *__restrict __dir,
            ^
make[1]: *** [installwatch.o] エラー 1
make[1]: ディレクトリ `/home/mitz/checkinstall/installwatch' から出ます
make: *** [all] エラー 2
$
    
ざっと調べてみると glibc のバージョン情報を格納していると思われるマクロ GLIBC_MINOR の値が不正で readlink()、 及び内部で定義している scandir()scandir64() のプロトタイプ宣言が正しく展開されていない様だったので、 他にもいくつか発生したコンパイルエラーを修正して 取りあえずコンパイルしてインストール可能なパッチを作成した。
本来は Amazon LINUX 上でも glibc のバージョンを正しく取得できる様にするべきなのだが、 諸処の事情でそこまで修正する余裕がないので暫定的な対策のみとなっている。

  1diff -rc checkinstall/installwatch/installwatch.c checkinstall~/installwatch/installwatch.c
  2*** checkinstall/installwatch/installwatch.c    2014-05-21 12:41:51.659879736 +0900
  3--- checkinstall~/installwatch/installwatch.c   2014-05-21 12:41:16.107577033 +0900
  4***************
  5*** 99,105 ****
  6  static int (*true_xstat)(int,const char *,struct stat *);
  7  static int (*true_lxstat)(int,const char *,struct stat *);
  8  
  9! #if(GLIBC_MINOR >= 10)
 10  
 11  static int (*true_scandir)(   const char *,struct dirent ***,
 12                int (*)(const struct dirent *),
 13--- 99,105 ----
 14  static int (*true_xstat)(int,const char *,struct stat *);
 15  static int (*true_lxstat)(int,const char *,struct stat *);
 16  
 17! #if 1
 18  
 19  static int (*true_scandir)(   const char *,struct dirent ***,
 20                int (*)(const struct dirent *),
 21***************
 22*** 130,136 ****
 23  static int (*true_open64)(const char *, int, ...);
 24  static struct dirent64 *(*true_readdir64)(DIR *dir);
 25  
 26! #if(GLIBC_MINOR >= 10)
 27  static int (*true_scandir64)( const char *,struct dirent64 ***,
 28                int (*)(const struct dirent64 *),
 29                int (*)(const struct dirent64 **,const struct dirent64 **));
 30--- 130,136 ----
 31  static int (*true_open64)(const char *, int, ...);
 32  static struct dirent64 *(*true_readdir64)(DIR *dir);
 33  
 34! #if 1
 35  static int (*true_scandir64)( const char *,struct dirent64 ***,
 36                int (*)(const struct dirent64 *),
 37                int (*)(const struct dirent64 **,const struct dirent64 **));
 38***************
 39*** 2545,2551 ****
 40  }
 41  
 42  FILE *fopen(const char *pathname, const char *mode) {
 43!   FILE *result;
 44    instw_t instw;
 45    int status=0;
 46  
 47--- 2545,2551 ----
 48  }
 49  
 50  FILE *fopen(const char *pathname, const char *mode) {
 51!   FILE *result = NULL;
 52    instw_t instw;
 53    int status=0;
 54  
 55***************
 56*** 2956,2962 ****
 57    return result;
 58  }
 59  
 60! #if (GLIBC_MINOR <= 4)
 61  int readlink(const char *path,char *buf,size_t bufsiz) {
 62    int result;
 63  #else
 64--- 2956,2962 ----
 65    return result;
 66  }
 67  
 68! #if 0
 69  int readlink(const char *path,char *buf,size_t bufsiz) {
 70    int result;
 71  #else
 72***************
 73*** 3096,3103 ****
 74  }
 75  
 76  int scandir(  const char *dir,struct dirent ***namelist,
 77!       int (*select)(const struct dirent *),
 78! #if (GLIBC_MINOR >= 10)
 79        int (*compar)(const struct dirent **,const struct dirent **)    ) {
 80  #else
 81        int (*compar)(const void *,const void *)    ) {
 82--- 3096,3103 ----
 83  }
 84  
 85  int scandir(  const char *dir,struct dirent ***namelist,
 86!       int (*filter)(const struct dirent *),
 87! #if 1
 88        int (*compar)(const struct dirent **,const struct dirent **)    ) {
 89  #else
 90        int (*compar)(const void *,const void *)    ) {
 91***************
 92*** 3114,3124 ****
 93      /* We were asked to work in "real" mode */
 94    if( !(__instw.gstatus & INSTW_INITIALIZED) ||
 95        !(__instw.gstatus & INSTW_OKWRAP) ) {
 96!       result=true_scandir(dir,namelist,select,compar);
 97        return result;
 98    }
 99  
100!   result=true_scandir(dir,namelist,select,compar);
101  
102    return result;
103  }     
104--- 3114,3124 ----
105      /* We were asked to work in "real" mode */
106    if( !(__instw.gstatus & INSTW_INITIALIZED) ||
107        !(__instw.gstatus & INSTW_OKWRAP) ) {
108!       result=true_scandir(dir,namelist,filter,compar);
109        return result;
110    }
111  
112!   result=true_scandir(dir,namelist,filter,compar);
113  
114    return result;
115  }     
116***************
117*** 3712,3719 ****
118  }
119  
120  int scandir64(    const char *dir,struct dirent64 ***namelist,
121!       int (*select)(const struct dirent64 *),
122! #if (GLIBC_MINOR >= 10)
123        int (*compar)(const struct dirent64 **,const struct dirent64 **)    ) {
124  #else
125        int (*compar)(const void *,const void *)    ) {
126--- 3712,3719 ----
127  }
128  
129  int scandir64(    const char *dir,struct dirent64 ***namelist,
130!       int (*filter)(const struct dirent64 *),
131! #if 1
132        int (*compar)(const struct dirent64 **,const struct dirent64 **)    ) {
133  #else
134        int (*compar)(const void *,const void *)    ) {
135***************
136*** 3730,3740 ****
137      /* We were asked to work in "real" mode */
138    if( !(__instw.gstatus & INSTW_INITIALIZED) ||
139        !(__instw.gstatus & INSTW_OKWRAP) ) {
140!       result=true_scandir64(dir,namelist,select,compar);
141        return result;
142    }
143  
144!   result=true_scandir64(dir,namelist,select,compar);
145  
146    return result;
147  }     
148--- 3730,3740 ----
149      /* We were asked to work in "real" mode */
150    if( !(__instw.gstatus & INSTW_INITIALIZED) ||
151        !(__instw.gstatus & INSTW_OKWRAP) ) {
152!       result=true_scandir64(dir,namelist,filter,compar);
153        return result;
154    }
155  
156!   result=true_scandir64(dir,namelist,filter,compar);
157  
158    return result;
159  }     
    
2014/05/22 に git clone した checkinstall のソースに このパッチを適用してコンパイル、動作する事は確認できている。
Amazon Linux で checkinstall の導入に困っている方の参考になればと思い公開してみる。


Copyright © 2008-2020 Mitzyuki IMAIZUMI. All rights reserved.