2011年の震源地マップ

データが大きくても耐えられるのか?

1週間分のデータなら処理できるとして,年間データでも動くスクリプトなのか試してみようと思っていたところ,地震データの検索サイトを見つけました.そこから地図で表示している緯度経度の地震データを抽出して図示してみました.

2011年の震源

こんな感じになります.秒あたり2日分のマップを表示しています

震源地mapを作る

はじめに

Rを使えば震源地がどう移り変わって行くか可視化できることを知ったので作ってみました.Rでスクリプトを書いてみたのは初めてなのでわからないことだらけだったので,調べたことを自分のメモがわりにまとめてみます.ソースと使い方はgithubに載せています.

準備

Rは対話モードで使うことが多いと思うのですが,今回はスクリプトとしてまとめました.スクリプトとして動かすさいにはrscriptを使います.さらに,今回はグラフ作成にggplot2,地図データとしてmaps,時系列データの取扱にxtsを使います.zooも読み込んでいますが,xtsを読み込むと自動で読み込むので,必要ないです.後で修正しないと.ライブラリは
対話モードで"install.packages()"メソッドを使って読み込むのが楽でしょう.例えば

> install.packages("ggplot2")

と実行します.そうするとどこのミラーからライブラリを落とすか聞いてくるので,適当に選べばインストールしてくれます.毎回聞かれるのが面倒な時は~/.Rprofileに

options(repos="http://cran.cnr.Berkeley.edu")

と記述すると,ここに記載したurlから落としてくれます.なんだか国内ミラーの調子が悪かったのでberkeleyを選んだ時の例です.

#!/usr/bin/env rscript
library(ggplot2)
library(maps)
library(zoo)
library(xts)

normDepthとnormMagnitudeは関数で,それぞれ震源地の深さと規模をグルーピングしています.図示するときの利便性を確保するため,というか,震源地に深さや規模に応じた点を描く際,毎日同じ基準で描くために使ったトリックです.別の方法でmax/minを指定すれば同じ規模なら同じ大きさの点が打てるかもしれないですが,やりかたがわかりませんでした.以下はnormMagnitudeの関数定義です.

normMagnitude <- function(x){
     if (x > 5) return(5)
     if (x > 4) return(4)
     if (x > 3) return(3)
     if (x > 2) return(2)
     1
}

データ読み込み

ここの箇所がデータ変形の肝になります.まず,csv形式の震源地データをネットから取得してdata.frame形式で格納します

catalog <- read.csv(
     "http://earthquake.usgs.gov/earthquakes/catalogs/eqs7day-M2.5.txt"
)

続いて正規表現を使ってjapan regionのデータだけを抽出し,data.frame形式で格納します

japan <- catalog[grep("Japan", catalog$Region),]

Datetime列はUTC時刻表記なのでこれをJSTに変換します.これが最初のはまりどころでした.色々やってみた結果60*60*9を足すとJST表記に変わることが偶然判ったんだけど,そんなことがどこに書いてあるのか見つけられませんでした.

time <- strptime(japan$Datetime, "%A, %B %d, %Y %H:%M:%S UTC") + 60*60*9

深さと規模を正規化して,JST表記の時刻と緯度経度を組み合わせてdata.frame形式で格納します

Depth <- sapply(japan$Depth, normDepth)
Magnitude <- sapply(japan$Magnitude, normMagnitude)
quake <- cbind(time, japan[5:6], Depth, Magnitude)

ここまでで元データが完成しました.

図の下準備

ここは定型処理です.mapsというライブラリは米国をターゲットにしているようで日本地図は概要しかありません.他にもっと詳細なデータを持っているライブラリがあるようですが,今回は調べていません.以下は東経120度から150度,北緯25度から50度までの地図データを抽出しています.

xydata <- as.data.frame(
     map("world", xlim = c(120, 150), ylim = c(25, 50), plot = FALSE)
     [c("x", "y")]
)
map <- ggplot(xydata, aes(x, y))
map <- map + geom_path()

この部分だけを対話モードで実行することもできます.その際は

> print(map)

と実行すれば該当エリアの地図を表示することができます.

日々のデータに分離

