2011/12/15

Ubuntu 11.10 autofs5/synax error in nsswitch の対処

autofs5 をインストールしたら次のエラーが出た。


  syntax error in nsswitch config near [ synax error ]


ここにバグとして挙がっていた。
/etc/nsswitch に次のエントリーを追加すると直った。

  automount: files



2011/10/17

LinuxでLoadable Moduleをデバッグする

Linux のloadable moduleをデバッグしたときのメモ。
元ネタはO'Reillyの"Linuxデバイスドライバ"。
static linkしたモジュールはgdbでvmlinuxを読み込めばシンボル情報が読み込まれるが、loadable moduleは一手間かけないとシンボルが見えない。

ここでは uvcvideo モジュールをデバッグ対象とする。
まず、ターゲットとなるモジュールの.text, .bss, .dataのアドレスを得る。

# cat /sys/module/uvcvideo/sections/.{text,bss,data}
0xf86c6000
0xf86d4560
0xf86d3000


続いてデバッガでシンボルを追加する。カレントディレクトリはLinuxカーネルソースのディレクトリ。

(gdb) add-symbol-file drivers/media/video/uvc/uvcvideo.ko 0xf86c6000 -s .bss 0xf86d4560 -s .data 0xf86d3000


これで関数名を指定してブレークポイントを置いたり、ソースを追いながらステップ実行できるようになる。
もう少し楽にやる方法はないかと思う。

なお、.ko にデバッグ情報を残すにはモジュールのソースディレクトリにある Makefile に、

EXTRA_CFLAGS += -O0 -g


などと記述しておけば良い。

ついでにモジュールのみのビルド方法は、カーネルソースのトップディレクトリにおいて、"make M=[モジュールのディレクトリ]"。

2011/10/15

カーネルをデバッグするためのGRUB2設定

Ubuntu 11.04でカーネルデバッグをしたときの設定です。
VMWare同士でシリアルポートを接続する方法で、baudrate 115200で接続。

* デバッグ対象のOS

- /boot/grub/grub.cfg のデバッグしたいカーネルの menuentry をコピーして、新しい menuentry を追加する。
- 追加した menuentry の "linux" のパラメータに "kgdboc=ttyS0,115200 kgdbwait" を追加する。以下は一例。


menuentry 'Ubuntu, with Linux 2.6.38.8 (debug)' --class ubuntu --class gnu-linux --class gnu --class os {
    recordfail
    set gfxpayload=$linux_gfx_mode
    insmod part_msdos
    insmod ext2
    set root='(hd0,msdos1)'
    search --no-floppy --fs-uuid --set=root ba000129-5c3a-403c-9107-3d7649a4d051
    linux    /boot/vmlinuz-2.6.38.8 root=UUID=ba000129-5c3a-403c-9107-3d7649a4d051 ro kgdboc=ttyS0,115200 kgdbwait
    initrd    /boot/initrd.img-2.6.38.8
}



- 再起動時にSHIFTキーを押し続けてブートメニューを表示し、追加した menuentry に対応する項目を選ぶ。

* デバッガを起動するOS

GRUBとは関係ないが、ついでにデバッガ側の作業も記述しておく。
カーネルをビルドしたディレクトリに移動して、gdbを起動。以下のコマンドを入力。.gdbinit に記述しておいても良い。


(gdb) file ./vmlinux
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS0



# GRUB2とはバージョン1.9以降を指すそうだ。ややこしい...。

2011/09/15

VMWare / Android x86 にadbで接続する

よく忘れるのでメモ。

* ゲストOS (Android)側

  - IPアドレスを調べる。
    # netcfg
    (ifconfigではない)

* ホストOS側

  - Androidデバイスに接続する。
    # adb connect [ゲストOSのIPアドレス]

  - 接続したデバイスを確認する。
    # adb devices

デフォルトのポート番号は 5555。
ポート番号を例えば 5678 に変更したいときは、次のようにする。

* ゲストOS側

  # setprop service.adb.tcp.port 5678
  # kill `pgrep adbd`

* ホストOS側

  # adb connect [ゲストOSのIPアドレス]:5678

2011/08/25

HTC Desireでb-mobile talking Fairを使う

現在 HTC Desire (X06HT, CyanogenMod 6.1.1) に b-mobile talking Fair のSIMを挿して使い始めた。

元々 b-mobile Fair を契約していたが、以下の理由により移行した。
  • 携帯電話とスマフォを2台持ち歩くのが面倒だったので、1つにまとめたかった。
  • 普段電話はそれほど使わないので、電話の基本料金も安く抑えたかった。
  • b-mobile Fair 販売開始直後に契約して3ヶ月半、そろそろ更新の時期だった。b-mobile も更新のタイミングで talking Fair の製品発表をしたのだろう。
携帯は11年以上使っていたauの最安プランSS。au の解約料金と b-mobile talking Fair の初期費用を含めて計算したところ、私の場合は13ヶ月目以降に2台持ちよりも支払いが少なくなるようだ。ちょっと長いが1台になる利便性を考えると移行して良かったと思う。

# au の"誰でも割"解約料は11年目以降に安くなる。11年に達していなければ、移行費用を回収できる期間がさらに長くなるので、契約更新月まで待ったほうがトータルで安くなりそう。
# ちなみに b-mobile は契約期間1年未満で解約料10,500円が発生。

留守電等のオプションサービスは契約していない。
Android Marketから "圏外着信お知らせツール" をインストールして試したら、問題なく動作した。これは便利。

ところでちょっとしたことだが、b-mobile Fair から talking Fair の SIM に変更したら、電波強度のところに HSDPA の "H" が表示されるようになった。Fair でも通信速度を見ると HSDPA は有効になっていたので表示が変わったというだけ。

2011/06/02

Androidでb-mobile Fair 30日間の通信量

5/1-5/31の使用量: 81MB

外出した日の使用量は2MB〜8MBの範囲。
主な用途はメール・ブラウザ・RSSリーダー。
たまにアプリをダウンロード。
5月は休日に外出することが少なかったので、平均的な月より比較的通信量が低めだったと思われる。
この調子で行くと半分以上余る。

2011/05/21

Bloggerでエクスポート/インポートするとURLが変わる

ちょっとブログの記事をまとめて変更したいことがあったので、エクスポート->記事を全て削除->エクスポートしたXMLを編集->インポート をしたら記事のURLが変更されてしまった....。
記事の中の記事へのリンクも手動で変更するハメに。
しばらくGoogleのインデックスも更新されないし、やらなきゃ良かった。

2011/05/17

Android DNSの設定を変更する

bionic libcのソースコードとドキュメントを見て以下のことがわかった。

net.dns[n].[pid] => net.dns[n] => /etc/resolv.conf
の順番でDNSを見る。

[n] : 1-8
[pid] : プロセスID


* プロセス毎の設定

system propertyのnet.dns[n].[pid]で、プロセス毎にDNSの設定ができるようになっている。
この設定が対象のプロセスにおいて見つかった場合、nが一致するnet.dns[n] は使われない。
Androidの開発においてはよほど特殊なことをしない限り、プロセス毎の設定を利用することはないだろう。


* グローバルな設定

1. net.dns[n]にDNSのIPアドレスを設定する。
2. net.dnschange の値を現在値とは別の整数値に設定する。通常この値はインクリメントされている。

net.dnschange (ここではカウンターと呼ぶことにする)はnet.dns#が変更されたことを検知するための整数値。
前回DNS Queryを発行しようとしたときと異なる値のとき、net.dns#が再読み込みされる。
libcが覚えるカウンター値はstatic変数なので、プロセスごとにカウンターによるDNS更新の判断を行っている。
javaでの実装方法は、ConnectivityServiceが参考になる。


* 補足

WiFiや3GをONにすると、net.[interface name].dns[n] が変更されて、その値の1つが net.dns[n] に設定される。

/etc/resolv.conf も見る実装がソースには残っていたが、#ifdef ANDROID_CHANGES によって除外されていた。

以下、覚書として"dns"を含むsystem propertyを挙げる。


$ adb shell getprop | grep dns
[net.change]: [net.dnschange]
[net.dns1]: [192.168.3.254]
[net.rmnet0.dns1]: [202.32.159.24]
[net.rmnet0.dns2]: [210.128.58.10]
[net.dns2]: []
[net.dnschange]: [118]
[dhcp.eth0.dns1]: [192.168.3.254]
[dhcp.eth0.dns2]: []
[dhcp.eth0.dns3]: []
[dhcp.eth0.dns4]: []
[net.eth0.dns1]: [192.168.3.254]
[net.eth0.dns2]: []
[net.dns3]: []

2011/05/16

Androidでb-mobile Fair 15日間の通信量

5/1-5/15の使用量: 28MB

連休中に出かけることが少なかったとはいえ、少なすぎる...。
目標値の3分の1以下。日中に節約しすぎた。
仕事中にメールは見ないので、休憩時間以外は3Gを切っていた。

主な用途はメール・ブラウザ・RSSリーダー。
RSSリーダーでのフィードの取得はWiFiで行っていた。
なので、通勤中のメールとブラウザの使用が主な内訳。

しかしまあ、あと105日使えるのだから、気分は良い。
4ヶ月/500MB/¥5000 という少しだけ割高なライトプランを出しても売れるのではなかろうか。

2011/05/14

AndroidのProxy設定のないアプリにProxyを越えさせる(HTTP+少しHTTPS)

DeleGateとwebnamedを使い、Proxy設定のないアプリでファイアウォールを越えてHTTP/HTTPS通信することに成功した。

こちらのブログを参考にさせてもらった。
DeleGateとwebnamedのネットワーク構成は"AndroidのProxy設定のないアプリにProxyを越えさせる(HTTPのみ)"に書いた図の、squidがDeleGateに置き換わったもの。

文末にDeleGateのinit scriptを示す。
参考元のスクリプトと主に違うところは、

- chkconfigに対応している
- DeleGateが参照するDNSサーバをwebnamedノードにしている
(192.168.3.10 がwebnamed、192.168.3.20:80 がProxy)

さて、題名に"少しHTTPS"とある理由だが、DeleGateのSSL透過プロキシ機能はman-in-the-middleな仕組みなので、サーバ証明書の検証をスキップする機能があるアプリでないと、HTTPSを使えない。

Dolphin Browserを試したところ、HTTPSでアクセスすると"アクセスしようとしているサーバ名が証明書と違うがどうするか?"という意味の警告が表示され、そのままページを開くかどうか選択できる。Gmailを開くことができた。
NewsRobはGoogle Readerから同期する際にExceptionが発生して、同期することができなかった。後日他のRSS Readerを試してみようと思う。

/etc/init.d/delegate:

#!/bin/sh
# chkconfig: 345 98 20
# description: delegate server
# processname: delegated

DAEMON=/usr/local/bin/delegated
DGROOT=/var/spool/delegate
DELEGATE_PORT=8080
PROXY_HOST=192.168.3.20
PROXY_PORT=80
DNS_SERV=192.168.3.10

test -x $DAEMON || exit 0

start_proc() {
$DAEMON DGROOT=$DGROOT -P$DELEGATE_PORT \
PROXY="$PROXY_HOST:$PROXY_PORT:*" \
PERMIT="http,https:*:*" \
REMITTABLE=http,https \
STLS=mitm \
RELAY=vhost RES_NS="$DNS_SERV,END"
}

stop_proc() {
$DAEMON DGROOT=$DGROOT -P$SRC_PORT -Fkill
}

case "$1" in
start)
start_proc
;;
stop)
stop_proc
;;
restart)
stop_proc
if [ "$?" != "0" ]; then
exit 1
fi
start_proc
;;
*)
echo "Usage: $0 start|stop|restart"
exit 1
;;
esac

2011/05/12

rgrepでソースコードを検索する

私はC/Javaのソースコードを検索するときvim/eclipse/ctags/globalを使うが、ソースコードを解析するときに比較的rgrepを使うことが多い。

git/svnレポジトリから取得したソースコードを検索するときのために、以下のaliasを定義している。


alias rgrep='rgrep --exclude="*\.o" --exclude="*\.class" --exclude="*\.dex" --exclude="*\.swp" --exclude="tags" --exclude="GTAGS" --exclude="GSYMS" --exclude="GRTAGS" --exclude="GPATH" --exclude-dir="*\.git" --exclude-dir="*\.svn"'

2011/05/10

HTC Desire - CyanogenMod 6.1.1のバッテリー消費量(WiFi)

以下の状態において、8時間で100%=>%94 だった。
意外と減り幅が少ない印象。

- Wifi ON, 3G OFF
- K9-Mail : IMAP Idle 2アカウント, メール1件取得して6時間LED点滅
- NewsRob : 1時間毎の同期、100件くらい自動取得
- なまず速報β (速報なし)


#追記 5/11

以下の使い方で、100%=>70%

- 7.5時間WiFi、NewsRobで220件くらい自動取得、ほとんどスリープ状態
- 3時間3G、1.5時間ほどNewsRobやブラウザを使う。
- 通信量: WiFi 3.9MB、3G 4.0MB

2011/05/09

AndroidのProxy設定のないアプリにProxyを越えさせる(HTTPのみ)

ここで紹介する方法は動作はするが役立つ場面が少ない。

squid透過プロキシとProxy越えをするDNS Serverを使って、プロキシ設定のできないアプリにファイアウォールを越えさせることに成功した。

ただし、squid透過プロキシの仕組み上、HTTPSは通すことができない。squid 3.1のssl-bumpというman-in-the-middleな機能を使うとできそうだが、制約が大きそうなのでやらない。
HTTPSを通せないので、大抵HTTPSが必要なアカウント認証するWebアプリが使えないのが痛い。

これだけやってもHTTPのみ...。
はっきり言って、プロキシ設定ができるOpera Mobileだけの方が役に立つ。

以下、検証した構成。
Androidからsquidへは、iptables で80 portをsquidのホストの3128 portへリダイレクトした。

#追記 5/14
その後、squidの代わりにDeleGateを使ってHTTPSを通すことに成功した。


                       +-----------------------+
                       |  android smart phone  |
                       +-----------------------+
                          |                |
                          | DNS query      | http
                          v                v 
                    +----------+     +-------------+
                    | webnamed |     |   squid     |
                    +----------+     |(transparent)|
                          |  |       +-------------+
                DNS query |  |  http        |
                          v  +---------+    | http
                    +------------+     |    |
                    | DNS Server |     |    |
                    +------------+     v    v 
                                     +-------------+
                                     | http proxy  |
                                     |  firewall   |
                                     +-------------+
                    +--------------+   |    |
                    | resolver.cgi |<--+    | http
                    +--------------+        v
                           |         |-------------+
                 DNS query |         |   web site  |
                           v         +-------------+
                    +------------+
                    | DNS Server |
                    +------------+

2011/05/08

Androidの非公開APIを呼ぶ

リフレクションを使うと、Android SDKには公開されていないAPIを呼び出すことができる。
公開されていないAPIなので、バージョンごとの互換性が失われる可能性が高いことに注意。

以下はUSBテザリングをONにする処理。


ConnectivityManager cm = (ConnectivityManager) getApplicationContext().getSystemService(CONNECTIVITY_SERVICE);

Class c = Class.forName(cm.getClass().getName());
Field f = c.getDeclaredField("mService");
f.setAccessible(true);
Object iconn = f.get(cm);

// Method: int tether(String iface)
Method method_tether = iconn.getClass().getMethod("tether", String.class);
Object ret = method_tether.invoke(iconn, "usb0");

// Returns ConnectivityManager.TETHER_ERROR_*
int e = ((Integer) ret).intValue();

2011/05/07

Android K-9 Mail 待機時の通信量を節約する(2)

K-9 Mail通信量計測の詳細。

* 条件

- b-mobile Fair
- K-9 Mail v3.706
- Gmailの1アカウント、SSL、受信トレイが空の状態。


* 計測結果

計測値はIMAP同期が5回の同期での平均値、IMAP IDLEが1時間の間に発生した通信の平均値。
()内は128 Bytes換算のパケット数

[IMAP同期の同期毎の平均値]
- IPパケット数 = 12 (12)
- IPパケットサイズ合計 = 2.01 KBytes

[IMAP IDLEの平均値 (リフレッシュ間隔24分)]
- IPパケット数 = 5分毎に12 (12), 初回接続時に47 (62)
- IPパケットサイズ合計 = 5分毎に1.54 KBytes, 初回接続時に8.47 KBytes


* どちらが節約できるか

5分毎のIMAP同期とIMAP IDLEは、パケット数においてほぼ同等と見なせる。
したがって、メールが通知されるまで5分以上かかっても良い人はIMAP同期の方が節約できて、それほど待てない人はIMAP IDLEにするしかない。IDLE接続のリフレッシュ時間はデフォルトの24分が良い。


* パケット数の算出について

実測したパケット数はIPパケット数なので、モバイルデータ通信時のパケット数はもっと多くなるかもしれない。
節約するならばデータサイズだけでなく、パケット数の削減を考えなければならない。
実際は単純には計算できないと思うが、b-mobile Fairのサイトでは便宜上1パケット=128 Bytesで計算する方法を紹介している。
私はモバイルデータ通信のプロトコルについて詳しくないので、この方法にあやかってみる。

b-mobile Fairでは4ヶ月で1GBytesまでという契約だが、通信量とパケット数は関係する。
b-mobileのサポートに問い合わせて見たら、純粋な送受信したいデータの他にヘッダ、デリミタといったオーバヘッドも通信量として計算されるので、100バイトを1回送信するよりも1バイトを100回送信する方が通信量が多いということだ。


* IMAP IDLE接続のタイムアウト

IMAP IDLEの5分毎というのはサーバから送られてくるKeepAliveのパケットだと思われる。
IMAP IDLE接続のリフレッシュ間隔とは関係がない。
上記のパケット以外に、約25分間隔でFINパケットが来た。FINパケットの後、K-9 Mailは同一のIPアドレスへ再接続した。
そして特筆すべきは、たまにRSTパケットがサーバから来た。RSTパケットが来る条件は不明。
RSTは接続を強制終了する要求であり、K-9 Mailが再接続を試みたようだが、さらにRSTが返された。
RSTに続く通信は9.27 KBytes、54(69)パケットのやりとりが発生した後に別のサーバへ接続。
これらの現象はIMAP IDLE接続のタイムアウトと関係しているようだが、明確な仕様がわからなかった。

FIN、RSTはIDLE接続のリフレッシュ時間を24分(デフォルト)に設定しておくと返されない。
そのかわり、リフレッシュ間隔毎に再接続のためのやりとりが発生する。
デフォルトが24分なのはFINが返ってくる25分という時間と関係があるのかもしれない。

Android K-9 Mail 待機時の通信量を節約する(1)

私はb-mobile Fairの回線を使用し、メーラーはK-9 Mailを使っている。
待機時のメーラーの通信量が気になったので、パケット代節約の方法を探るために計測した。

ここでの計測対象はGmailに限った話しで、他のメールサーバだと結果が異なるかもしれない。

結論を言うと、
  • プッシュ接続(IMAP IDLE)より、5分より大きい間隔での定期的な同期(IMAP同期)の方が、パケット数的にはお得。
  • 節約したいならばIMAP同期で同期時間を10分以上に設定。
  • 節約したいけどプッシュ通知は必須というときは、IMAP IDLEのリフレッシュ時間を24分(デフォルト)に設定。
  • いずれにせよ、1ヶ月で5~6万パケットの大した数ではないので、倹約家以外はあまり気にする必要はなさそう。

である。

詳細は Android K-9 Mail 待機時の通信量を節約する(2) に。


補足としては、どちらの方法でも取得するフォルダの数は少ない方が通信量を節約できる。
K-9 Mailではプッシュフォルダの数だけ通信の接続数が増えるようだ。

私はK-9 Mailでプッシュフォルダを"1stクラスフォルダのみ"として、受信トレイのみを1stクラスに設定している。
基本的に受信トレイからの振り分けはPCからまとめてやっている。

逆USBテザリングでメール待ち受けができるCyanogenModパッチ(2)

環境: Ubuntu 10.10 Desktop, HTC Desire, CyanogenMod 6.1.1

前回のパッチの欠点を改善したものを作った。一応patchと、jarを固めたzipをここに置いてある。

このパッチにできることは、次のとおり。

- 一度だけAndroidとPCに設定をしておけば、USBケーブルを刺したときに通信できるようになる。
- 逆USBテザリングと通常の正方向のUSBテザリングを切り替えられる。

これで晴れて快適な逆USBテザリング生活?ができるようになった。

必要な設定は
- PC
    USB RNDIS (Ubuntuだとusb0)のPC側IPアドレスを固定アドレスにする。
- Android
    システムプロパティを設定する。
    - usb.reverse.dns1 ... DNSアドレス
    - usb.reverse.gw ... PC側のusb0のアドレス
    これらのプロパティのいずれかが空のとき、通常のUSBテザリングになる。

RNDISドライバさえあれば他のOSでも動くはずだが、試していない。


以下、設定方法。

ここに記述してあることは当然上記パッチをAndroidに焼かないと動作しないので、あしからず。

AndroidをUSBで接続したときに現れるusb0インターフェースのIPアドレスを固定する。

Ubuntu Desktopに標準インストールされるNetwork Managerとethtoolではこれができなかった。なぜなら、usb0のMACアドレスは接続するたびに変化する。Network Managerが前回接続したことのあるインターフェースであると認識せず、usb0に施したはずの設定が反映されない。

ということで、Network Managerは消して、/etc/network/interfaces での運用に変更する。
# Network Managerと共存する方法があるかもしれないが、私は上手く行っていない。


# apt-get remove network-manager



これは私の環境のinterfaces。usb0 以外の設定は、環境に合わせて変更する必要がある。
usb0 のPC側アドレスは 192.168.42.100 とする。192.168.42.0/24 のうち、192.168.42.129 以外なら他のアドレスでも良い。

PC: /etc/network/interfaces

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp

auto usb0
iface usb0 inet static
    address 192.168.42.100
    netmask 255.255.255.0 



Androidで次のファイルを作成する。system partitionなのでadb remountや、recovery modeで変更する。

android: /data/local.prop:

net.dns1=
usb.reverse.dns1=
usb.reverse.gw=



Androidを再起動する。
PCからadbでシステムプロパティの値を設定する。
192.168.3.254 はDNSのアドレスなので、環境に合わせて変更する。


adb shell setprop usb.reverse.dns1 192.168.3.254
adb shell setprop usb.reverse.gw 192.168.42.100



以上で設定完了。

この状態でAndroidをUSBケーブルでPCに接続し、USBテザリングを有効にしてWebブラウザ等が使用できるかを確認する。
なお、Android Marketでダウンロードできないときは、Google Talkに一度ログインするとダウンロードが開始するかもしれない。

通常の正方向のUSBテザリングに戻したいときは、usb.reverse.dns1かusb.reverse.gwを空にすれば良い。

adb shell setprop usb.reverse.gw ""



このパッチでも不便な点がある。
それは複数の異なるLAN上のPCに接続するとき、DNSとゲートウェイの設定を変更しなければならないことだ。

2011/05/06

git pullとgit pull --rebaseの違い

git pull          ... git fetch, git merge [origin/master]
git pull --rebase ... git fetch, git rebase [origin/master]

古いgitだと git pull に --rebase オプションがない。


それでは merge と rebase の違いは?というと、他に詳しく説明しているページをご参考に。

はしょって言うと、次のとおり。

個人のブランチに他のコミットを取り込む ... rebase
公開されたコミットを統合する ... merge (rebaseしてはいけない)

ADWLauncherで地味にうれしいこと

アイコンをドラッグしてゴミ箱に入れるとき、1秒くらい待つと"アンインストールしますか?"と聞いてくる。
アプリケーションの管理を開かなくてもアンインストールできる。
アプリの入れ替えをちょくちょくやるのでうれしい。

2011/05/05

EclipseでF11を押すとAndroidManifest.xml.outが生成されるときの対処

EclipseでF11を押すと、xmlファイル名の後ろに.outが付いたファイル(AndroidManifest.xml.outやlayout.xml.out)が生成されてプロジェクトが実行されないときの対処。

発生条件
- EclipseにADT PluginとWTP Pluginをインストールしている
- Androidプロジェクトで作業している
- xmlファイルを開いている
- (Ctrl+)F11を押す

これはEclipse WTPの機能で、xmlファイルに対して Run As > XSL Transformation を実行したことになるため。

以下の設定を変更すればOK。

Window > Preferences > Run/Debug > Launching を開き、
"Always launch the previously launched application' in the 'Launch Operation' section."
のラジオボタンを選択する。

こちらがネタ元

git tag のコミット日時を一覧表示する

git tagのタグ一覧をコミット日時と共に見たいと思うことがある。
gitだけで見る方法がわからなかったので、スクリプトを書いた。

こんな感じ。

$ git-tagdate
2011-02-08 11:00:00 -0800 tag1
2011-02-09 12:00:00 -0800 tag2
2011-02-10 13:00:00 -0800 tag3


日付順にソートしたいときは git-tagdate | sort で。

git-tagdate:

#!/usr/bin/perl

open(TAG, "git tag|") || die "$!";
while ($t = <TAG>) {
    chomp $t;
    open(LOG, "git log --format=\%ci $t^..$t|") || next;
    $d = <LOG>;
    close LOG;
    chomp $d;
    print "$d  $t\n";
}
close TAG;

逆USBテザリングの通信速度/メリット/デメリット

先に結論を言うと、WiFiと比較したときの逆USBテザリングのメリットはあまりない。

逆USBテザリングの通信速度を計測した。
接続したPCの回線はYahooBB ADSL 8M。
ping値は私のレンタルサーバ(埼玉->東京)に対して行った。

* HTC Desire

SpeedTest.Net
Download 5.19 Mbps, Upload 0.77 Mbps
ping 28ms

* PC - Ubuntu 10.10 Desktop x86_64

速度.jpスピードテスト
Download 5.13 Mbps, Upload 0.80 Mbps
ping 26ms


ということで、この環境だとほぼUSBホストOSと同等の速度が出ることがわかった。

逆USBテザリングのメリットは、

- 充電速度がWiFi接続中よりも速い
- WiFiステーションがなくても接続できる

だと思う。
WiFi接続しながら充電していると充電完了までの時間が長くなる。逆USBテザリングの場合は通信のための消費電力がかなり小さいものと思われるため、WiFi OFF時の充電速度とほぼ変わらない。
WiFiステーションはいまどきどこでもありそうなので、あまりアドバンテージにはならないかもしれないが、セキュリティ上の理由でWiFiステーションを使いたくないときに、モバイルデータ通信の代替にはなる。

ちなみに私が逆USBテザリングを選択する理由は"趣味"だ。


反対にデメリットは多い。

- PCが必要
- PCでの設定が必要 (Linuxではできたが、Windowsは知らない)
- USBケーブルが必要
- rootedが必要
- USBテザリングをサポートしているAndroidが必要
- メール待ち受けやAndroid Marketを使用するにはAndroidのベースモジュールの変更が必要
- USBでの接続時間が長いとバッテリーの劣化が進むかも

Linux、rooted、テザリングをサポートしているROMが必要という時点で、逆USBテザリングは相当しきいが高い。
VMWare等でLinuxを動かせばWindowsでもできないことはないが...

テザリングは、最近ではドコモのOptimus PADやKDDIのHTC Evoが標準でサポートしている。今後標準サポートする機種は増えてくるのではないかと思う。

逆USBテザリングでメール待ち受けができるCyanogenModパッチ(1)

Androidの逆USBテザリング時に通信しないアプリが存在する理由で述べた問題を解決すべく、Androidのベースモジュールに手を入れた。

やったことは、アプリに対してUSBテザリングのON/OFFをWiFiの接続状態に見せかけるようにした。
ConnectivityManager を通じてネットワークの状態を確認しているアプリは、USBテザリングがONになったときWiFiが接続したものと認識するようになる。
また、USBテザリングがON(OFF)になったときにWiFiが接続(切断)したという通知がアプリに伝わるようになる。

すなわち、メーラーの自動受信やAndroid MarketのOATのような待機系の機能が逆USBテザリング時に使えるようになる。


試したアプリの動作は次のとおり。

* K-9 Mail