時系列データを扱うのはxtsが得意なのでdata.frameから変換します.直接変換はできないようなので,zooを経由します.これもノウハウです.一旦xts形式に変換すると,splitを使って日ごとのリストに分割することができます.リストの各要素に対してplotdataという関数を適用して図を作成しています.

quake.xts <- as.xts(read.zoo(quake))
days <- split(quake.xts, "days")
for ( day in days ) plotdata(day)

plotdata(day)

この部分はきれいなスクリプトがかけていません.indexというグローバル変数でファイル名を作っているところがいまいち.img%03d.pngという名前でファイルを作成しているのはffmegで動画を作るためです.

index <- 1

plotdataは日ごとに分割されたxts形式のデータです.ggplot2はxts形式を扱えないので,data.frameに戻しています.coredata()というのがそのためのメソッドで,これを使うと各要素がfactor型になるので,必要な項目に関してはas.numeric()を使って数字型に戻しています.

plotdata <- function(x){
      j <- data.frame(coredata(as.numeric(x$Lat)),
          coredata(as.numeric(x$Lon)),
          coredata(x$Magnitude),
          coredata(x$Depth)
     )

これでdata.frame型に変換できましたが,列名がごちゃごちゃしているので,すっきりさせます.

     colnames(j) <- c("Lat", "Lon", "Magnitude", "Depth")

震源地をプロットします.サイズと色をfactorで指定しているのは,その日にどんな地震があったとしても,同じ規模なら同じサイズ,同じ深さなら同じ色で描くためのノウハウです.

     map.point <- map + geom_point(
          data = j,
          aes(x = Lon, y = Lat,
               size = factor(Magnitude, c(1, 2, 3, 4, 5, 6, 7, 8, 9)),
               colour = factor(Depth, c(0, 10, 20, 30, 50, 100, 200, 700))
          )
     )

説明表記をすっきりさせて

     map.point <- map.point + scale_colour_discrete("Depth")
     map.point <- map.point + scale_size_discrete("Magnitude")

ファイルに落とします

     png(filename = paste(sprintf("data/img%03d", index), "png", sep = "."))
     index <<- index + 1
     print(map.point)
     dev.off()
}

これでできあがり.dataディレクトリ配下に地震データが... あれ,6個しかない.なぜだ?調べなきゃ.後はdataディレクトリに移動して

ffmpeg -r 1 -f image2 -i img%03d.png  -r 1 quakemapjp.mp4

を実行すれば震源地の推移を表す動画ができあがります.

おわりに

実行すると色々ワーニングがでたり,上に書いたように1週間分のデータのはずなのに6個しか出力してなかったりまだまだですが,とりあえず動いたのでまとめてみました.随時github上で修正していくつもりです.って,Rネタに興味を持ってくれる人ってどれくらいいるんだろうかなぁ.それはそれとして,mp4に変換するとはてダにあげられないのに気づいたので,gifアニメにできるかどうかも試してみます.

追加

スクリプトで作成したpngファイルをimagemagickについてくるconvertコマンドを使ってanimation GIFに変換しました.コマンドは

convert -delay 50 img*.png quake.gif

このようになります.

さらに追加

convertでdelayオプションに渡すパラメータを間違ってました.修正して画像も変更しました.秒あたり2日分の地図を表示するようにしています.

Rで書かれた2012カレンダーの中身

はじめに

最近R言語について調べていてCalendaR 2012 with ggplot2という記事を見つけました.どうやったらこんなことができるのかわからなかったので中身を追いかけてみたメモです.

行ごとの解説

library(ggplot2)
year <- 2012

ggplot2という画像ライブラリを呼び込んで,変数yearに2012をセットしています.これは簡単.

d <- seq.Date(as.Date(paste(year, "-01-01", sep = "")), 
 as.Date(paste(year, "-12-31", sep = "")), by="days")
z <- data.frame(Y = format(d, "%Y"), M = as.numeric(format(d, "%m")),
 D = as.numeric(format(d, "%d")), W = format(d, "%w"))

dに"2012-01-01"から"2012-12-31"までのDateオブジェクトのシーケンスを作ります.zには年,月,日,曜日を一つの要素としたデータフレームを作っています.それぞれの先頭二つのデータは以下のようになっています.

> d[1]
[1] "2012-01-01"
> d[2]
[1] "2012-01-02"
> z[1,]
     Y M D W