IMAP IDLE接続でUSBテザリングON/NAT設定すると、"同期停止"の表示がなくなり、メールを自動的に受信した。
USBテザリングをOFFにすると、再び"同期停止"の表示になった。

* Android Market

アプリからのダウンロード、PCサイトからのOTAインストール(USBにつながっているからOn The Airじゃないけど)はできた。
しかしたまにダウンロードが開始しないことがある。そのようなときはダウンロード履歴を何回か開いてみると開始する。Google Talkにログインするとダウンロードが開始した。通常のWiFi接続のときも同様の現象を見たことがあるので、これが今回の変更による問題なのかどうかはわからない。

* NewsRob

WiFi Onlyの定期的な同期を設定。USBテザリングON/NAT設定すると、設定された同期間隔でフィードを取得した。

* なまず速報

USBテザリングON/NAT設定した後すぐに接続した。
USBテザリングをOFFにするとしばらく通知領域にアイコンが残り、"診断情報"のサーバ接続が"はい"のままだった。
これはアプリの性質上、できる限り切断時間を短くするよう、繰り返しリトライを行っているものと推測される。


変更内容は以下のとおり。ものすごくwork aroundなやり方。

* android.net.ConnectivityManager
  • getActiveNetworkInfo() ... テザリングON時に"WiFiで接続した"という情報を返す。
  • getAllNetworkInfo() ... WiFiのNetworkInfoについて、テザリングが有効であれば"WiFiで接続した"という情報に置き換える。
  • getNetworkInfo(type) ... getAllNetworkInfo()と同様。

* com.android.server.connectivity.Tethering
  • TetheredState.enter() ... USBテザリングがONになった直後、"WiFiで接続した"というIntentをbroadcastする。
  • TetheredState.exit() ... USBテザリングがOFFになった直後、"WiFiが切断した"というIntentをbroadcastする。


ビルドした/system/framework配下のファイルとソースコードのパッチを一応ここにおいた。
zipはHTC Desire、CyanogenMod 6.1.1限定なので需要ないと思うが。

最後に問題点を挙げる。

- WiFiとテザリングの同時使用は考えていないので、USBテザリング中にWiFiをONにするとおかしな挙動をするかもしれない。

- 本来の正方向のUSBテザリングを3G回線でするとき、アプリがWiFiだと認識してWiFi向けの機能を実行してしまう。アクティブな接続の種類をアプリに返すとき、3Gより優先してなりすましWiFiの情報を返してしまうため。

- USBテザリングをONにした後、それほど間を空けずホストOS側でrusbtether-natを実行しないと、アプリが通信できないものと判断してしまうかもしれない。

- jarを焼いて直後のOS起動後、USBテザリングをOFFにするとUSBデバッグができない状態となり、USBテザリングをON/OFF繰り替えしても復帰できなくなってしまった。再起動したら直った。その後この現象は発生していない。

2011/05/04

CyanogenMod 6 froyo-stable をソースコードからビルドする

環境: Ubuntu 10.10 Desktop x86_64

CyanogenMod 6.1.1の/system/framework/framework.jar, services.jar を入れ替えるために、ソースコードからビルドした。
/syste/framework/配下のファイルの入れ替え以外は試していない。

CyanogenMod Wikiにプラットフォームごとのビルド方法が説明されている。有難い。
UbuntuとOS Xでの方法が書いてあるが、両方見た方がいいかも。


$ repo init -u repo init -u git://github.com/CyanogenMod/android.git -b froyo-stable
$ repo sync


を実行すると


error: revision master in CyanogenMod/android_device_advent_vega not found


というエラーが発生した。
.repo/manifest.xml からandroid_device_advent_vegaをコメントアウトして、再び repo sync。
これが完了したらビルドの設定。

WikiでのUbuntu上でのビルド方法には brunch bravo を実行するように書いてあったものの、このバージョンのenvsetup.shにはbrunchがないようだ。
OS X上でのビルド方法にはlaunchを実行するように書いてある。

続いてmake。frameworks/base と frameworks/base/services/java だけ部分的にmakeしたいが、モジュール間の依存関係を調べるのも面倒なのでフルビルド。

次のエラーが発生した。


(unknown): error 17: Field org.apache.http.protocol.HTTP.EXPECT_CONTINUE has changed value from "100-Continue" to "100-continue"


このスレッドを見ると、CyanogenMode 6.1.1リリースのすぐ後に修正された問題のようだ。


external/apache-http/src/org/apache/http/protocol/HTTP.java:    public static final String EXPECT_CONTINUE = "100-continue";


の 100-continue を 100-Continue に変更。

続いて次のエラーが発生した。


make: *** No rule to make target `vendor/cyanogen/proprietary/RomManager.apk', needed by `out/target/product/bravo/system/app/RomManager.apk'.  Stop.


WikiにROM Managerをダウンロードするように書いてあったが、やらなかったので出た。
ビルド中にcurlで外から取得するようになっていて、取得先のURLが見つかっていない。
CyanogenModの.zipから取り出して配置して、再びmake。

以上でmakeが完了した。
フルビルドした後はビルドしたいモジュールのディレクトリに移動して、mmコマンドを実行。

実はCyanogenModのビルドに先立って、手元にandroid git repositoryから取得したソースがあったので、それを使おうとした。
CyanogenModのframework.jarのclasses.dexをsmaliで逆コンパイルし、一部のクラスだけ置き換えたのだが、問題があった。
gitのタグandroid-2.2.1_r1とCyanogenMod 6.1.1では、android.internal.RのリソースIDが変わっていて、適切なリソースを取得できなかった。

Android Platformでの一部のプロジェクトのみmakeする

例えば frameworks/base をmakeしたいとき。
mmmをAndroid.mk のあるディレクトリを渡して実行する。

$ . build/envsetup.sh
$ mmm frameworks/base


mmmは再帰的にディレクトリを見るわけではないので、
frameworks/base 配下のプロジェクトに対しても個別にmmmを実行する。

Ubuntu 10.10 x86_64 でAndroidをビルドする

環境: Ubuntu 10.10 Desktop x86_64

libstdc++.so がないと言われる

embeddedさんのブログを参考にして、

$ sudo apt-get install gcc-multilib g++-multilib ia32-libs lib32z1-dev lib32ncurses5-dev

を実行したらビルドできた。
たぶん
g++-4.4-multilib: /usr/lib/gcc/x86_64-linux-gnu/4.4/32/libstdc++.so
が必要だったのだと思う。

2011/05/03

Androidの逆USBテザリング時に通信しないアプリが存在する理由

正確にはWiFi/3GがOFF、USBテザリングONのときに通信しないアプリが存在する理由。

私が使用している K-9 Mail、NewsRob、ついっぷるは逆USBテザリング時に自動同期しない。Android Marketでアプリをダウンロードできない。
その理由について考えた。

Android SDKのConnectivityManagerから、ネットワーク接続の状態を取得できる。
ConnectivityManagerが認識する接続の種類を挙げる。

TYPE_MOBILE
TYPE_WIFI
TYPE_WIMAX
TYPE_BLUETOOTH (非公開, 2.3.3から)
TYPE_DUMMY (非公開,2.3.3から)
TYPE_ETHERNET (非公開,2.3.3から)
(Sub Type) TYPE_MOBILE_DUN
(Sub Type) TYPE_MOBILE_HIPRI
(Sub Type) TYPE_MOBILE_MMS
(Sub Type) TYPE_MOBILE_SUPL

この中にUSBテザリングはない。
AndroidのUSBテザリングはUSB => WiFi/3G の方向での通信だけを想定していると思われるため、テザリング中であるか否かはAndroid端末内からの通信ができるかどうかと関係ない、ということなのだろう。

2.3.3で追加されているTYPE_BLUETOOTH, TYPE_DUMMY, TYPE_ETHERNETは@hideが記述されていて、Android SDKには非公開となっている。コミットログには

  Add some network types that OEM's are asking for.
  Adding them hidden so that if OEM's are rolling their
  own at least they can use the same values.
  Will mark them unhidden in a future sdk release.

と記述されていたので、企業からリクエストがあって追加した将来の互換性を保つための値で、わからないがTYPE_ETHERNETはEthernetインターフェースを装備したデバイスに使うのだと思う。Androidが動くNet Bookとか?

定期的にデータを取得するようなアプリは、ConnectivityManagerから接続状態を見て、未接続状態のときはデータを取得しない処理が実装されていることが多いと思われる。それが、アプリのお作法として適切だ。
したがって、WiFi/Mobile Data Connection/WiMAXが全て未接続であれば、USBテザリングのON/OFFには関係なく未接続状態だと判断されるだろう。
オープンソースであるK-9 Mailのソースを確認したらメールを同期する条件として、少なくとも全ての接続タイプのうち1つでもState.CONNECTEDであることが含まれていた。

なお、ConnectivityManagerでチェックせずに通信を開始するような機能は使用できる。
ついっぷるでは、「ネットワークに接続されていません」とメッセージは出ていても、手動で更新ができる。


#追記 5/5
ConnectivityManagerで接続状態を確認していることだけが理由ではなかった。
K-9 Mailはメール送受信用のサービスが常時動作しているが、常に走っているわけではなくネットワークが切断状態のときはサスペンドするようになっている。再び起動するのは ConnectivityManager.CONNECTIVITY_ACTION の Intent が通知されたときだ。
電力消費を抑えるための工夫だろうと思う。実験したところ、Android Marketについても同様のことが言える。

逆USBテザリングでメール待ち受けができるCyanogenModパッチを作って検証したら上手く動いた。

2011/05/02

HTC Desire Softbankを解約してb-mobile Fairへ変更した

今日b-mobile Fairを開通したので、HTC DesireのSoftbank契約を解除した。
契約日は1年前のHTC Desire発売日。今の時点で支払い残高は37,500円だそうだ。

早速通信速度を計測した。
SpeedTest というアプリがあり、速度計測アプリの中では結構気に入っているのだが、10MBくらい通信量を使う。
もったいないから busybox の wget を使った。

こんな感じで。
$ time wget -O /dev/null http://domain/url

久喜市の自宅から東京のどこかにあるらしい私のレンタルサーバへアクセス。
1MiBのファイルをダウンロードしたら、2.2Mbpsくらい出た。ping は 120-140ms くらい。
満足できる速度だ。アップロードは試していない。

my b-mobileで残データ量を確認したら、だいたい使った分だけ減っていた。
b-mobileのサイトでFAQを確認したら、「表示はリアルタイムではなく数時間ごとに更新される」そうだ。表示の最小単位は1MB。
残量表示はそれでいいが、使う側としては「数時間ごと」の使用量を1Byte単位で表示してほしいものだ。

ところで、b-mobileのSIMを刺すとアンテナの表示が x になると聞いていたが、CyanogenModのおかげか、RADIOを最新にしているおかげなのかはわからないが、問題なく表示されている。

Android Reverse USB Tethering with NAT

Environment: Android:CyanogenMod 6.1.1, PC:Ubuntu 10.10

It might be inconvenient when using reverse usb tethering with bridged network.

I must be execute the script that deletes a bridge interface when I stop tethering.
Without doing that, the network transfer in PC stops.

So I tried tethering with NAT that doesn't stop the network transfer in PC.
I came to be able to disconnect USB cable anytime without iconvenient effect in PC.

Firstly, enable IP forwarding in my PC.

/etc/sysctl.conf:

net.ipv4.ip_forward=1



To apply, execute 'sysctl -p'.
If you want to change the perameter temporarlily without editing sysctl.conf,
execute 'sysctl -w net.ipv4.ip_forward=1'.

Next, configure iptables.

iptables -A INPUT -j ACCEPT
iptables -A OUTPUT -j ACCEPT
iptables -A FORWARD -i usb0 -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE



To enable auto setting of iptables in Ubuntu, you must write a script to apply your settings.
I use following script in /etc/rc.local.

/etc/rc.local:

#!/bin/sh -e
/sbin/iptables-restore < /etc/iptables-rusbnat.rules



iptables-rusbnat.rules are created by 'iptables-save > iptables-rusbnat.rules'.


You have to do the work above only once.

You have to do following works whenever enabling reverse tethering.
Enable 'USB Tethering' in android. Execute rusbtether-nat in PC.

rusbtether-nat:

#!/bin/sh 
set -e
export LANG=C
ROUTE=/system/bin/route
GWA=`LANG=C ifconfig usb0 | grep "inet addr:" | \
    sed 's/ *inet addr:\([0-9\.]\+\).*$/\1/'`

ping -I usb0 -c 1 $GWA > /dev/null
# add route
adb shell su -c "$ROUTE add default gw $GWA dev usb0"

DNS=`grep nameserver /etc/resolv.conf | head -n1 | awk '{print$2}'`

# clear dns
for p in net.dns1 net.dns2 net.dns3 net.rmnet0.dns1 \
    net.rmnet0.dns2 dhcp.eth0.dns1 dhcp.eth0.dns2 \
    dhcp.eth0.dns3 dhcp.eth0.dns4 net.eth0.dns1 \
    net.eth0.dns2 net.usb0.dns1 net.usb0.dns2; do
    adb shell su -c "setprop $p ''"
done

# set dns
adb shell su -c "setprop net.dns1 $DNS"

echo Completed



To stop reverse tethering, just disconnect USB cable or disable 'USB Tethering' in android.
You have nothing to do in PC.

You should not to enable WiFi/3G while using reverse tethering.
Because dns settings in android are changed when enabling WiFi/3G and dns client will be not able to resolve names.
If you enable WiFi/3G while reverse tethering, execute rusbtether-nat again to fix dns settings.

Proxy越えをするDNS Server

Proxy越えをするDNS Serverを作った。Perlの Net::DNS::Nameserver を利用した。
これとsquidの多段Proxyを組み合わせれば、AndroidのProxy対応していないHTTPアクセスするアプリを全てProxy越えさせることが可能なはず。

ダウンロードはここのwebnamed-*.tgz。
webnamed + 逆USBテザリング + squid透過Proxyを組み合わせて、DMZのProxy越えができるかを検証しようと思う。

* 仕組み

既存のLAN内のDNS Server ... Default DNS Server
作ったDNS Server ... Slave DNS Server (webnamed)
外部CGI Server上のCGI ... Resolver CGI (resolver.cgi)