1 2012 1 1 0
> z[2,]
     Y M D W
2 2012 1 2 1

カレンダーに使うデータはこれだけで,後は図示するためのしくみです.

c <- ggplot(z, aes(D, M))

zオブジェクトのデータを横軸を日(D),縦軸を月(M)で表示する宣言.

c + geom_text(aes(label=z$D, colour=factor(W), size = 20))

日(z$D)をラベルとして曜日(W)に基づいた色を使います.サイズは20,う,単位は何だろう.

+ scale_colour_manual(values = c("magenta", rep("black", 5),"darkturquoise"))

日曜日(0)がマジェンタ,月曜から金曜までの5日間は黒,土曜はダークターコイズ

+ scale_y_continuous(trans = "reverse", breaks = 1:12)

通常だと縦軸が下から1,2,3になるので,カレンダーとして使うために上から1,2,3になるようにしてる.

+ labs(x="", y="")
+ opts(title = paste("CalendaR", year, "\n", sep = " "),
plot.title = theme_text(colour = "black"),
legend.position = "none"
)

縦横のラベルを消して,表題を付けて,曜日ごとの色の説明を消してる(legend.position).

おまけ

単に調べただけでは面白くないので,カレンダーに休日を追加してみました.Wには曜日にあたる0〜6までの数字が入っているのですが,休日用に7を追加します.データフレームを置き換えるを参考にしました.

levels(z$W)<-c(levels(z$W),7)
z[z$Y==2012 & z$M==1 & z$D==1,]<-list(2012,1,1,7)
z[z$Y==2012 & z$M==1 & z$D==2,]<-list(2012,1,2,7)
z[z$Y==2012 & z$M==1 & z$D==9,]<-list(2012,1,9,7)
z[z$Y==2012 & z$M==2 & z$D==11,]<-list(2012,2,11,7)
z[z$Y==2012 & z$M==3 & z$D==20,]<-list(2012,3,20,7)
z[z$Y==2012 & z$M==4 & z$D==29,]<-list(2012,4,29,7)
z[z$Y==2012 & z$M==4 & z$D==30,]<-list(2012,4,30,7)
z[z$Y==2012 & z$M==5 & z$D==3,]<-list(2012,5,3,7)
z[z$Y==2012 & z$M==5 & z$D==4,]<-list(2012,5,4,7)
z[z$Y==2012 & z$M==5 & z$D==5,]<-list(2012,5,5,7)
z[z$Y==2012 & z$M==7 & z$D==16,]<-list(2012,7,16,7)
z[z$Y==2012 & z$M==9 & z$D==17,]<-list(2012,9,17,7)
z[z$Y==2012 & z$M==9 & z$D==22,]<-list(2012,9,22,7)
z[z$Y==2012 & z$M==10 & z$D==8,]<-list(2012,10,8,7)
z[z$Y==2012 & z$M==11 & z$D==3,]<-list(2012,11,3,7)
z[z$Y==2012 & z$M==11 & z$D==23,]<-list(2012,11,23,7)
z[z$Y==2012 & z$M==12 & z$D==23,]<-list(2012,12,23,7)
z[z$Y==2012 & z$M==12 & z$D==24,]<-list(2012,12,24,7)

後は表示の色を細工すれば完成です.

scale_colour_manual(values = c("magenta", rep("black", 5),"red","chocolate"))

動かしてみると,こんなカレンダーができあがります.

jsmodemを使ってみる

はじめに

jslinuxを使ってみてその完成度の高さに驚いたものの,ネットワークが動いてないのは残念だと思っていました.そう思いつつ自分でネットワーク廻りを作るのはしんどいなぁとぐぐって見つけたのがjsmodemです.youtubeを使った説明もあって,どんなことができるのか見てみることができます.でもそこはやはり自分でやってみないとつまらないので,自前環境で動かしてみました.これはそのまとめのメモです.

準備

サーバとしてvmware上のx86ベースのcentos5.6を使いました.まずはgitをインストールして

$ wget http://dag.wieers.com/rpm/packages/rpmforge-release/rpmforge-release/rpmforge-release-0.5.2-2.el5.rf.i386.rpm
$ sudo rpm -ivh rpmforge-release-0.5.2-2.el5.rf.i386.rpm
$ sudo vi /etc/yum.repos.d/rpmforge.repo