1) Slave DNS ServerがDNS queryを受信する。
2) アドレス解決のDNS query の場合、Resolver CGI にDomain Nameを送信して、アドレスを取得する。
3) アドレス解決以外の DNS queryまたは 2) でアドレスが未解決の場合は、Default DNS ServerへDNS queryをforwardする。
4) Slave DNS Serverが 2) または 3) の結果を返信する。


* なぜwebnamedが必要か

まずAndroidにProxy設定をせずにProxy越えするには、DMZのHTTP ProxyへProxy用のHTTP Headerを含むHTTP Requestを投げる必要がある。これにはAndroidのWebブラウザからのHTTP Requestを、一旦squidのTransparent Proxyに投げる。次にsquidからDMZのHTTP Proxyへ投げれば解決する。

だが、WebブラウザはHTTP Proxyを通すことを認識していないため、WebブラウザのDNS Resolverが動いてしまう。Firewall内のDNS Serverは、外部DNS Serverへアドレス解決を参照しない運用になっていることが多いため、大抵DNS queryが失敗するだろう。
そこで、このDNS queryに対して適切に返答するために、今回作ったDNS Serverが必要になる。


* resolver.cgi について

resolver.cgi は私のレンタルサーバに配置した。
webnamed のデフォルトの設定だとそのレンタルサーバに接続する。別のサーバに配置することも簡単にできる。
Google App Engineの利用を最初に考えたものの、InetAddress.getHostAddress() が禁止されていたので断念した。
広告入りの無料サーバでも動作するが、CGIへの直接アクセスは(できるけど)規約違反になることがあるので確認が必要。


* 他に方法はないのか

Google先生への質問スキルが低いのか、似たような物が見つからなかった。
あるいは、もっと別の解決方法が存在するのか、単に需要がないのか。
ご存知の方がいたら教えてください。

2011/05/01

vimで線や矢印を描画する

たまにテキストでクラス図やネットワーク構成図を書くことがある。
vimのプラグインDrawIt!を使うと線、矩形、矢印、楕円を描画することができる。

2011/04/30

Androidで逆USBテザリングを使う(NAT)

環境: Android:CyanogenMod 6.1.1, PC:Ubuntu 10.10

逆USBテザリングをブリッジ接続で行うと、不便が生じた。

- テザリングを止めるとき、PC側でブリッジ削除スクリプトを実行する必要がある。
- ブリッジ削除スクリプトを実行しないとPC側の通信ができなくなる。

それに、Androidにsshで入りたいわけでもないので、AndroidのIPアドレスがLAN内で見える必要もないし。
ということで、NATを利用する方法を考えた。この方法ならば、いつUSBを抜いてもPC側の通信には影響がない。
ブリッジ接続を紹介していたMathieuさんのコメント欄のところで、原理的に同じことを書いている人がいた。

なお、メーラーが自動受信しないとか、Android Marketのダウンロードが動作しないなどのAndroidの逆USBテザリング時に通信しないアプリが存在する理由で述べた問題があるが、私はAndroidのモジュールを変更して無理やりできるようにした。興味のある方はリンク先を参照してください。


まずPC側のIP Forwardを有効にする。

/etc/sysctl.conf:

net.ipv4.ip_forward=1


sysctl -p で反映。sysctl.confを変更せずに一時的に試すなら、
sysctl -w net.ipv4.ip_forward=1 で。

次にiptablesの設定。

iptables -A INPUT -j ACCEPT
iptables -A OUTPUT -j ACCEPT
iptables -A FORWARD -i usb0 -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# 一部修正 5/6

Ubuntuでiptablesの設定をOS起動時に有効にするには、自分でスクリプトを書く必要がある。私は /etc/rc.local に次のように書いている。

/etc/rc.local:

#!/bin/sh -e
/sbin/iptables-restore < /etc/iptables-rusbnat.rules


iptables-rusbnat.rules は上記の設定を行った後に iptables-save > iptables-rusbnat.rules で作った。

ここまで一度設定すれば良い。

以下はテザリングを有効にする度にPC側で実行するもの。
Android側ではやることはテザリングの設定を有効にするだけ。
停止するときはAndroidでテザリングの設定を無効にする。PC側は何もする必要がない。

rusbtether-nat:

#!/bin/sh 
set -e
export LANG=C
ROUTE=/system/bin/route
GWA=`LANG=C ifconfig usb0 | grep "inet addr:" | \
    sed 's/ *inet addr:\([0-9\.]\+\).*$/\1/'`

if [ "$GWA" = "" ]; then
    echo exit
    exit 1
fi

ping -I usb0 -c 1 $GWA > /dev/null
# add route
adb shell su -c "$ROUTE add default gw $GWA dev usb0"

DNS=`grep nameserver /etc/resolv.conf | head -n1 | awk '{print$2}'`

# clear dns
for p in net.dns1 net.dns2 net.dns3 net.rmnet0.dns1 \
    net.rmnet0.dns2 dhcp.eth0.dns1 dhcp.eth0.dns2 \
    dhcp.eth0.dns3 dhcp.eth0.dns4 net.eth0.dns1 \
    net.eth0.dns2 net.usb0.dns1 net.usb0.dns2; do
    adb shell su -c "setprop $p ''"
done

# set dns
adb shell su -c "setprop net.dns1 $DNS"

echo Completed


なお、AndroidのDNSを再設定するところはブリッジ接続の方法と変わらないので、テザリング中にWiFi/3GをONしない方が良い。
ONにした場合DNSの設定が変わって名前解決できなくなるが、再度 rusbtether-nat を実行すれば解決できるようになる。

#追記 5/3
Android側に付くアドレス 192.168.42.129 は固定のようだ。com.android.server.connectivity.Tethering にハードコーディングされていた。

Google検索にBloggerの最新記事がヒットする速度

Bloggerで記事を投稿した30秒後に記事中のキーワードで検索をかけたら、その記事がトップに出た。26400件ヒットしたにもかかわらず。
こういう検索アルゴリズムの仕様に少し疑問もあるが、15へぇくらいだ。

2011/04/29

Androidの通信量の節約方法を考える

近いうちに Softbank を解約して b-mobile Fair に移行すべく、通信量を節約する方法を模索している。
今までは自宅でもWiFiを使わずに3Gで通信していたこともあり、月300-400MB程度使っていた。
目標は 平日8MB/日、休日4MB/日。これで120日使うと900MB。1GBまでの残り100MBはたまに使うGoogle Mapやメール添付イメージなどのバッファとして考えている。
b-mobile U300で通信料制限なしという選択肢もあるが、通信速度は私にとってとても重要なことだ。遅いとイラッと来るので。

現在通信量のうち多くの割合を占めるものは以下の3つ。

1) 通勤中のRSSリーダ、Webブラウザ使用。たぶん通信量の8割を占める。
2) Android Marketでのアプリのアップデート、新しいアプリのダウンロード。
3) Gmailの待ち受け、取得、写真送受信。メイン携帯へのメールもGmail(auone)で取得。

- RSSリーダ対策

自動更新はOFFにする。RSSリーダ(NewsRob)は、出勤前にキャッシュしておく。
手動で更新するのは割りと苦にならない性格なので。

会社では技術調査の名目で社内LANに接続したPCにスマフォを接続している。Firewallを越えるにはProxyに対応したアプリが必要。そこで、プロキシに対応した Sparse rss というのがあったが、Google Readerの同期機能がない。残念。
Google Reader にフィードを集約しているので、個人的に対応は必須要件。

結局探したところで、Proxy対応したアプリが使いやすいとは限らないので、Proxy越えをする要rootedな別の方法を選択した。後日検証する予定。

- Webブラウザ対策

Webブラウザを使うときは Opera Mini を使う。Opera Mini はOpera用のサーバが仲介して、参照先のWebページを圧縮してからブラウザに送るため、普通のダイレクトに接続するタイプのブラウザよりも通信量が結構少ない。
画質の設定を最低・携帯表示ONにすると、広告等の画像の多いページの通信量がかなり軽減される。私の場合、ほとんどが文字情報を見たいだけだったりするのでこれで十分。

ただ、Opera Miniは仲介サーバにつながらないことがある。そんなときは仕方なくOpera MobileまたはDolphin Browser Miniを使っている。

#追記 5/8
Opera MobileでOpera TurboをONにすると、Opera Miniと同様に圧縮される。


- Android Market対策

自動更新をOFFにして、自宅でアップデート&インストールする。
アプリのバージョンアップでデグレードすることもあるため、自動更新はリスクが高いと思っている。
なので元々自動更新はしていない。この作業も苦にならない。

- Gmail対策

メーラはK9-MailでIMAP Idleを使用。待ち受けだけだと、それほど通信量は多くなさそうだが、今度念のため計測しておきたい(計測した)。
たまに取った写真を送ったり、送られたりするのが通信量が大きい。
送信するときは SmallPic で小さくしてから。
受信した添付ファイルは、すぐに見る必要のないものは自宅でWiFiやPCで確認する。
K9-Mailは受信するメッセージのサイズを指定できるので、8kにしている。

#追記 5/3
- 広告/情報収集対策

無料アプリの中にはAdMobなどの広告を取得するものがあるので、通信系のアプリではなくても起動する度に広告をダウンロードすることがある。よく起動するアプリならば、広告なしの有料版に変更したほうが通信量を節約できる。
また、アプリ使用状況の情報収集のためにデータを送信するアプリもある。データ送信することをユーザに許可を求めるアプリが送信するのは構わないが、WiFiのときだけという設定がほしい。
パケットを眺めていたら、ToggleSettings無料版はwww.togglesettings.comと通信するのでアンインストールした。CyanogenModのウィジットボタンで事足りてしまったので。有料版は使ったことがないが、どうなのだろうか。

Ubuntuのアップグレードを促すダイアログに驚く

Ubuntuで作業している最中、突然Ubuntu11.04へアップグレードするかどうかを聞くダイアログ(結構大きめ)が表示された。これには驚いた。
さらにそのダイアログのデフォルトのフォーカスが「今すぐアップグレードする」だったことにも驚いた。

このインターフェースは不親切だと思った。
パネルから小さな枠の通知を表示するだけにするとか、他にスマートで効果的な方法があったはず。
さすがにこれはナシだ。

気を取り直して自動起動するアプリからアップデート通知をはずした。

chkconfig対応のsquid init script

Ubuntuでapt-getでインストールしたらinitスクリプトがなかったので。
起動時にDNSテストをスキップする"-D"が指定されていることに注意。

/etc/init.d/squid:

#!/bin/sh
# chkconfig: 345 98 20
# description: squid server
# processname: squid

# description for chkconfig: 345 98 20
#  345: run levels
#  98: priority to start
#  20: priority to stop

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/squid
NAME=squid
DESC="squid server"
PIDFILE=/var/run/$NAME.pid
test -x $DAEMON || exit 0

if [ -f /etc/default/$NAME ]; then
    . /etc/default/$NAME
fi

DAEMON_OPTS="-D"

start_proc() {
    $DAEMON $DAEMON_OPTS
    if [ "$?" -eq 0 ]
    then
        pidof $DAEMON > $PIDFILE
        return 0
    fi
    return 1
}

stop_proc() {
    kill -9 `cat $PIDFILE 2>/dev/null`
    [ "$?" -ne 0 ] && return 1
    rm -f $PIDFILE
    return 0
}

status_proc() {
    killall -0 $DAEMON 2>/dev/null
    return "$?"
}

case "$1" in
    start)
        echo "Starting ${DESC}"
        start_proc
        [ "$?" -eq 0 ] || echo "failed"
        ;;
    stop)
        echo "Shutting down ${DESC}"
        stop_proc
        [ "$?" -eq 0 ] || echo "failed"
        ;;
    restart|force-reload)
        echo "Restarting ${DESC}"
        stop_proc
        if [ $? -ne 0 ]; then
        failure ; echo ; exit 0
        fi
        sleep 1
        start_proc
        [ "$?" -eq 0 ] || echo "failed"
        ;;
    status)
        status_proc
        [ $? -eq 0 ] && echo "$DESC is alive" || echo "$DESC is dead"
        echo
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|force-reload|status}" >&2
        exit 1
        ;;
esac
exit 0

2011/04/28

Android MarketでDownload pausedが発生したときの対処

逆USBテザリングをするために色々弄っていたのが悪かったのか、WiFi接続時にAndroid Marketからアプリをインストールできなくなってしまった。

現象としては、"Download paused" (日本語で"ダウンロードが一時停止しました") が表示され、ずっとダウンロードが終わらない。3Gに切り替えると正常にダウンロード&インストールはできる。
WiFiネットワークにファイアウォールはない。また、logcat にExceptionやそれらしいメッセージは出ないので原因究明のきっかけを掴めない。
最終的には原因不明のまま、メニューのプライバシーにあるデータの初期化を行って回復した。

少し調べたら同じ現象の人がいるようで、

  • 再起動する。
  • Marketアプリのキャッシュ、データをクリアする。
  • Marketアプリにアカウントを再登録する。
  • Google Talkに接続する。

を試してみた。しかし、現象は変わらず。
Titanium Backupでデータの復元はできるので、結局Factory Resetを選択した。

#追記 5/8
  • Google Talkに接続して、すでにログインしている場合はログアウト・ログインする。
    Google TalkとAndroid Marketの認証・通信の仕組みは共有されているのだろうか。
    PCからMarketサイトでインストールを実行した後、WiFiに接続しているAndroidでダウンロードが始まらないときは、これをやると始まる。
  • /cacheの容量が少ないとダウンロードが開始しないようだ。/cacheのパーティションサイズを変更したとき、大きなファイルはダウンロード開始しなかった。不足しているなら不足しているというエラーを出してくれればいいのに、エラーが出ない。

Bloggerにテキストファイルを投稿するスクリプト

このブログのほとんどの記事をLinuxからGoogleCLで投稿している。
記事のタイトルとタグをいちいちコマンド引数に渡すのが面倒なので、投稿するファイルから拾うスクリプトを作成して使っている。

実行方法:
$ blogger-post file1.txt file2.txt ...

投稿ファイルのサンプル (UTF-8):

記事のタイトル
tag1,tag2
本文
本文
本文