# s/enabled=1/enabled=0/
# ↑ファイル内の変更点

$ sudo yum install git --enablerepo=rpmforge

jsmodemを落として動かしてみたら.socatがないと.これは

$ sudo yum install socat

で解決ですが,python26も必要と言われます.これは

$ sudo rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-4.noarch.rpm
$ sudo vi /etc/yum.repos.d/epel.repo

# s/enabled=1/enabled=0/
# ↑ファイル内の変更点

$ sudo yum install python26 --enablerepo=epel
$ sudo yum install python26-numpy --enablerepo=epel

として解決.後は2080ポートにアクセスするので,vmwareでnat設定を変更して,母艦の2080ポート向けの通信をcentosの2080ポートに届けます.

動かしてみる

どうもsocatは一度セションが切れるとプロセスが終了するようなので,serve.shを起動するのではなく,その中身のコマンドをひとつづつ母艦で実行しながら動作確認してる段階です.

$ sudo socat -d -d PTY,link=/dev/jsppp,raw,echo=0 TCP-LISTEN:2001
$ sudo pppd /dev/jsppp debug noauth passive 10.0.5.1:10.0.5.2
$ python2.6 websockify -D --web=./ 2080 127.0.0.1:2001

↑は実際には別シェルから実行して試しています.socatだけ工夫すればここまでしなくてもよいと思うのですが,まだ確認してません.
jslinuxが立ち上がったら,rootでログインして

# ./ppp_up
# ifconfig -a
...
ppp0      Link encap:Point-to-Point Protocol                                    
          inet addr:10.0.5.2  P-t-P:10.0.5.1  Mask:255.255.255.255              
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1            
          RX packets:20 errors:0 dropped:0 overruns:0 frame:0                   
          TX packets:22 errors:0 dropped:0 overruns:0 carrier:0                 
          collisions:0 txqueuelen:3                                             
          RX bytes:1360 (1.3 KiB)  TX bytes:1203 (1.1 KiB)

となるのを確認します.これでppp接続ができました.次は母艦側で外に出る設定をします.

$ sudo /sbin/iptables -F
$ sudo /sbin/iptables -t nat -F
$ sudo /sbin/iptables -t mangle -F
$ sudo /sbin/iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to 192.168.63.129
# echo 1 > /proc/sys/net/ipv4/ip_forward

eth0と192.168.63.129は私のvmwareでの設定になります.個々の環境に依存する値で,外,つまりインターネットに向いている側のインタフェース名とそのIPアドレスを指定します.これで外に出られるようになりました.後は名前解決が必要なので,

# cat /etc/resolv.conf
search localdomain
nameserver 192.168.63.2

などと設定します.最初は母艦のアドレスを指定したのですがそれでは動きませんでした.ここで記載しているIPアドレスは母艦の/etc/resolv.confに指定されていた私のvmwareでの例です.これで名前解決もできるので

# ping www.perl.com                                                             
PING www.perl.com (207.171.7.72): 56 data bytes                                 
64 bytes from 207.171.7.72: seq=0 ttl=127 time=103.510 ms                       
64 bytes from 207.171.7.72: seq=1 ttl=127 time=107.590 ms                       
64 bytes from 207.171.7.72: seq=2 ttl=127 time=112.811 ms

となって外に出れていることが分かります.ちなみに,母艦上でもpingを実行すると

$ ping www.perl.com
PING www.perl.com (207.171.7.72) 56(84) bytes of data.
64 bytes from mt.perl.org (207.171.7.72): icmp_seq=1 ttl=128 time=107 ms
64 bytes from mt.perl.org (207.171.7.72): icmp_seq=2 ttl=128 time=105 ms
64 bytes from mt.perl.org (207.171.7.72): icmp_seq=3 ttl=128 time=110 ms

のような値になるので,体感できるほど遅いなどということはありません.

おわりに

いやー,ホントに面白いですね.dhcpとかと組み合わせて複数のjslinuxをぶら下げられるサーバ作ってみようかな.dotcloudとかに.
あ,後.jslinux.jsでwebsocketをlocalhost:2080にコネクトするようハードコードされていたので,書き直して使っています.

// modem.connect('localhost', 2080);
modem.connect(location.hostname, 2080);

セキュリティ対策なのかな?

jslinuxで遊んでみる

はじめに

javascriptlinuxが動くjslinuxが公開されて1ヶ月が経ちました.人の噂も75日とはよく言ったもので,すでに話題にもあがらなくなってきましたね.多少新鮮度は落ちたとはいえ,面白い物は面白いってことで,ちょっと遊んでみることにしました.

でっかくしてみる

小飼さんのブログでjslinuxと戯れる方法がでています.ここにも書いてあるんですが,デフォルトのkernelではroot.binのサイズを4096KBより大きくすることができません.そこで,倍のサイズのroot.binを動かすことを目標に試してみることにしました.まずは必要なリソースを集めます.小飼さんがブログを書いた時期からjslinuxはバージョンアップしているので,以下のファイルをローカルに落とします.例として~/jslinux/htdocs/配下に置くものとします.

次にroot.binを大きくします

$ mkdir mnt
$ mkdir roots
$ sudo mount -t ext2 -o loop root.bin mnt
$ dd if=/dev/zero of=roots/root.bin bs=1k count=8192
$ sudo /sbin/mke2fs roots/root.bin
$ sudo mount -t ext2 -o loop roots/root.bin mnt2
$ sudo cp -dpR mnt/* mnt2/
$ sudo umount mnt mnt2
$ mv root.bin roots/root.bin.org
$ cp roots/root.bin .

これで8MBのroot.binができました.

kernelのリコンパイル

jslinuxが4MBよりでかいRAMファイルシステムを扱えないのは,kernelのコンパイルオプションで

CONFIG_BLK_DEV_RAM_SIZE=4096

が指定してあるからです.これはjslinux用のパッチとかコンフィグファイルに含まれている,config_linux-2.6.20に記載されています.本来ならbootオプションでramdisk_sizeを指定すれば扱えるファイルシステムサイズは変えられると思ったのですが,うまくいかないようです.ここら辺はまたいつか詳しく調べてみたいと思っています.
さて,kernelを作ることにします.最初x86_64アーキテクチャのマシンで作ろうとしたのですが,x86用のkernelを作れませんでした.単にクロスコンパイルのやり方を忘れているだけなのですが,調べるのが面倒になったので><,以前作ってそのままになっていたcentos5.3 i386版を使ってみました.これのkernelバージョンは

$ uname -a
Linux fig 2.6.18-238.12.1.el5 #1 SMP Tue May 31 13:23:01 EDT 2011 i686 i686 i386 GNU/Linux

となっていました.ざっくりと作り方を書いてみると,

$ cd
$ curl -O http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.20.tar.gz
$ curl -O http://bellard.org/jslinux/linuxstart-20110526.tar.gz
$ tar xvfz linux-2.6.20.tar.gz
$ tar xvfz linuxstart-20110526.tar.gz
$ mv linux-2.6.20 linux-2.6.20.org
$ mv tmp/linuxstart-20110526 .
$ patch -p0 < linuxstart-20110526/patch_linux-2.6.20
$ cd linux-2.6.20.org
$ make mrproper
$ sudo yum install ncurses-devel ← curses.hが入ってなかった
$ make O=/home/kei/kernel/ menuconfig

とすると,kernel設定用のメニュー画面がでてきます.ここで"Load an Alternate Configuration File"コマンドを選択して"config_linux-2.6.20"を読み込み,メニューを終了します.これ使わずにコンフィグを読み込む方法があると思うのですが,まだ見つけていません.誰か知ってたら教えてくださいませ.んで,

$ make O=/home/kei/kernel
$ cd ~/kernel
$ object -O binary vmlinux vmlinux26.bin
$ cp vmlinux26.bin ~/jslinux/htdocs

とやればkernelの準備もできあがりです.そうそう,config_linux-2.6.20を読み込む前に

CONFIG_BLK_DEV_RAM_SIZE=16384

と変更しておくのを忘れずに.

準備

jslinux.jsで物理メモリサイズを

    /* memory size (in bytes) */
    params.mem_size = 16 * 1024 * 1024;

と定義しているので,これを

    /* memory size (in bytes) */
    params.mem_size = 32 * 1024 * 1024;