blogger-post:

#!/usr/bin/perl
use utf8;

my $blog = "My Blog";
my $tf = "/tmp/blogger-post.tmp";

while (my $f = shift) {
    print "Posting $f ...\n";
    open(IN, "<:utf8", $f) || die "Cannot open $f : $!\n";
    my $title = <IN>;
    chomp $title;
    (length($title) > 0) || die "Title is empty\n";
    my $tags = <IN>;
    chomp $tags;
    open(OUT, ">:utf8", $tf) || die "Cannot write to $tf : $!\n";
    while (<IN>) {
        print OUT;
    }
    close IN;
    close OUT;
    my $r = system("google blogger post --blog=\"$blog\" ".
        "--draft -n \"$title\" -t \"$tags\" $tf");
    unlink $tf;
    exit 1 if ($r ne 0);
}


$blog は対象のブログ名。

2011/04/26

HTC Desireのパーティションサイズを任意に変更する

AlphaRev 1.8のCM7は /cache が5MBなのだが、これが小さすぎるようだ。
Android Marketは一時ダウンロードディレクトリとして /cache を使用しており、5MB以上の.apkをダウンロードできない。現象としては、Marketアプリでずっとダウンロードが完了しない状態になる。
これは困るのでパーティションをまた変更した。

今度はXDAのCustom MTD Partitionsを見て、system/cache を130/20に設定。再度CM6.1.1を焼いた。
ATOK TrialをMarketからインストールできるようになった。

ついでに使用しない.apkを/system/appから削除。私はカレンダー同期を使用しないが、アカウントの設定でカレンダーの同期をOFFにしているはずなのに、裏でサービスが動いてコネクションまで張っていたので削除した。

2011/04/25

Androidで逆USBテザリングを使う(ブリッジ接続)

ここに記述されている方法よりも、NATで行う方法の方が使い勝手が良いので、逆USBテザリングをしたい方は先にそちらを参照してください。


WiFiの代わりに3Gよりかは高速な接続方法として、USBテザリングを利用できないか調べたら、こちらのMathieuさんがやり方を紹介していた。ただし要rooted。
AndroidからPC側のブリッジを介して通信できる。

ブラウザ、メールは使えた。ただしアプリ的な問題がある。
K9-Mailを使っているのだが、"WiFiと3Gが無効のときはメール取得を停止する"機能が実装されているようで、受信メニューを手動で選択しないと受信してくれない。
もちろん普通は正しい動作。待機系のアプリは大抵このような動作をするのではなかろうか。
(Androidの逆USBテザリング時に通信しないアプリが存在する理由で調べてみた)

以下、使ったスクリプト。PC側はrusbtether-start で逆USBテザリング開始。rsubtether-stop で元に戻す。Android側は開始前にUSBテザリングの設定をON、終了後にOFFすれば良い。
最初、ping www.google.com が通らないので調べたらDNSが別のWiFiネットワークで設定されたものだった。setprop net.usb1 を実行して上手く行った。他にもMathieuさんのブログのコメント欄にいくつか情報がある。

WiFi/3GがONの状態だと、WiFi/3Gのインターフェースから取得したDNSも見に行ってしまう。
これをやるときはWiFi/3GはOFFにした方が良い。

#追記4/29
さらに検証したら3GがOFFのときでも、net.rmnet0.dns1, net.rmnet0.dns2 を使って名前解決を試みる現象を確認した。そのため、net.usb1 を設定する前に全てのDNS設定をクリアするようにした。クリアしたDNSはWiFiまたは3GをONにしたときDHCPでのアドレス取得と共に戻る。テザリング中にWiFi/3GをONにしてからまたOFFにしてしまうと、WiFi/3GのDNS設定が行われて名前解決ができなくなるので注意。
また、スクリプトに一部誤りがあったので修正。

#追記4/30
Android側でUSBテザリングを停止すると、ブリッジを構成しているPC側のeth0も通信ができなくなってしまう。USBを抜いたり、テザリングを停止した後は必ず rusbtether-stop を実行する必要がある。


rusbtether-start:

#!/bin/sh
sudo ifconfig eth0 0.0.0.0
sudo ifconfig usb0 0.0.0.0
sudo brctl addbr br0
sudo brctl addif br0 eth0
sudo brctl addif br0 usb0
sudo ifconfig br0 up
sudo dhclient br0

# Clear DNS
for p in net.dns1 net.dns2 net.dns3 net.rmnet0.dns1 \
    net.rmnet0.dns2 dhcp.eth0.dns1 dhcp.eth0.dns2 \
    dhcp.eth0.dns3 dhcp.eth0.dns4 net.eth0.dns1 \
    net.eth0.dns2 net.usb0.dns1 net.usb0.dns2; do
    adb shell su -c "setprop $p ''"
done

adb shell su -c "netcfg usb0 dhcp"
# Replace DNS
adb shell su -c "setprop net.dns1 192.168.3.254"
# Show the address attached to usb0
adb shell su -c netcfg


rusbtether-stop:

#!/bin/sh
sudo ifconfig eth0 down
sudo ifconfig usb0 down
sudo ifconfig br0 down
sudo brctl delbr br0
sudo ifconfig eth0 up
sudo dhclient eth0

Logitec LAN-PW150N/Rで頻繁に切断したときの対処

HTC DesireをLAN-PW150N/Rで接続していると2-4分置きに切断する。
ルータのログを見ると以下のメッセージが出力されていた。

[XX:XX:XX:XX:XX:XX] has been aged-out and disassociated

別のマシンからDesireに対して1分置きにpingしてやると切断しない。

ルータモードでもAPモードでも症状は同じ。
MACがaged-outするということはarpテーブル関連なのだと思うが、ルータの設定にはそれらしきものはない。
"Age Out Timer"の設定ができるLogitecのルータはあるようだが、このルータではできない。
DTIMピリオドという項目を1に変更してみても効果なし。
安価でお手軽な物だから仕方ないのか。

Desire側での対処ができないかと、Wifi Keep Alive、REGPON wifi KeepAlive を使ってみたが改善しない。
唯一有効だった外部マシンからの定期pingはやりたくないし、
もう新しいルータを購入するしかないかと考えている。

#追記 5/10
ファームウェアを 1.09 から 1.11 に更新したら10分~1時間置きの切断になった。
これならまあ使えるかも。

2011/04/24

ソースコードをHTMLへ変換する

ソースコードをHTMLへ変換するツールをいくつか試したが、しっくり来るものがない。
タブやスペースを"&nbsp;"に変換してくれて、monospaceフォントで出力してほしい。それと可変幅のテーブルで囲わずにページ幅のhrで挟んでほしい。
探すのが面倒なので以下のスクリプトを作った。
以下のスクリプトのHTMLは、以下のスクリプト自体で変換して貼り付けたもの。

blogger-code:

#!/usr/bin/perl

print "<hr style=\"border: 2px #9999ff solid;\" />";
print "<pre><span style=\"font-family: 'Courier New', ".
    "Courier, monospace;\">";
while (<>) {
    s/\t/    /g;
    s/&/&amp;/g;
    s/</&lt;/g;
    s/>/&gt;/g;
    s/  /&nbsp;&nbsp;/g;
    print;
}
print "</span></pre>";
print "<hr style=\"border: 2px #9999ff solid;\" />\n";

HTC DesireにCyanogenMod 6.1.1を入れる

CyanogenMod 7.0.0 (Android 2.3.3) から CyanogenMod 6.1.1 (Android 2.2.1) に変更した。
やはり今は2.2じゃないとアプリが追いついてないので、生活ができない。
パーティションサイズを変更したので、以前から使っていたアプリを入れても、/dataにまだ160MBの空きがある。

RADIOはCyanogenMod 7.0.0を入れたときすでに最新に更新済み。
アプリは Titanium Backup でバックアップ&レストアした。
しかし、OS Monitor / Simeji / Opera Mini など他にもいくつかのアプリケーションが起動しなくなった。
adb logcat で確認したら、起動しないアプリは UnsatisfiedLinkError が出てた。
起動しないアプリを一度アンインストールしてから、Android Marketから再インストールしたら動いた。

HTC Desireのパーティションサイズを変更する

S-OFFへの変更とパーティションサイズの変更をした。
app2sd-extを使うかどうか迷ったが、内部メモリの大半を眠らせておくのはもったいない気がしたので。

AlphaRev r1.80のBravo CM7を使用。
fastboot をHTCからダウンロードした。

まずS-OFFについてはAlphaRev 1.8 HBOOT reflash utilityの .iso をダウンロード。VMWare Player で Ubuntu Linux タイプのVMを作って起動し、VM にUSBを接続した。10分もかからず終了。
パーティションサイズの変更は、AlphaRevのページのFAQの手前に書いてあるやり方でサクッと終了。

これで/dataが302.6MBになった。

HTC Desire - CyanogenMod 7.0.0のバッテリー消費量

6時間ほぼスリープ状態で100%=>89% だった。

常駐アプリの状況
- K9-Mail (IMAP Idle 2アカウント, メール3件取得)
- NewsRob (100件くらいのrssを自動取得)
- なまず速報β (速報なし)


あと、3G回線。

2011/04/23

付箋紙アップレットのデータを抽出する

Ubuntu Desktopに標準でインストールされるアップレット"付箋紙"(Sticky Notes)のデータは、~/.gnome2/stickynotes_applet にXML形式で保存されている。

これを抽出して一覧で出力するスクリプトを作った。

こんな感じ。

$ list-stickynotes
2011年04月20日 猫の餌を買う
2011年04月21日 4/23 食事会 大宮 12:00


1つの付箋が"タイトル<タブ文字>内容"の1行で出力される。
付箋の内容に改行がある場合はスペースに変換。エンコーディングはUTF8。
"付箋紙"は内容を記述してから10秒ぐらいしないと、ファイルに保存されないことに注意。

list-stickynotes:

#!/usr/bin/perl
package StickyNotesHandler;
use base qw(XML::SAX::Base);

my $elm;

sub start_element {
    my ($self, $data) = @_;
    $elm = $data->{Name};
    return if ($elm ne 'note');
    my $na = $data->{Attributes};
    my $t = $na->{'{}title'}->{Value};
    print "${t}\t";
}

sub end_element {
    return if ($elm ne 'note');
    print "\n";
    $elm = undef;
}

sub characters {
    my ($self, $data) = @_;
    return if ($elm ne 'note');
    my $c = $data->{Data};
    if ($c eq "\n") { $c = ' '; }
    print "$c";
}

#-----------------------------------
package main;

use HTML::Entities;
use XML::SAX;
use Env qw(HOME);

binmode STDOUT => "utf8";

my $fi = "${HOME}/.gnome2/stickynotes_applet";
my $p = XML::SAX::ParserFactory->parser(
    Handler => StickyNotesHandler->new);

$p->parse_uri($fi);

HTC DesireにCyanogenMod 7.0.0を入れる

HTC Desire (Softbank X06HT)の内部メモリが少ない。X06HTに限らないと思うが、プリインストールされた使わないかつ消せないアプリが多すぎて、無駄にリソースを消費している。なので、CyanogenMod-7 for Desire GSM V7.0.0を入れた。

・root化 ... unrevoked 3.22

この時点で最新の3.32だとfarmwareが新しすぎるというエラーが出るので、3.22を使用。
最新版ダウンロードへのリンクである、
http://downloads.unrevoked.com/recovery/3.32/reflash.tar.gz
の3.32の部分を3.22に手動で変更したらダウンロードできた。

・RADIO 32.54.00.32U_5.14.05.17

RADIOはunrevoked 3.22で入れたままのバージョンだと動かない。最新のものを入れた。
RADIOの更新に失敗するとスマフォが文鎮化することがあるそうで。少し緊張しつつ繰り返し再起動するのを見守った。

・CyanogenMod-7 for Desire GSM V7.0.0

XDA Developersの投稿を参考にした。Introduction通りにやったら起動した。最初wipeするのを忘れたらsplashがループして先に進まなかった。

CyanogenMod-7 は Android 2.3.3。空きメモリは前と同じアプリを入れた後で70MBだった。正規ROMではアプリを入れた状態で空きが20MB。

少し触ってみた感じでは、

満足な点
- 正規ROMよりもきめ細かい設定ができる。
- ロック画面上で使用可能なアプリが選択できる。音楽再生ウィジットを置いた。
- Android標準のテザリング機能を利用できる。

不満な点
- Android 2.3 対応アプリが少ない。まだこれで生活するのは不便だった。アプリのメインアクティビティからサブアクティビティが表示されないものが結構ある。これにより一部のアプリの一部の設定変更できないので困る。アプリ / CyanogenMod / Android 2.3のいずれの問題なのかはわからない。
- たまにフリーズする。再起動が必要になった。
- 以前から使っていたお天気系のウィジットがウィジットの選択に出てこなくなった。原因不明。

Androidバージョン分布の調査によると2011/4/1の時点で2.3系は2.5%(2.3:0.8%、2.3.3:1.7%)。DesireとXperiaが今年中に2.3へアップデートすることが発表されているため、アプリの実質的な対応はそれからになるだろう。

テザリングについては、たまに使うのでうれしい。httpsも有料アプリ不要だし。速度はLinux PCに接続してBNR Speed Testで計測した結果、Down 364kbps / Up 232kbps だった。
なお、近いうちにSoftbankを解約してb-mobile Fairに変更するつもりなので、Softbank回線でのテザリングにおけるグレーな感じとはおさらばに。

Android 2.2 から 2.3 でバッテリ消費量が改善されたそうで、正規ROMの無駄アプリが排除されたことによる効果も合わせて、消費量がどの程度かを調べたい。

次はCyanogen 6.1 Froyoを試す。

Linuxで固定IPアドレスを設定する

固定IPアドレスの設定の仕方をいつも忘れるのでメモ。

CentOS系: /etc/sysconfig/network-scripts/ifcfg-eth#

DEVICE=eth0
IPADDR=192.168.3.10
NETMASK=255.255.255.0
HWADDR=00:11:22:33:44:55
ONBOOT=yes



resolv.conf:

nameserver 192.168.3.253
nameserver 192.168.3.254
search localdomain