とかにしてメモリサイズを増やしておきます.ファイルサイズと同じ大きさじゃ動かないでしょうからね.最後に

use Plack::Builder;

builder {
	enable "Plack::Middleware::Static",
		path => qr{^/}, root => 'htdocs/';
	$app;
};

という内容のapp.psgiファイルを作って~/jslinuxディレクトリにおいて

$ sudo plackup --port 80

とやってアクセスしてみると…

RAMDISK: Loading 8192KiB [1 disk] into ram disk... done.
VFS: Mounted root (ext2 filesystem).
Freeing unused kernel memory: 128k freed
Booted in 12.678 s
Welcome to JS/Linux
~ #

となってうまく動きました.

次は

デバイスドライバを作ってwebsocketでサーバと通信するようにしてみたいですね.もうデバイスドライバの作り方なんてすっかり忘れているので,いつできるかさっぱりわからないですけど…

MEDIASでperlプログラミング

はじめに

iPhone 3Gを使っているんですが,docomoのMEDIASを使うことになりました.元々別の機種に変えようとは思っていたんですが,訳あって2台持ちとなっています.そのうちiPhoneは解約しますけど.んで,せっかくAndroid機を手に入れたのでsl4aを入れてperlでプログラミングしようと思い立ちました.この記事はサンプルスクリプトを動かすまでについて書いています.

Macからのadb

sl4aを使うだけなのでAndroid SDKをすべてインストールする必要はありません.adbがあればいいだけなのですが,ここではまりました.Macからusbを使ってadbでMEDIASを制御するにはMEDIASのベンダIDが必要で,これを~/.android/adb_usb.iniに記載しておく必要があったのです.ちなみに,adb_usb.iniファイルは

$ android update adb

を実行すると作成されます.ここにMEDIAS用の0x0409を記入します.こんな感じ.

# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.
# USE 'android update adb' TO GENERATE.
# 1 USB VENDOR ID PER LINE.
0x0409

これで

$ adb devices
List of devices attached 
xxxxxxxxxxxxxxx	device

(xxxの場所は数字が入ります)と出力されてMEDIASへのアクセスができるようになりました.注意しないといけないのは,アクセスする前にAndroid本体で【アプリケーション】→【開発】を選択して出てくる,【USBデバッグ】にチェックを入れておくことです.これに気づかなくて,私は時間をえらく無駄にしてしまいました.ベンダIDは,/Developer/Applications/Utilities/USB Proberで探すことができます.

サンプルスクリプト

adbも動くようになったので,早速サンプルスクリプトを動かしてみました.すると,hello_world.plは動くのですが,test.plが動きません.最初に動かした際にはちゃんと動いたはずなのに,しばらくAndroidを使ってからまた試してみると,エラーが出て動かなくなりました.よく見ると,unicore/PVA.plがないとかエラーがでていました.これはAndroid.pmがJSONを使う際にutf8を考慮しない作りになっているからで,

$droid->getClipboard()

で取得するクリップボードの中身にマルチバイト文字が入ってると発生します.最初に使った際には,偶然マルチバイト文字が入っていなかったのでしょう.twitterで動かねーと騒いでいたら対応方法をHideaki Ohnoさんに教えてもらったので,ちょっとやりかた変えてAndroidに突っ込んでみました.Ohnoさんはdo_rpc()の中にでてくるto_json, from_jsonに{utf8 => 1}属性を入れていますが,encode_json, decode_jsonに変えれば動くんじゃないかと思って試してみたら動きました.そこで変更後のスクリプトは以下のようになります.
さらに,Android.pmを直接編集すると後でわからなくなると思ったので,pluginを作成しています.adbを使ってAndroid.pmがある/mnt/sdcard/com.googlecode.perlforandroid/extras/perl/site_perlディレクトリにAndroid/Pluginというディレクトリを作って,その中にMine.pmというモジュールを置きました.

package Android::Plugin::Mine;
use strict;