Debian系: /etc/network/interfaces

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
address 192.168.3.10
network 192.168.3.0
netmask 255.255.255.0
gateway 192.168.3.254
dns-nameservers 192.168.3.253 192.168.3.254
dns-search localdomain


(dns-nameservers, dns-search は resolvconf 使用時)

2011/04/20

JPAでbooleanをマッピングする

環境: Hibernate 3.6.3, PostgreSQL 8.4.7

JPAでbooleanをDBのBOOLEAN型にマッピングできるのかを試したら、普通にできた。


@Entity
public User {
    @Id
    private int id;
    @Column
    private boolean valid;

    public boolean isValid() {
        return valid;
    }
 
    public void setValid(boolean valid) {
        this.valid = valid;
    }



できて当然といえば当然だけどね。ちょっとうれしい。

しかし、BOOLEAN型ではない型を使ってブール値を保存しているような既存システムのDBに対してJPAでマッピングするには、値を変換する処理を書く必要があるようだ。

変換処理はWikibooksのJava Persistence/Basic Attributesここで紹介されている。これは面倒でスマートではない。

そんな面倒な変換処理をAnnotationで生成してくれる、Boolean Magic Converterというのを作っている人がいた。あるプロジェクトの一部品だが、BooleanMagicProcessorFactory.jar だけ個別にダウンロードできるようになっている。
こんな感じで書ける。


@BooleanMagic(trueValue = "T", falseValue = "F",
columnName = "VALID", ifNull = ReturnType.FALSE)
private transient Boolean valid;


Hibernateではorg.hibernate.annotations.Typeで次の型がある。

@Type(type="yes_no") ... Y/N
@Type(type="true_false") ... T/F

1/0はないのか...と思った。一応 org.hibernate.usertype.UserType を実装すれば、1/0を@Typeで指定できるようになりそうだが、いずれにせよ他のアプリケーションサーバで動かすことになったときHibernate依存だと困るので避けたい。

2011/04/19

Struts2 ConventionとTilesのJSP検索パス

環境: Struts 2.2.1.1, Tiles 2.2.2

Struts2 ConventionとTilesを一緒に使うときの話。

Conventionが.jsp を検索するパスはデフォルトで /WEB-INF/content (constant: struts.convention.result.path) だが、Tilesは Context Root から検索する。Tilesの方はソースを追ってみた限りでは、templateに共通のプリフィックスを付加するような処理はなく、templateの値がそのままdispatchされるようだ。

できる限り設定の追加は避けたいので、Tiles のパスを長々と書くことにした。

tiles.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
"http://tiles.apache.org/dtds/tiles-config_2_0.dtd">

<tiles-definitions>

<definition name="/main.tiles" template="/WEB-INF/content/main.jsp">
<put-attribute name="body" value="/WEB-INF/content/body.jsp" />
<put-attribute name="footer" value="/WEB-INF/content/footer.jsp" />
</definition>

</tiles-definitions>



template attribute の /WEB-INF/conent を書かずに済む方法があれば、すっきりするのだが。

update-alternativesコマンドを使う

Ubuntu 10.10 Server x86_64 にaptitudeでgcc-4.4をインストールした。
/usr/bin/gcc-4.4, cpp-4.4 はあるが、/usr/bin/gcc, cpp はないので、update-alternatives で追加。
"gcc" "cpp" がないとconfigure時に面倒なので。


# update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.4 90
# update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-4.4 90


ld は
/usr/bin/ld -> /usr/bin/ld.bfd
という symlink になっていた。
goldの方が速いので、/usr/bin/ld.gold を使うように変更することにした。
ld もデフォルトでは /etc/alternative に入っていない。


# update-alternatives --install /usr/bin/ld ld /usr/bin/ld.bfd 1
# update-alternatives --install /usr/bin/ld ld /usr/bin/ld.gold 2
# update-alternatives --list ld
/usr/bin/ld.bfd
/usr/bin/ld.gold


今担当しているプロジェクトのフルビルドにかかる時間が、52秒から50秒に短縮。もともとリンクにそれほど時間のかかるプロジェクトではないから、こんなものだろう。

2011/04/18

GoogleCLでDeprecationWarningが発生したときの対処

環境: Ubuntu 10.04, Python 2.6.5, GoogleCL 0.9.13

GoogleCLでgoogle blogger listすると次のエラーが出た。


/usr/lib/pymodules/python2.6/atom/http.py:225: DeprecationWarning: socket.ssl() is deprecated.  Use ssl.wrap_socket() instead.
ssl = socket.ssl(p_sock, None, None)
/usr/lib/pymodules/python2.6/atom/http.py:226: DeprecationWarning: FakeSocket is deprecated, and won't be in 3.x. Use the result of ssl.wrap_socket() directly instead.
fake_sock = httplib.FakeSocket(p_sock, ssl)
Traceback (most recent call last):
File "/usr/bin/google", line 849, in
main()
File "/usr/bin/google", line 835, in main
run_once(options, args)
...
...
File "/usr/lib/python2.6/httplib.py", line 755, in send
self.sock.sendall(str)
AttributeError: sendall


これはGoogle Data APIs Python Client Libraryを最新にすることで解決した。Ubuntu 10.04のaptで入るpython-gdataは1.2.4。元サイトの最新版は2.0.14。ちなみにUbuntu 10.10のaptでは2.0.8だったので問題は発生しなかった。

1.2.4のままでも /usr/lib/pymodules/python2.6/atom/http.py を以下のように修正したら動いた。

@@ -42,6 +42,7 @@
import atom.http_interface
import socket
import base64
+import ssl

@@ -222,12 +223,10 @@
raise ProxyError('Error status=%s' % str(p_status))

# Trivial setup for ssl socket.
- ssl = socket.ssl(p_sock, None, None)
- fake_sock = httplib.FakeSocket(p_sock, ssl)

# Initalize httplib and replace with the proxy socket.
connection = httplib.HTTPConnection(proxy_url.host)
- connection.sock=fake_sock
+ connection.sock=ssl.wrap_socket(p_sock, None, None)
return connection

WindowsのVMWare Player同士でシリアルポートを接続する

ホストOSとして使えるのがWindowsしかなく、VMWare Player上のLinuxカーネルをデバッグしたときの設定。

デバッグする側のVMをdebugger vm、デバッグされる側のVMをdebuggee vmとする。VMに仮想シリアルポートを名前付きパイプで追加して、debugger vmの仮想シリアルポートはサーバ、debugee vmの仮想シリアルポートはクライアントにする。
最近のVMware Playerは.vmxの設定をUIからできるので楽。

以下の設定はUIが無かったころのもの。
なお、ホストOSがLinuxのときはserial0.fileNameを/tmp/vm-ttyとかにすればOK。

- debugger vm の .vmx:


serial0.present = "TRUE"
serial0.startConnected = "TRUE"
serial0.yieldOnMsrRead = "TRUE"
serial0.pipe.endPoint = "server"
serial0.tryNoRxLoss = "FALSE"
serial0.fileType = "pipe"
serial0.fileName = "\\.\pipe\com1"
serial0.autodetect = "TRUE"



- debuggee vm の .vmx:


serial0.present = "TRUE"
serial0.startConnected = "TRUE"
serial0.yieldOnMsrRead = "TRUE"
serial0.pipe.endPoint = "client"
serial0.tryNoRxLoss = "FALSE"
serial0.fileType = "pipe"
serial0.fileName = "\\.\pipe\com1"
serial0.autodetect = "TRUE"

LinuxのVMWare PlayerでホストOSからゲストOSのLinuxをデバッグする

LinuxでVMware Player上のLinuxをデバッグするときの設定。
仮想シリアルポートをソケットとして追加する。

.vmx:

serial0.present = "TRUE"
serial0.fileType = "pipe"
serial0.fileName = "/tmp/vm-tty-pipe"
serial0.pipe.endPoint = "server"
serial0.yieldOnMsrRead = "TRUE"



あとはVirtualBoxのときと同じ。

/boot/grub/menu.lst(/boot/grub/grub.conf) の
kernel /vmlinuz-2.6..... というところに、以下を追加。

kgdbwait kgdboc=ttyS0,115200



Host OSで
$ socat -d -d /tmp/vm-tty-pipe pty

socatのログで /dev/pts/# の部分を確認。

2011/04/18 20:50:05 socat[23989] N PTY is /dev/pts/5



gdbの設定
.gdbinit:

file ./vmlinux
set remotebaud 115200
target remote /dev/pts/5 ... socatで確認した番号

2011/04/17

Evernoteからデータを移した

Evernoteにあったメモをこのブログに移した。

Evernoteのエディタがブラウザ版/アプリ版が共に使いずらいと思っていた。
私としては直接HTMLを編集したい。

なので、ありきたりにブログへ移行することにした。
Linuxで作業することが多いのだが、GoogleのサービスはLinuxで動作するコマンドラインツールGoogleCLが公開されているのがかなりうれしい。

Windowsの"送る"フォルダを開く

「ファイル名を指定して実行」で
shell:sendto
shellで開くのは他にもたくさんあるけど、使ったことがあるのはこれだけ。

Windows XPでスクリーンロックのショートカットを作成する

%windir%\system32\rundll32.exe user32.dll,LockWorkStation

VMware Player 3D accelarationの設定

どの程度動くのか興味があってやってみた。
Host OSには当然及ばないが、かなり頑張ってる方だと思う。

なお最新のVMware Playerならば.vmxを直接編集せずにUIから設定できる。

.vmx:

#Enabled 3D Accelaration
mks.enable3d = TRUE
#VRAM 64MB
svga.vramSize = 67108864


あと、ゲームをするときなどはマウスが外に出ないよう、以下の設定をしておくと良い。

#Disable mouse integration
vmmouse.present = FALSE

VMware Playerのキーバインドを変更する

ある環境にVMware Playerをインストールしたとき、日本語キーボードで"_"を入力できなかったり、上矢印キーの動作が変なことがあった。以下の設定で直った。

/etc/vmware/config:

xkeymap.language = jp109
xkeymap.keycode.37 = 0x01d # Control_L
xkeymap.keycode.64 = 0x038 # Alt_L
xkeymap.keycode.97 = 0x073 # kana_RO
xkeymap.keycode.100 = 0x079 # Henkan
xkeymap.keycode.104 = 0x11c # KP_enter
xkeymap.keycode.105 = 0x11d # Control_R
xkeymap.keycode.106 = 0x135 # devide
xkeymap.keycode.107 = 0x137 # Print
xkeymap.keycode.108 = 0x138 # Alt_R
xkeymap.keycode.110 = 0x147 # Home
xkeymap.keycode.111 = 0x148 # Up
xkeymap.keycode.112 = 0x149 # Prior
xkeymap.keycode.113 = 0x14b # Left
xkeymap.keycode.114 = 0x14d # Right
xkeymap.keycode.115 = 0x14f # End
xkeymap.keycode.116 = 0x150 # Down
xkeymap.keycode.117 = 0x151 # Next
xkeymap.keycode.118 = 0x152 # Insert
xkeymap.keycode.119 = 0x153 # Delete
xkeymap.keycode.132 = 0x07d # backslash
xkeymap.keycode.133 = 0x15b # Super_L
xkeymap.keycode.135 = 0x15d # Menu
xkeymap.keycode.211 = 0x073

Ubuntu / VMWare Server 2.0の管理ユーザ設定

Ubuntuではrootでログインできない。したがって初期状態でVMware Infrastructure Web Accessにログインできない。

そこで、rootグループに属する別のユーザを追加する。
/etc/vmware/hostd/authorization.xml の
<ACEDataUser>root</ACEDataUser> のrootを、rootグループの別のユーザへ変更する。

VirtualBoxでLinuxカーネルをデバッグする

VirtualBoxの[Settings]->[Serial Ports]で"Port Mode"を"Host Pipe"にして、"Create Pipe"にチェック。
Pathに適当なパス(ここでは/tmp/vm-tty-pipe)を入力。

カーネルブート時のパラメータに次の文字列を追加。
kgdbwait kgdboc=ttyS0,115200

host os で
$ socat -d -d /tmp/vm-tty-pipe pty:

ちなみにsocatを使わないで直接 /tmp/vm-tty-pipe をgdbに渡す方法は不可。

gdbの設定
.gdbinit:

file ./vmlinux
set remotebaud 115200
target remote /dev/pts/0 ... 0 はsocatで出力された番号とする

VirutlaBoxで仮想ディスクをコピーする

cpコマンドなどでファイルをコピーした場合、VDIに記録されているUUIDを変更する。

$ VBoxManage internalcommands sethduuid original.vdi


以下のコマンドでも cp+sethduuid と同じ

$ VBoxManage clonevdi VDI1 VDI2


コピーしたときに、"udev: renamed network interface eth0 to eth2"と表示されて、
/dev/eth* が存在しない場合、/etc/udev/rules.d/70-persistent-net.rules
の中のMACアドレスを新しいものに変更する。

VMware Playerで仮想ディスクへの変更を削除する

.vmxを手動で変更。

ide0:0.mode = " independent-nonpersistent "
snapshot.action = " autoRevert "
snapshot.disabled = "TRUE"

ブラウザからサイトの表示速度を測定する

「ウェッブサイトの表示速度を測定するフリーツール集」

chkconfig対応のsvnserv init script

/etc/init.d/svnserv:

#!/bin/sh
# chkconfig: 345 98 20
# description: Subversion server
# processname: svnserve

# description for chkconfig: 345 98 20
# 345: run levels
# 98: priority to start
# 20: priority to stop

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/svnserve
REPOSITORY=/var/svn/repos
NAME=svnserve
DESC="Subversion server"
USER=repos
PIDFILE=/var/run/$NAME.pid
test -x $DAEMON || exit 0

if [ -f /etc/default/$NAME ]; then
. /etc/default/$NAME
fi

DAEMON_OPTS="-d -r $REPOSITORY $OPTIONS"

start_proc() {
sudo -u $USER $DAEMON $DAEMON_OPTS
if [ "$?" -eq 0 ]
then
pidof $DAEMON > $PIDFILE
return 0
fi
return 1
}

stop_proc() {
kill -9 `cat $PIDFILE 2>/dev/null`
[ "$?" -ne 0 ] && return 1
rm -f $PIDFILE
return 0
}

status_proc() {
killall -0 $DAEMON 2>/dev/null
return "$?"
}

case "$1" in
start)
echo "Starting ${DESC}"
start_proc
[ "$?" -eq 0 ] || echo "failed"
;;
stop)
echo "Shutting down ${DESC}"
stop_proc
[ "$?" -eq 0 ] || echo "failed"
;;
restart|force-reload)
echo "Restarting ${DESC}"
stop_proc
if [ $? -ne 0 ]; then
failure ; echo ; exit 0
fi
sleep 1
start_proc
[ "$?" -eq 0 ] || echo "failed"
;;
status)
status_proc
[ $? -eq 0 ] && echo "$DESC is alive" || echo "$DESC is dead"
echo
;;
*)
echo "Usage: $0 {start|stop|restart|force-reload|status}" >&2
exit 1
;;
esac
exit 0