sub import {
    my $class = shift;
    *Andoid::do_rpc = sub {
        my $self = shift;
        if ( $self->trace ) {
            show_trace(qq[do_rpc: $self: @_]);
        }
        my $method  = pop;
        my $request = encode_json(
            {
                id     => $self->{id},
                method => $method,
                params => [@_]
            }
        );
        if ( defined $self->{conn} ) {
            print { $self->{conn} } $request, "\n";
            if ( $self->trace ) {
                show_trace(qq[client: sent: "$request"]);
            }
            $self->{id}++;
            my $response = readline( $self->{conn} );
            chomp $response;
            if ( $self->trace ) {
                show_trace(qq[client: rcvd: "$response"]);
            }
            if ( defined $response && length $response ) {
                my $result  = decode_json($response);
                my $success = 0;
                my $error;
                if ( defined $result ) {
                    if ( ref $result eq 'HASH' ) {
                        if ( defined $result->{error} ) {
                            $error = encode_json( { error => $result->{error} } );
                        }
                        else {
                            $success = 1;
                        }
                    }
                    else {
                        $error = "illegal JSON reply: $result";
                    }
                }
                unless ( $success || defined $error ) {
                    $error = "unknown JSON error";
                }
                if ( defined $error ) {
                    printf STDERR "$0: client: error: %s\n", $error;
                }
                if ( $Androd::Opt{trace} ) {
                    print STDERR Data::Dumper->Dump( [$result], [qw(result)] );
                }
                return $result;
            }
        }
        $self->close;
        return;
      }
}
1;

こうしておけば,test.pl の頭に追加モジュールをuseしておくだけで動作変更することができます.つまりこんな感じですね.

# Author: Sawyer X
# Email:  xsawyerx@cpan.org or xsawyerx@gmail.com

use strict;
use warnings;

use Android;
use Android::Plugin::Mine;
use Try::Tiny;

次は

test.plを動かすと

  • receiveEvent is deprecated

なんてメッセージがでてきます.次はこれを推奨のAPIに移植するのが目標です.

chroot環境を作る

perlも作れたし,前回のメモでforkを禁止するモジュールも作ったし,後やらないといけないのは/chroot環境を作ればllevalを移植できるはず.ということでchroot環境を作ってperlが動くところまで試してみました.

$ sudo mkdir /chroot
$ sudo mkdir /chroot/bin
$ sudo mkdir /chroot/lib64
$ sudo mkdir /chroot/usr
$ sudo mkdir /chroot/tmp
$ sudo mkdir /chroot/dev
$ sudo mkdir /chroot/etc
$ sudo mkdir /chroot/home
$ sudo mknod -m 666 /chroot/dev/null c 1 3
$ sudo mknod -m 666 /chroot/dev/zero c 1 5
$ sudo mknod -m 666 /chroot/dev/random c 1 8

と,これで必要最低限必要なディレクトリとファイルを作りました.まずはbashとlsが動くようにライブラリをコピーしましょう.ライブラリはlddで探すことができます.

$ ldd /bin/ls:
     linux-vdso.so.1 =>  (0x00007fff1698b000)
     libselinux.so.1 => /lib64/libselinux.so.1 (0x0000003fc4a00000)
     librt.so.1 => /lib64/librt.so.1 (0x0000003fc7200000)
     libcap.so.2 => /lib64/libcap.so.2 (0x0000003fca200000)
     libacl.so.1 => /lib64/libacl.so.1 (0x0000003fc7600000)
     libc.so.6 => /lib64/libc.so.6 (0x0000003fc4200000)
     libdl.so.2 => /lib64/libdl.so.2 (0x0000003fc4600000)
     /lib64/ld-linux-x86-64.so.2 (0x0000003fc3e00000)
     libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003fc4e00000)
     libattr.so.1 => /lib64/libattr.so.1 (0x0000003fc6e00000)
$ ldd /bin/bash
     linux-vdso.so.1 =>  (0x00007fffeecaf000)
     libtinfo.so.5 => /lib64/libtinfo.so.5 (0x0000003fc6600000)
     libdl.so.2 => /lib64/libdl.so.2 (0x0000003fc4600000)
     libc.so.6 => /lib64/libc.so.6 (0x0000003fc4200000)
     /lib64/ld-linux-x86-64.so.2 (0x0000003fc3e00000)

ここに出て来た/lib64配下のライブラリを/chroot/lib64にコピーします.んで,

sudo chroot --userspec=ec2-user /chroot

とすることでchroot環境に移ります.