ssh tunnilingでsvn+ssh

.subversion/config:

[tunnel] 
            ssh5022 = ssh -p 5022 -i /path/to/id_rsa.pub 



% svn co svn+ssh5022://localhost/proj/trunk 

chkconfig対応のHudson init script

/usr/local/hudson/hudson:

#!/bin/sh
/usr/lib/jvm/java-6-sun/bin/java $HUDSON_JVM_OPTS -jar /usr/local/hudson/hudson.war


/etc/init.d/hudson:

#!/bin/sh
# chkconfig: 345 98 20
# description: Hudson
# processname: java

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/hudson/hudson
NAME=hudson
DESC="Hudson"
USER=repos
PIDFILE=/var/run/$NAME.pid
LOG=/var/log/$USER/hudson_stdout.log
HTTP_PROXY_HOST=www-proxy.sra.co.jp
HTTP_PROXY_PORT=80
export HUDSON_JVM_OPTS="-Xmx128m -server -Dhttp.proxyHost=$HTTP_PROXY_HOST -Dhttp.proxyPort=$HTTP_PROXY_PORT -Dhttps.proxyHost=$HTTP_PROXY_HOST -Dhttps.proxyPort=$HTTP_PROXY_PORT"

test -x $DAEMON || exit 0

if [ -f /etc/default/$NAME ]; then
. /etc/default/$NAME
fi

get_pid() {
PID=`ps ax|grep hudson.war|grep -v grep|awk '{print $1}'`
}

start_proc() {
cd /tmp
sudo -u $USER $DAEMON 1>$LOG 2>&1 &
if [ "$?" -eq 0 ]; then
sleep 1
get_pid
echo $PID > $PIDFILE
return 0
fi
return 1
}

stop_proc() {
kill -9 `cat $PIDFILE 2>/dev/null`
[ "$?" -ne 0 ] && return 1
rm -f $PIDFILE
return 0
}

status_proc() {
get_pid
[ "$PID" != "" ];
return $?
}

case "$1" in
start)
echo "Starting ${DESC}"
start_proc
[ "$?" -eq 0 ] || echo "failed"
;;
stop)
echo "Shutting down ${DESC}"
stop_proc
[ "$?" -eq 0 ] || echo "failed"
;;
restart|force-reload)
echo "Restarting ${DESC}"
stop_proc
if [ $? -ne 0 ]; then
failure ; echo ; exit 0
fi
sleep 1
start_proc
[ "$?" -eq 0 ] || echo "failed"
;;
status)
status_proc
[ $? -eq 0 ] && echo "$DESC is alive" || echo "$DESC is dead"
echo
;;
*)
echo "Usage: $0 {start|stop|restart|force-reload|status}" >&2
exit 1
;;
esac
exit 0

CUnit

オリジナルのCUnitよりも、Google が拡張したCUnitの方が導入が簡単。
Google版にはJUnitのXML形式のレポートを生成する拡張がある。
試したバージョンはオリジナルの2.1-0、Google版の1.1.1。

・オリジナルのCUnit 2.1 のビルド方法

$ dos2unix *
$ libtoolize
$ aclocal
$ autoconf
$ automake --add-missing
$ ./configure --prefix=/usr/local --enable-curses
$ make


・Google版CUnitでJUnitのXML形式のレポートを生成する方法

CU_automated_enable_junit_xml(CU_TRUE);
CU_automated_run_tests();


・Google版CUnitでHudsonのレポートを表示するには

Hudson でレポートを表示するにはJUnit形式で出力して、
以下のスクリプトを通して修正する必要がある。

cunit-to-junit:

#!/usr/bin/perl
while (<>) {
    next if (/<cunit_testsuites>/);
    next if (/<\/cunit_testsuites>/);
    next if (/<cunit_footer>/);
    next if (/<\/cunit_footer>/);
    print;
}


・Google版CUnit サンプル

#include <stdio.h>  
#include <CUnit/CUnit.h>
#include <CUnit/Console.h>
#include <CUnit/CUCurses.h>
#include <CUnit/Automated.h>
#include <CUnit/Basic.h>
#include "config.h"

void setup_test_suite();

void test_01() {
FILE *fp;
fp = fopen("test.log", "w");
CU_ASSERT_PTR_NOT_NULL(fp);
fprintf(fp, "PARAMS=%s %s %s\n", PARAM_A, PARAM_B, PARAM_C);
fclose(fp);
}

void test_02() {
}

void test_03() {
if (strcmp(PARAM_B, "B3") == 0) {
CU_FAIL("PARAM_B is B3");
}
}

enum {
MODE_CONSOLE,
MODE_CURSES,
MODE_AUTOMATED,
MODE_BASIC,
};

int main(int argc, char **argv) {

int mode = MODE_CURSES;

if (argc >= 2) {
char *arg = argv[1];
if (strcmp(arg, "-c") == 0) {
mode = MODE_CONSOLE;
} else
if (strcmp(arg, "-r") == 0) {
mode = MODE_CONSOLE;
} else
if (strcmp(arg, "-a") == 0) {
mode = MODE_AUTOMATED;
} else
if (strcmp(arg, "-b") == 0) {
mode = MODE_BASIC;
} else {
fprintf(stderr, "uknown option: %s\n", arg);
return 1;
}
}

CU_initialize_registry();
CU_automated_enable_junit_xml(CU_TRUE);
CU_set_output_filename("cunit");
setup_test_suite();

switch (mode) {
case MODE_CONSOLE:
CU_console_run_tests();
break;
case MODE_CURSES:
CU_curses_run_tests();
break;
case MODE_AUTOMATED:
CU_automated_run_tests();
break;
case MODE_BASIC:
(void)CU_basic_run_tests();
break;
}

CU_cleanup_registry();

return 0;
}

void setup_test_suite() {
CU_pSuite suite1;

suite1 = CU_add_suite("TestSuite1", NULL, NULL);
CU_add_test(suite1, "test_01", test_01);
CU_add_test(suite1, "test_02", test_02);
CU_add_test(suite1, "test_03", test_03);
}

wineの日本語フォントを変更する

wineで表示される日本語を見やすくする設定方法。
小林さんが提供しているIPAモナーフォントをダウンロードして、
fonts ディレクトリからttfを取り出す。

ttf を /usr/share/fonts/ipa に配置する。
$ sudo chmod 644 /usr/share/fonts/ipa/*
を忘れずに。

$ cd ~/.wine/drive_c/windows/Fonts/
$ ln -s /usr/share/fonts/ipa/*.ttf .

~/.wine/user.reg へ追加:

[Software\\Wine\\Fonts\\Replacements]
"MS Gothic"="IPA \x30e2\x30ca\x30fc \x30b4\x30b7\x30c3\x30af"
"MS Mincho"="IPA \x30e2\x30ca\x30fc \x660e\x671d"
"MS PGothic"="IPA \x30e2\x30ca\x30fc P\x30b4\x30b7\x30c3\x30af"
"MS PMincho"="IPA \x30e2\x30ca\x30fc P\x660e\x671d"
"MS UI Gothic"="IPA \x30e2\x30ca\x30fc UI\x30b4\x30b7\x30c3\x30af"
"\xff2d\xff33 \x30b4\x30b7\x30c3\x30af"="IPA \x30e2\x30ca\x30fc \x30b4\x30b7\x30c3\x30af"
"\xff2d\xff33 \x660e\x671d"="IPA \x30e2\x30ca\x30fc \x660e\x671d"
"\xff2d\xff33 \xff30\x30b4\x30b7\x30c3\x30af"="IPA \x30e2\x30ca\x30fc P\x30b4\x30b7\x30c3\x30af"
"\xff2d\xff33 \xff30\x660e\x671d"="IPA \x30e2\x30ca\x30fc P\x660e\x671d"

[Software\\Wine\\X11 Driver]
"ClientSideAntiAliasWithCore"="Y"
"ClientSideAntiAliasWithRender"="Y"

autofsの設定

/etc/auto.master:

/fs             /etc/auto.misc --timeout=600 --ghost


--ghost はマウントせずにエントリを表示するオプション。
tab補完を効かせたいので付けた。

/etc/auto.misc:

# ntfs
ntfshdd         -fstype=ntfs            :/dev/sda2

# ssh
sshsvr       -fstype=fuse,follow_symlinks,rw,nodev,nonempty,noatime,allow_other,max_read=65536,uid=xxx,gid=yyy     :sshfs\#xxx@sshsvr\:

# smb
smbsvr     -fstype=smbfs,credential=/etc/credential.xxx,uid=xxx,gid=yyy,iocharset=utf8    ://smbsvr/dir

coreファイルのパスを変更する

coreファイルは一ヶ所にまとまっていてほしいので保存先パスを変更した。

$ cat cat /proc/sys/kernel/core_pattern
core

# echo /var/core/core.%e.%p > /proc/sys/kernel/core_pattern

書式指定子は以下のとおり

%p - pid
%u - uid
%g - gid
%s - signal number
%t - UNIX time of dump
%h - hostname
%e - executable filename
%% - output one "%"
%<NUL> - "%" is dropped
%<OTHER> - both are dropped


init scriptで起動するプロセスはinit script内か/etc/defaultでulimit -c unlimitedすることを忘れずに。


Linuxのプロセス数制限

・システム全体の最大プロセス数

/etc/sysctl.conf: kernel.threads-max

・ユーザ当たりの最大プロセス数

/etc/security/limits.conf: nproc

・ulimit -u

Ubuntuの特殊ディレクトリ名を変更する


アプリケーションが日本語対応してくれるのは有難いけど、日本語ディレクトリ名はいや。
次のファイルを編集する。中身は見れば分かる。

    ~/.config/user-dirs.dirs

変更後の設定に合わせてmv/mkdir/rmを実行。

git diffのpagerで日本語文字化け対策

LANG=ja_JP.UTF8な環境で、

git config --global core.pager "nkf -w | LESSCHARSET=utf-8 less"

LESSCHARSET=utf-8 less は lv でも良い。

gitのコミットメール設定

push時にメール送信する。

hooks/post-receive:

#!/bin/sh
sh /usr/local/bin/post-receive-email $*


以下、オリジナルからSubject内容を変更したもの。

/usr/local/bin/post-receive-email:

#!/bin/sh
#
# Copyright (c) 2007 Andy Parkins
#
# An example hook script to mail out commit update information.  This hook
# sends emails listing new revisions to the repository introduced by the
# change being reported.  The rule is that (for branch updates) each commit
# will appear on one email and one email only.
#
# This hook is stored in the contrib/hooks directory.  Your distribution
# will have put this somewhere standard.  You should make this script
# executable then link to it in the repository you would like to use it in.
# For example, on debian the hook is stored in
# /usr/share/doc/git-core/contrib/hooks/post-receive-email:
#
#  chmod a+x post-receive-email
#  cd /path/to/your/repository.git
#  ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive
#
# This hook script assumes it is enabled on the central repository of a
# project, with all users pushing only to it and not between each other.  It
# will still work if you don't operate in that style, but it would become
# possible for the email to be from someone other than the person doing the
# push.
#
# Config
# ------
# hooks.mailinglist
#   This is the list that all pushes will go to; leave it blank to not send
#   emails for every ref update.
# hooks.announcelist
#   This is the list that all pushes of annotated tags will go to.  Leave it
#   blank to default to the mailinglist field.  The announce emails lists
#   the short log summary of the changes since the last annotated tag.
# hooks.envelopesender
#   If set then the -f option is passed to sendmail to allow the envelope
#   sender address to be set
# hooks.emailprefix
#   All emails have their subjects prefixed with this prefix, or "[SCM]"
#   if emailprefix is unset, to aid filtering
#
# Notes
# -----
# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
# give information for debugging.
#

# ---------------------------- Functions

#
# Top level email generation function.  This decides what type of update
# this is and calls the appropriate body-generation routine after outputting
# the common header
#
# Note this function doesn't actually generate any email output, that is
# taken care of by the functions it calls:
#  - generate_email_header
#  - generate_create_XXXX_email
#  - generate_update_XXXX_email
#  - generate_delete_XXXX_email
#  - generate_email_footer
#
generate_email()
{
    # --- Arguments
    oldrev=$(git rev-parse $1)
    newrev=$(git rev-parse $2)
    refname="$3"

    # --- Interpret
    # 0000->1234 (create)
    # 1234->2345 (update)
    # 2345->0000 (delete)
    if expr "$oldrev" : '0*$' >/dev/null
    then
        change_type="create"
    else
        if expr "$newrev" : '0*$' >/dev/null
        then
            change_type="delete"
        else
            change_type="update"
        fi
    fi

    # --- Get the revision types
    newrev_type=$(git cat-file -t $newrev 2> /dev/null)
    oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
    case "$change_type" in
    create|update)
        rev="$newrev"
        rev_type="$newrev_type"
        ;;
    delete)
        rev="$oldrev"
        rev_type="$oldrev_type"
        ;;
    esac

    # The revision type tells us what type the commit is, combined with
    # the location of the ref we can decide between
    #  - working branch
    #  - tracking branch
    #  - unannoted tag
    #  - annotated tag
    case "$refname","$rev_type" in
        refs/tags/*,commit)
            # un-annotated tag
            refname_type="tag"
            short_refname=${refname##refs/tags/}
            ;;
        refs/tags/*,tag)
            # annotated tag
            refname_type="annotated tag"
            short_refname=${refname##refs/tags/}
            # change recipients
            if [ -n "$announcerecipients" ]; then
                recipients="$announcerecipients"
            fi
            ;;
        refs/heads/*,commit)
            # branch
            refname_type="branch"
            short_refname=${refname##refs/heads/}
            ;;
        refs/remotes/*,commit)
            # tracking branch
            refname_type="tracking branch"
            short_refname=${refname##refs/remotes/}
            echo >&2 "*** Push-update of tracking branch, $refname"
            echo >&2 "***  - no email generated."
            exit 0
            ;;
        *)
            # Anything else (is there anything else?)
            echo >&2 "*** Unknown type of update to $refname ($rev_type)"
            echo >&2 "***  - no email generated"
            exit 1
            ;;
    esac

    # Check if we've got anyone to send to
    if [ -z "$recipients" ]; then
        case "$refname_type" in
            "annotated tag")
                config_name="hooks.announcelist"
                ;;
            *)
                config_name="hooks.mailinglist"
                ;;
        esac
        echo >&2 "*** $config_name is not set so no email will be sent"
        echo >&2 "*** for $refname update $oldrev->$newrev"
        exit 0
    fi

    # Email parameters
    # The email subject will contain the best description of the ref
    # that we can build from the parameters
#    describe=$(git describe $rev 2>/dev/null)
#    if [ -z "$describe" ]; then
#        describe=$rev
#    fi

    committer=$(git rev-list --pretty=format:%an ${rev}^..${rev} | tail -n 1)

    generate_email_header

    # Call the correct body generation function
    fn_name=general
    case "$refname_type" in
    "tracking branch"|branch)
        fn_name=branch
        ;;
    "annotated tag")
        fn_name=atag
        ;;
    esac
    generate_${change_type}_${fn_name}_email

    generate_email_footer
}

generate_email_header()
{
    # --- Email (all stdout will be the email)
    # Generate header

    cat <<-EOF
    To: $recipients
    Subject: ${emailprefix} $short_refname ${change_type}d by ${committer}
    X-Git-Refname: $refname
    X-Git-Reftype: $refname_type
    X-Git-Oldrev: $oldrev
    X-Git-Newrev: $newrev

    The $refname_type, $short_refname has been ${change_type}d
    EOF
}

generate_email_footer()
{
    SPACE=" "
    cat <<-EOF


    hooks/post-receive
    --${SPACE}
    EOF
}

# --------------- Branches

#
# Called for the creation of a branch
#
generate_create_branch_email()
{
    # This is a new branch and so oldrev is not valid
    echo "        at  $newrev ($newrev_type)"
    echo ""

    echo $LOGBEGIN
    # This shows all log entries that are not already covered by
    # another ref - i.e. commits that are now accessible from this
    # ref that were previously not accessible
    # (see generate_update_branch_email for the explanation of this
    # command)
    git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
    git rev-list --pretty --stdin $newrev
    echo $LOGEND
}

#
# Called for the change of a pre-existing branch
#
generate_update_branch_email()
{
    # Consider this:
    #   1 --- 2 --- O --- X --- 3 --- 4 --- N
    #
    # O is $oldrev for $refname
    # N is $newrev for $refname
    # X is a revision pointed to by some other ref, for which we may
    #   assume that an email has already been generated.
    # In this case we want to issue an email containing only revisions
    # 3, 4, and N.  Given (almost) by
    #
    #  git rev-list N ^O --not --all
    #
    # The reason for the "almost", is that the "--not --all" will take
    # precedence over the "N", and effectively will translate to
    #
    #  git rev-list N ^O ^X ^N
    #
    # So, we need to build up the list more carefully.  git rev-parse
    # will generate a list of revs that may be fed into git rev-list.
    # We can get it to make the "--not --all" part and then filter out
    # the "^N" with:
    #
    #  git rev-parse --not --all | grep -v N
    #
    # Then, using the --stdin switch to git rev-list we have effectively
    # manufactured
    #
    #  git rev-list N ^O ^X
    #
    # This leaves a problem when someone else updates the repository
    # while this script is running.  Their new value of the ref we're
    # working on would be included in the "--not --all" output; and as
    # our $newrev would be an ancestor of that commit, it would exclude
    # all of our commits.  What we really want is to exclude the current
    # value of $refname from the --not list, rather than N itself.  So:
    #
    #  git rev-parse --not --all | grep -v $(git rev-parse $refname)
    #
    # Get's us to something pretty safe (apart from the small time
    # between refname being read, and git rev-parse running - for that,
    # I give up)
    #
    #
    # Next problem, consider this:
    #   * --- B --- * --- O ($oldrev)
    #          \
    #           * --- X --- * --- N ($newrev)
    #
    # That is to say, there is no guarantee that oldrev is a strict
    # subset of newrev (it would have required a --force, but that's
    # allowed).  So, we can't simply say rev-list $oldrev..$newrev.
    # Instead we find the common base of the two revs and list from
    # there.
    #
    # As above, we need to take into account the presence of X; if
    # another branch is already in the repository and points at some of
    # the revisions that we are about to output - we don't want them.
    # The solution is as before: git rev-parse output filtered.
    #
    # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N
    #
    # Tags pushed into the repository generate nice shortlog emails that
    # summarise the commits between them and the previous tag.  However,
    # those emails don't include the full commit messages that we output
    # for a branch update.  Therefore we still want to output revisions
    # that have been output on a tag email.
    #
    # Luckily, git rev-parse includes just the tool.  Instead of using
    # "--all" we use "--branches"; this has the added benefit that
    # "remotes/" will be ignored as well.

    # List all of the revisions that were removed by this update, in a
    # fast forward update, this list will be empty, because rev-list O
    # ^N is empty.  For a non fast forward, O ^N is the list of removed
    # revisions
    fast_forward=""
    rev=""
    for rev in $(git rev-list $newrev..$oldrev)
    do
        revtype=$(git cat-file -t "$rev")
        echo "  discards  $rev ($revtype)"
    done
    if [ -z "$rev" ]; then
        fast_forward=1
    fi

    # List all the revisions from baserev to newrev in a kind of
    # "table-of-contents"; note this list can include revisions that
    # have already had notification emails and is present to show the
    # full detail of the change from rolling back the old revision to
    # the base revision and then forward to the new revision
    for rev in $(git rev-list $oldrev..$newrev)
    do
        revtype=$(git cat-file -t "$rev")
        echo "       via  $rev ($revtype)"
    done

    if [ "$fast_forward" ]; then
        echo "      from  $oldrev ($oldrev_type)"
    else
        #  1. Existing revisions were removed.  In this case newrev
        #     is a subset of oldrev - this is the reverse of a
        #     fast-forward, a rewind
        #  2. New revisions were added on top of an old revision,
        #     this is a rewind and addition.

        # (1) certainly happened, (2) possibly.  When (2) hasn't
        # happened, we set a flag to indicate that no log printout
        # is required.

        echo ""

        # Find the common ancestor of the old and new revisions and
        # compare it with newrev
        baserev=$(git merge-base $oldrev $newrev)
        rewind_only=""
        if [ "$baserev" = "$newrev" ]; then
            echo "This update discarded existing revisions and left the branch pointing at"
            echo "a previous point in the repository history."
            echo ""
            echo " * -- * -- N ($newrev)"
            echo "            \\"
            echo "             O -- O -- O ($oldrev)"
            echo ""
            echo "The removed revisions are not necessarilly gone - if another reference"
            echo "still refers to them they will stay in the repository."
            rewind_only=1
        else
            echo "This update added new revisions after undoing existing revisions.  That is"
            echo "to say, the old revision is not a strict subset of the new revision.  This"
            echo "situation occurs when you --force push a change and generate a repository"
            echo "containing something like this:"
            echo ""
            echo " * -- * -- B -- O -- O -- O ($oldrev)"
            echo "            \\"
            echo "             N -- N -- N ($newrev)"
            echo ""
            echo "When this happens we assume that you've already had alert emails for all"
            echo "of the O revisions, and so we here report only the revisions in the N"
            echo "branch from the common base, B."
        fi
    fi

    # The diffstat is shown from the old revision to the new revision.
    # This is to show the truth of what happened in this change.
    # There's no point showing the stat from the base to the new
    # revision because the base is effectively a random revision at this
    # point - the user will be interested in what this revision changed
    # - including the undoing of previous revisions in the case of
    # non-fast forward updates.
    echo ""
    echo "Summary of changes:"
    git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev

    echo ""
    if [ -z "$rewind_only" ]; then
        #echo "Those revisions listed above that are new to this repository have"
        #echo "not appeared on any other notification email; so we list those"
        #echo "revisions in full, below."

        echo ""
        echo $LOGBEGIN
        git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
        git rev-list --pretty --stdin $oldrev..$newrev

        # XXX: Need a way of detecting whether git rev-list actually
        # outputted anything, so that we can issue a "no new
        # revisions added by this update" message

        echo $LOGEND
    else
        echo "No new revisions were added by this update."
    fi
}

#
# Called for the deletion of a branch
#
generate_delete_branch_email()
{
    echo "       was  $oldrev"
    echo ""
    echo $LOGEND
    git show -s --pretty=oneline $oldrev
    echo $LOGEND
}

# --------------- Annotated tags

#
# Called for the creation of an annotated tag
#
generate_create_atag_email()
{
    echo "        at  $newrev ($newrev_type)"

    generate_atag_email
}

#
# Called for the update of an annotated tag (this is probably a rare event
# and may not even be allowed)
#
generate_update_atag_email()
{
    echo "        to  $newrev ($newrev_type)"
    echo "      from  $oldrev (which is now obsolete)"

    generate_atag_email
}

#
# Called when an annotated tag is created or changed
#
generate_atag_email()
{
    # Use git for-each-ref to pull out the individual fields from the
    # tag
    eval $(git for-each-ref --shell --format='
    tagobject=%(*objectname)
    tagtype=%(*objecttype)
    tagger=%(taggername)
    tagged=%(taggerdate)' $refname
    )

    echo "   tagging  $tagobject ($tagtype)"
    case "$tagtype" in
    commit)

        # If the tagged object is a commit, then we assume this is a
        # release, and so we calculate which tag this tag is
        # replacing
        prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)

        if [ -n "$prevtag" ]; then
            echo "  replaces  $prevtag"
        fi
        ;;
    *)
        echo "    length  $(git cat-file -s $tagobject) bytes"
        ;;
    esac
    echo " tagged by  $tagger"
    echo "        on  $tagged"

    echo ""
    echo $LOGBEGIN

    # Show the content of the tag message; this might contain a change
    # log or release notes so is worth displaying.
    git cat-file tag $newrev | sed -e '1,/^$/d'

    echo ""
    case "$tagtype" in
    commit)
        # Only commit tags make sense to have rev-list operations
        # performed on them
        if [ -n "$prevtag" ]; then
            # Show changes since the previous release
            git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
        else
            # No previous tag, show all the changes since time
            # began
            git rev-list --pretty=short $newrev | git shortlog
        fi
        ;;
    *)
        # XXX: Is there anything useful we can do for non-commit
        # objects?
        ;;
    esac

    echo $LOGEND
}

#
# Called for the deletion of an annotated tag
#
generate_delete_atag_email()
{
    echo "       was  $oldrev"
    echo ""
    echo $LOGEND
    git show -s --pretty=oneline $oldrev
    echo $LOGEND
}

# --------------- General references

#
# Called when any other type of reference is created (most likely a
# non-annotated tag)
#
generate_create_general_email()
{
    echo "        at  $newrev ($newrev_type)"

    generate_general_email
}

#
# Called when any other type of reference is updated (most likely a
# non-annotated tag)
#
generate_update_general_email()
{
    echo "        to  $newrev ($newrev_type)"
    echo "      from  $oldrev"

    generate_general_email
}

#
# Called for creation or update of any other type of reference
#
generate_general_email()
{
    # Unannotated tags are more about marking a point than releasing a
    # version; therefore we don't do the shortlog summary that we do for
    # annotated tags above - we simply show that the point has been
    # marked, and print the log message for the marked point for
    # reference purposes
    #
    # Note this section also catches any other reference type (although
    # there aren't any) and deals with them in the same way.

    echo ""
    if [ "$newrev_type" = "commit" ]; then
        echo $LOGBEGIN
        git show --no-color --root -s --pretty=medium $newrev
        echo $LOGEND
    else
        # What can we do here?  The tag marks an object that is not
        # a commit, so there is no log for us to display.  It's
        # probably not wise to output git cat-file as it could be a
        # binary blob.  We'll just say how big it is
        echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
    fi
}

#
# Called for the deletion of any other type of reference
#
generate_delete_general_email()
{
    echo "       was  $oldrev"
    echo ""
    echo $LOGEND
    git show -s --pretty=oneline $oldrev
    echo $LOGEND
}

send_mail()
{
    if [ -n "$envelopesender" ]; then
        /usr/sbin/sendmail -t -f "$envelopesender"
    else
        /usr/sbin/sendmail -t
    fi
}

# ---------------------------- main()

# --- Constants
LOGBEGIN="- Log -----------------------------------------------------------------"
LOGEND="-----------------------------------------------------------------------"

# --- Config
# Set GIT_DIR either from the working directory, or from the environment
# variable.
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
if [ -z "$GIT_DIR" ]; then
    echo >&2 "fatal: post-receive: GIT_DIR not set"
    exit 1
fi

#projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
# Check if the description is unchanged from it's default, and shorten it to
# a more manageable length if it is
#if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
#then
#    projectdesc="UNNAMED PROJECT"
#fi

recipients=$(git config hooks.mailinglist)
announcerecipients=$(git config hooks.announcelist)
envelopesender=$(git config hooks.envelopesender)
#envelopesender="git@local"
#emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
GIT_DIR_NAME=$(cd $GIT_DIR; basename `pwd`)
emailprefix="[${GIT_DIR_NAME}]"

# --- Main loop
# Allow dual mode: run from the command line just like the update hook, or
# if no arguments are given then run as a hook script
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
    # Output to the terminal in command line mode - if someone wanted to
    # resend an email; they could redirect the output to sendmail
    # themselves
    PAGER= generate_email $2 $3 $1
else
    while read oldrev newrev refname
    do
        generate_email $oldrev $newrev $refname | send_mail
    done
fi