$ sudo chroot --userspec=ec2-user /chroot
bash-4.1$ ls
bin  dev  etc  home  lib  lib64  tmp  usr
bash-4.1$ cd 
bash-4.1$ pwd
/home/ec2-user
bash-4.1$ exit
exit

ちなみに,色々入れた後に確認した結果を書いてるので,手順通りに作った場合とは表示が違うかもしれません.次にbashやlsと同じようにperlでもライブラリを調べて

$ ldd `which perl`
     linux-vdso.so.1 =>  (0x00007fff9b5ff000)
     libnsl.so.1 => /lib64/libnsl.so.1 (0x0000003fc8e00000)
     libdl.so.2 => /lib64/libdl.so.2 (0x0000003fc4600000)
     libm.so.6 => /lib64/libm.so.6 (0x0000003fc5600000)
     libcrypt.so.1 => /lib64/libcrypt.so.1 (0x0000003fc5a00000)
     libutil.so.1 => /lib64/libutil.so.1 (0x0000003fc9200000)
     libc.so.6 => /lib64/libc.so.6 (0x0000003fc4200000)
     /lib64/ld-linux-x86-64.so.2 (0x0000003fc3e00000)
     libfreebl3.so => /lib64/libfreebl3.so (0x0000003fc5e00000)

コマンドをいくつか実行してみて動かなかったらstraceを使って足りないライブラリを見つけては足して行くという作業を続けました.そして今の環境はこんな感じ.

$ tree --filelimit 18 /chroot
/chroot
├── bin
│     ├── bash
│     ├── curl
│     ├── ls
│     └── ping
├── dev
│     ├── null
│     ├── random
│     └── zero
├── etc
│     ├── group
│     ├── host.conf
│     ├── hosts
│     ├── localtime
│     ├── nsswitch.conf
│     ├── passwd
│     └── resolv.conf
├── home
│     └── ec2-user
│             └── perl5
...
├── lib64
│     ├── ld-linux-x86-64.so.2
│     ├── libacl.so.1
│     ├── libattr.so.1
│     ├── libcap.so.2
│     ├── libcrypt.so.1
│     ├── libc.so.6
│     ├── libdl.so.2
│     ├── libfreebl3.so
│     ├── libm.so.6
│     ├── libnsl.so.1
│     ├── libnss_dns.so.2
│     ├── libnss_files.so.2
│     ├── libpthread.so.0
│     ├── libresolv.so.2
│     ├── librt.so.1
│     ├── libselinux.so.1
│     ├── libtinfo.so.5
│     └── libutil.so.1
├── tmp
└── usr
    ├── bin
    │      ├── id
    │      └── strace
    ├── lib
    │      └── locale
    │               └── locale-archive
    └── lib64
        ├── libcurl.so.4
        └── libidn.so.11

29 directories, 44 files

この状況でchrootしてperlを動かすと,

$ sudo chroot --userspec=ec2-user /chroot
bash-4.1$ export PATH=/home/ec2-user/perl5/perlbrew/perls/perl-5.12.2/bin/:$PATH
bash-4.1$ perl -v

This is perl 5, version 12, subversion 2 (v5.12.2) built for x86_64-linux

Copyright 1987-2010, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

ってことで,llevalでの特徴となっているLWP::Simpleが動くかどうかを試してみると,

bash-4.1$ perl -MLWP::Simple -le 'getprint "http://www.perl.com"'
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="generator" content="Movable Type Pro 5.02" />
<link rel="stylesheet" href="http://www.perl.com/pub/styles.css" type="text/css" />
<link rel="start" href="http://www.perl.com/pub/" title="Home" />
...

となるので,成功です.このとき,/etc/passwdは

ec2-user:x:222:500:EC2 Default User:/home/ec2-user:/bin/bash

同様に/etc/groupは

ec2-user:x:500:

としています.
ちなみに,今気になっているのはidコマンドの結果です.普通は

$ id
uid=222(ec2-user) gid=500(ec2-user) groups=500(ec2-user),10(wheel)

となるはずですが,chroot環境では

bash-4.1$ id
uid=222(ec2-user) gid=0 groups=500(ec2-user),0,1,2,3,4,6,10

となっています.なぜに0とかがでてくるのかよくわかっていません.大丈夫なのかな?