win
iPhone 用アプリ「ExifSafe」について
ソースコード: GitHub - exif
また、このコードを実際に使って次のような iPhone 向けアプリを試作してみました。
「ExifSafe」という名前をつけています。
次の場所で公開しています。興味のある方はお試し下さい。
※「写真.jpg」のような識別性に乏しいシステム既定の添付名の不便さを補うもの
iTunes App Store -ExifSafeソースコード: GitHub - ExifSafe
2018年6月追記:諸般の事情により App Store 上の本アプリの更新は今後当面見送らせて頂きます。本アプリをご利用下さった皆様に謹んで御礼申し上げます。
(tanabe)
Exif データにアクセスするコードを自作してみる
先日ちょっとしたアプリを書いていた折に JPEG ファイル内の Exif データを参照する処理が必要になりました。初めての機会だったのでやり方を調べたところ 開発環境向けに用意されたこれこれのライブラリを使うのが常道とのことで、リファレンスを読みながらそういうコードを書きました。プログラムは期待通りに動作しその時はそれで特にどうと言うこともなかったのですが、後日別のプラットフォームで Exif データを利用するアイディアを思いつき、その環境でまた別のライブラリの使い方など調べているうちに何だか面倒になりました。
ここでのターゲットはあくまでも一介のデータ形式です。既存のソフトウェア資産を利用することは生産性の上でも効率の面でも間違った選択ではないものの、「普通のファイルに記録されたデータ」にアクセスするための道具立てにいろいろ左右されることが煩わしく感じられたのです。Exif の規格は公開されているため仕様を確認して必要なコードを自作すれば今後この形式を既存のライブラリごしに「ブラックボックス」として扱う必要はなくなるはずで、また、自作のコードであれば拡張や応用の融通が利きやすく必要なら別のプログラム言語への移植も楽でしょう。
デジタルカメラという今日ではごく身近な道具を通じてその存在を知りながら中身をずっと知らずにいた Exif のしくみに触れることはそれ自体も興味ぶかい経験でした。その一端をご紹介します。C 言語で試作したソースコード一式を記事の最後に掲載しています。
Exif の仕様
まず Exif のデータ形式の仕様をできるだけコンパクトに整理してみます。もっとも詳しく正確なのはもちろん公式の規格書ですから不明な点があればそこでの記述を参照して下さい。
規格書
電子情報技術産業協会 (JEITA) ホームページ上の「JEITA 規格総合検索」フォームから、「規格名」に "Exif" を指定して検索を実行すると「JEITA CP-3451C デジタルスチルカメラ用 画像ファイルフォーマット規格 Exif 2.3」を電子文書として閲覧できます。 ※ 2013年8月31日現在
全体の構造
下の図は規格書をもとに JPEG ファイル中の Exif データの構造をまとめてみたものです。要点をピックアップしてみます。
タグフィールドの扱い方
どこにどのような情報が配置されているかは上の図を追えば把握できるでしょう。構造を順に辿って最後に到着するのは具体的なデータの格納された末端のタグフィールドです。もちろんこれらが最も重要な要素であり、ここではタグの値にアクセスする方法を説明します。
フィールドの構成
一件のタグフィールドには以下の内容が含まれます- タグ 番号 2 バイト 所定のタグを識別するための番号
- タイプ 2 バイト タグの値のデータタイプを示す
- カウント 4 バイト 値の個数
- オフセット 4 バイト 値の格納された位置
タグ名称 Field Name タグ番号(Hex) タイプ カウント Exif バージョン ExifVersion 9000 UNDEFINED 4 色空間情報 ColorSpace A001 SHORT 1 : : : : : ■ Exif バージョン ExifVersion 本規格での対応バージョンを示す。このフィールドが存在しなければ,本規格に準拠していない と判断される。本規格に準拠する場合には,4Byte の ASCII "0230" を記録しなければならない。 Type が UNDEFINED のため最後に NULL は記録してはならない。 Tag = 36864 (9000.H) Type = UNDEFINED Count = 4 Default = "0230" ■ : :
タイプについて
タグの値には以下のタイプがあります。値の取得方法は後述のカウント・オフセットのルールとの組合せで決まります。
カウントについて
カウントは「値の個数」を示します。「バイト数の合計」を表すものではありません。以下に例を示します。
オフセットについて
4 バイトのオフセット領域にはタグの値が記録されている位置(上図中の★が起点)が保持されています。ただし、値が 4 バイト以内で表現できる場合はその値がオフセット領域に直書きされます。4 バイト以内の値の例を以下に示します。
さらに、値の長さが 4 バイト未満の場合、値はオフセット領域に「左詰め」で格納されます。以下の要領です。
■ BYTE タイプの値 1
0 1 2 3 ┌─┬─┬─┬─┐ │01│00│00│00│ データのバイトオーダーがビッグエンディアンの場合 └─┴─┴─┴─┘ ┌─┬─┬─┬─┐ │01│00│00│00│ データのバイトオーダーがリトルエンディアンの場合 └─┴─┴─┴─┘
■ SHORT タイプの値 1, 3
0 1 2 3 ┌─┬─┬─┬─┐ │00│01│00│03│ データのバイトオーダーがビッグエンディアンの場合 └─┴─┴─┴─┘ ┌─┬─┬─┬─┐ │01│00│03│00│ データのバイトオーダーがリトルエンディアンの場合 └─┴─┴─┴─┘
データの実例
図は Exif セグメントを含むある JPEG ファイル冒頭部分の HEX ダンプイメージです。上述のデータ構造とデータ形式を踏まえてここから読み取られる 0th IFD の内容を併記しています。
試作したコード
C 言語で書いたソースコード一式を以下で公開しています。
Windows 用実行形式 (x86)
- 現在のバージョンには以下の機能があります
- 所定の JPEG ファイルの Exif セグメント中の一部またはすべての IFD の内容をダンプ表示
- 指定された IFD タイプ+タグ番号に該当するタグフィールドの情報を取得
- 所定の JPEG ファイルから Exif セグメントを除去した新しい JPEG ファイルを出力
- exif.c, exif.h の二本が実体で sample_main.c は関数呼び出しのサンプルです。
サンプルプログラムのビルドは以下の要領です- gcc の場合: gcc -o exif sample_main.c exif.c
- Microsoft Visual C++ の場合: cl.exe /o exif sample_main.c exif.c
- 標準ライブラリ関数のみを使っているため多くの環境での利用が可能です。手元では以下の組合せでの動作を確認しました
- Windows XP 32bit + Microsoft Visual C++ でビルドした 32bit バイナリ
- Windows 7 64bit + Microsoft Visual C++ でビルドした 64bit バイナリ
- Redhat Linux 32bit + 32bit 版 gcc でビルドしたバイナリ
- Mac OS X 64bit + 64bit 版 gcc でビルドしたバイナリ
- 動作確認用の test.jpg をサンプルプログラムで処理した結果を以下に引用します
$ ./exif test.jpg [test.jpg] createIfdTableArray: result=4 {0TH IFD} - Make: [Apple] - Model: [iPod touch] - Orientation: 1 - XResolution: 72/1 - YResolution: 72/1 - ResolutionUnit: 2 - Software: [6.1.4] - DateTime: [2013:09:01 09:49:00] - YCbCrPositioning: 1 - ExifIFDPointer: 206 - GPSInfoIFDPointer: 576 {EXIF IFD} - ExposureTime: 1/30 - FNumber: 12/5 - ExposureProgram: 2 - PhotographicSensitivity: 400 - ExifVersion: 0 2 2 1 - DateTimeOriginal: [2013:09:01 09:49:00] - DateTimeDigitized: [2013:09:01 09:49:00] - ComponentsConfiguration: 0x01 0x02 0x03 0x00 - ShutterSpeedValue: 4035/821 - ApertureValue: 4845/1918 - BrightnessValue: 2234/1113 - MeteringMode: 5 - Flash: 32 - FocalLength: 77/20 - FlashPixVersion: 0 1 0 0 - ColorSpace: 1 - PixelXDimension: 960 - PixelYDimension: 720 - SensingMethod: 2 - ExposureMode: 0 - WhiteBalance: 0 - FocalLengthIn35mmFormat: 32 - SceneCaptureType: 0 {GPS IFD} -- GPS 経由で得られた撮影地点の座標情報 - GPSLatitudeRef: [S] - GPSLatitude: 69/1 17/100 0/1 - GPSLongitudeRef: [E] - GPSLongitude: 39/1 35/100 0/1 - GPSAltitudeRef: 0 - GPSAltitude: 6151/470 - GPSTimeStamp: 0/1 48/1 3921/100 {1ST IFD} -- 埋め込みサムネイルの情報 〜 サムネイル画像データへ続く - Compression: 6 - XResolution: 72/1 - YResolution: 72/1 - ResolutionUnit: 2 - JPEGInterchangeFormat: 840 - JPEGInterchangeFormatLength: 8648 0th IFD : Model = [iPod touch] Exif IFD : DateTimeOriginal = [2013:09:01 09:49:00] GPS IFD : GPSLatitude = 69/1 17/100 0/1 removeExifSegmentFromJPEGFile: result=1 $ ./exif _noexif.jpg [_noexif.jpg] does not seem to contain the Exif segment.
(tanabe)
GCM で Wake On WAN
Android 端末を一台 LAN に接続した状態で待機させておけば、Wake On LAN の設定とアプリへの登録を済ませた任意の PC をルータの設定に手を加えることなく外から起動することができます。起動さえできれば PC の遠隔操作そのものには TeamViewer など既存のソリューションを柔軟に利用できますね。
このところ出先から自宅 PC へのアクセスが必要となるケースが増えていたもののルータに穴を開けるのはあまり気が進まずやむなく PC を起ち上げっぱなしにしたりしていたのですが、不経済な上にこれからの夏場には密室での連続稼動に一抹の不安がありました。興味のある方はお試し下さい。
アプリ本体: Google Play - WakeOnLan GCM
使い方
以下に使い方を簡単に控えます
対象 PC をアプリへ登録
端末を GCM へ登録〜遠隔操作用 HTML フォームの生成と使用
(tanabe)
QR コードを機器間での秘密情報の輸送に利用する試み
QR コードのもうひとつの利点
QR コードはインターネットアドレスなど所定のテキストデータを簡単に機器に取り込むための手段として広く利用されています。印刷物に限らずさまざまな媒体で扱えるのも便利ですね。
先日、公開鍵暗号を使う Android アプリを試作していた折に、自分の端末 A で生成した秘密鍵を手持ちの別の端末 B へ安全に輸送する手段の検討が必要になりました。やり方はいろいろありそうですが、よりシンプルな方法をとあれこれ考えている内に、机上に無造作に置いたチラシの QR コードにふと目が留まりました。
QR コードからのデータ取り込みは通常光学的に行われます。このことは電子機器の視覚を利用したデータ通信と考えることができるでしょう。第三者による情報窃取の可能性がしばしば問題となるネットワーク通信とは異なり、「目」を使っての情報伝達には物理的に介入することが難しいため、デジタルデータを画像で表現する QR コードは秘匿性の求められる局面での通信手段としても有用と考えられます。
今回の話題はふたつの端末の間でのデータ授受なので、送り側の端末で QR コードの生成と画面表示を行い、受け側の端末でそれを読み取ればよさそうです。情報の受け渡しさえできればコードを残しておく必要はないので QR コードは常に動的に生成し使い捨てとすればよいでしょう。
留意すべき点
一件の QR コードに格納できるデータの量には上限があります。QR コードの開発元である株式会社デンソーウェーブ様のサイトの記事から概要を示す表を以下に引用します。
コードの大きさ | 21セル×21セル〜177セル×177セル(4セル /辺毎に増加) | |
情報の種類及び
情報量(混在も可) QRコードの情報量とバージョンについて |
数字 英数字 8ビットバイト(バイナリ) 漢字 |
最大7,089文字 最大4,296文字 最大2,953文字 最大1,817文字 |
誤り訂正能力 (データ復元機能) 誤り訂正能力について |
レベルL レベルM レベルQ レベルH |
コードワードの約 7% が復元可能 コードワードの約 15% が復元可能 コードワードの約 25% が復元可能 コードワードの約 30% が復元可能 |
コード連結機能 | 最大16分割 (細長いエリアなどへの印刷) |
下の QR コードには半角英数記号 1675 文字を格納しています。これを一般的なサイズ・解像度の PC のモニタで表示した状態であればおそらく多くの携帯端末の QR コードリーダーで読み取ることが可能でしょう。しかし、スマートフォンの小さな画面にこれを表示した状態だと読み取りに失敗するリーダーが出てくるかもしれません。また、一般に情報の密度が高くなるほど読み取りにより多くの時間がかかるため、たとえ成功しても別のストレスが残る可能性もあります。
そういった事情を考え合わせると、今回のような目的で QR コードを利用する場合は、一件のコードに強引に多くのデータを押し込むのではなく、複数のコードに分けて利用することを前提に余裕を持ってデータを扱うほうが賢明でしょう。
実は上の表にも記述があるように、QR コードには一件のデータを最大 16 件のコードに分割して格納することの可能な「コード連結」という仕様があります。次の記事には分割 QR コードの実例が掲載されています。 「IT4206,QRコードの連結機能に対応していますか。」 - 株式会社エイポック様 公式サイトより -
残念ながらすべてのリーダー・ライブラリがこの機能に対応しているわけではなく、現在手元で使っているメジャーな ZXing のライブラリも未対応のようです。しかし、今回はリーダーだけではなくライターも自作することが前提なので、両者間で整合性のとれる内容で独自の分割プロトコルを用意すれば事足りると判断しました。また、二台の端末を操作しながら複数のコードを順番に処理していくのは想像するだけで非常に面倒なので、両者の連携にネットワーク通信を併用し、リーダーが正しくコードを読み取ったらライターが自動的に次のコードを表示することにしました。
ちなみに、先日の記事で操作性の良い QR コードリーダーを自作するために行った取り組みを紹介しましたが、そのきっかけは今回の一連の話題にありました。リーダーを自分で実装すれば上記のような細かい取り回しも柔軟に実現できるわけですね。QR コードの読み取りが主目的ではないアプリへ補助機能としてリーダー処理を組み込むことにはちょっと新鮮な印象があります。
試作と実験
そんなわけで次のような QR コードライターアプリと QR コードリーダーアプリを作ってみました。
デモ動画
動作の様子です。奥の端末で QR コードライター、手前の端末でリーダーを動かしています。
リソース
今回試作したソフトウェア一式を公開します。興味のある方はお試し下さい。
QR コードライター・リーダーのソースコード
MyQRCodeReaderEx - github
QR コードライターのビルドずみ apk
http://dsas.blog.klab.org/data/qr_secret/QR_writer.apk
QR コードリーダーのビルドずみ apk
http://dsas.blog.klab.org/data/qr_secret/MyQRCodeReaderEx.apk
(tanabe)
「ZXing QR コードスキャナー」の内部処理を追う
このライブラリの優秀さはスマートフォンでバーコード/QR コードを処理する際の実質標準の座にある状況からも裏打ちされていますが、ZXing スキャナの「使いやすさ」は本ライブラリの性能のみに依るものではなくアプリの実装に大きく支えられています。ZXing スキャナのソースコードは公開されているので、それに学べば本家と同等の使いやすさを備えた QR コードリーダーの自作が可能となるはずですね。操作性の良いリーダーを自作できるのであれば QR コード読み取り機能をアプリへ組み込む際にわざわざ ZXing スキャナをインテントで呼び出す必要はなく、独自の処理を柔軟に組み込むこともできるでしょう。また、上記ライブラリを知り尽くした ZXing 謹製スキャナのコードはライブラリの性能を最大限に引き出すための最良のお手本となりそうです。zxing Multi-format 1D/2D barcode image processing library with clients for Android, Java
ZXing スキャナのソースコードを正面から分析した情報はほとんど見当たらないようですが、手元での調査を通じて得られた情報と、そのエッセンスを小さくまとめた形で試作した自作リーダーのリソース一式を公開します。なお、この記事では ZXing 2.1 Release 中の zxing-2.1/android/ 配下のソースコードを対象としています。
注目すべき要素
なぜ ZXing スキャナは QR コードリーダーとして使いやすいのでしょう?その要素こそが実装を調べる上で注目すべきポイントとなるでしょう。特長をみっつピックアップしてみます。
- コードの認識が早い
ターゲットにレンズを向けてじっとしていればすぐに認識が完了する - 対象とするコードをファインダ枠内に留める以外の操作が不要
フォーカスを合わせたり画面をタップするといった操作をする必要がない - コード識別パターン検出位置を示すポイントが描画される
プレビュー表示にコード識別パターンを発見した位置を示すポイント (ResultPoint) がリアルタイムで描画されるため端末の位置・角度を加減し易い
ソースコードの追跡とまとめ
コード読みの途中で迷子にならないための道具としてホワイトボード代わりにエクセルシートを使いました。あくまでも作業用なので決して見た目の良いものではありませんが記録としてそのまま掲載します。
以下、ソースコードから得られた情報を整理してみます。
- プレビュー表示中に裏側で走っている太い処理の内容
※カメラには 2 秒ごとに autoFocus() が適用される- 現在プレビュー表示中のフレームイメージを取得
- 取得したイメージをバックグラウンドスレッドへ渡す
- バックグラウンドスレッドはイメージを ZXing ライブラリ処理に渡す
- ライブラリはコードの検出過程で QR コード画像の識別パターンである可能性のある箇所(ResultPoint)を見つけるとその座標を UI 側へ逐次通知する
- ResultPoint を受け取とった UI 側はそれを表示中のプレビュー画面に重ねて描画する
- ライブラリ側処理が今回のフレームイメージから QR コードを検出しなかった場合、1. からの処理が繰り返される
- 重要なクラスとその処理の概要
- com.google.zxing.client.android- CaptureActivity
QR コードスキャナーの Activity クラス - CaptureActivityHandler
CaptureActivity のハンドラ。DecodeThread インスタンスを生成し DecodeHandler からライブラリ処理による QR コード認識結果を受け取る。コードが認識されなかった場合はこのクラスが CameraManager.requestPreviewFrame() を叩くことで、次のフレームイメージ取得〜QR コード認識という全体のループを継続させる - DecodeThread
プレビュー中のフレームイメージから QR コードを検出するための手続きを連続的に繰り返すバックグラウンドスレッド - DecodeHandler
DecodeThread のハンドラ。ZXing ライブラリの MultiFormatReader クラスの QR コード検出メソッドを呼び、認識結果を CaptureActivityHandler へ伝える - ViewfinderView
View の継承クラス。プレビュー画面の描画、ファインダ矩形上に捕捉したイメージについての識別パターン検出点(ResultPoint)の描画を onDraw() メソッド内で実施 - ViewfinderResultPointCallback
ZXing ライブラリ内に宣言のある「com.google.zxing.ResultPointCallback」インターフェイスの実装クラス。DecodeThread 経由でライブラリ側処理へ登録され、ライブラリが ResultPoint 認識時にこのクラスの foundPossibleResultPoint() メソッドをコールバックする。同メソッドは ViewfinderView クラスへ ResultPoint を伝達する
- CameraManager
カメラのオープン・クローズや PreviewCallback へのフレームイメージ取得指示など - PreviewCallback
Camera.PreviewCallback の実装クラス。onPreviewFrame() 発生時に DecodeHandler へキャプチャイメージを渡す - AutoFocusManager
バックグラウンドで 2 秒ごとにカメラに autoFocus() を適用 - CameraConfigurationManager
カメラまわりの解像度や各種パラメータの取得・設定
- CaptureActivity
- 使いやすさを支えているもの
- 「コードの認識が早い」
→ プレビュー表示中、コードを検出するまで絶えずフレームイメージの取得とバックグラウンドスレッドでの分析を繰り返している - 「対象とするコードをファインダ枠内に留める以外の操作が不要」
→ カメラへのオートフォーカスの適用を効果的に利用している - 「コード識別パターン検出位置を示すポイントが描画される」
→ 検出位置を直ちに通知するためにライブラリの深部に用意されたコールバック機構と UI への描画処理を適切に組み合わせて使用している
- 「コードの認識が早い」
リーダーの試作
以上のように、全体像が見えてしまえば ZXing スキャナの操作性を支えているのは意外なほどシンプルなしくみであることがわかります。それを自作のコードに組み込むことは難しくなさそうですね。そこで ZXing スキャナの実装に倣いつつできるだけ短いコードでざっくりとリーダーを作ってみることにしました。認識結果はダイアログ表示のみとしています。動作確認は一部の環境でのみ行っており、不具合があれば適宜手を入れて下さい。
ソースコードの一覧と概要を以下に示します。
- MyActivity.java
本アプリの Activity クラス。implements SurfaceHolder.Callback, Handler.Callback, Camera.PreviewCallback - MyCameraConfigurationManager.java
ZXing スキャナの CameraConfigurationManager より。カメラパラメータの設定を決め打ちに - MyDecodeThread.java
ZXing スキャナの DecodeThreadより。対応コードフォーマットを決め打ちに - MyDecodeHandler.java
ZXing スキャナの DecodeHandlerより。コード認識成功時に MyActivity へ Bitmap は送らず検出したテキストの情報のみに - MyFinderView.java
ZXing スキャナの ViewfinderView および ViewfinderResultPointCallback より。オリジナルの ViewfinderView はプレビュー画面全体を覆っているが単純化のためここではファインダ矩形と一対一に変更。ResultPoint が 4 以上になるとファインダ内に「Warning!」と表示
ビルドずみの apk はここにあります
http://dsas.blog.klab.org/data/zxing_qr_scanner/MyQRCodeReader.apk
(tanabe)
親指サイズの USB 赤外線リモコンが面白い
昨年末、調べごとをしていた時にちょっと気になる商品が目に留まりました。 株式会社ビット・トレード・ワン 様の「USB 接続 赤外線リモコン KIT」という製品です。
特徴をざっくりまとめてみるとこんな感じです。
PC から制御可能な学習型赤外線リモコンといえば 2006 年の発売以来ロングセラーを続ける PC-OP-RS1 がとても有名ですが、このキットは昨年(2012年)発売された製品とのことで、ソフトウェア要素の多くがオープンであることに魅力を感じました。一方で 公式フォーラムを覗いてみるとエアコンまわりをはじめいろいろ制約もあるようで、それをどう考えるかは価格とのバランスの解釈次第でしょう。そういった話題も含めて好奇心をそそられ、年明け早々に入手しました。画像は「フリスク」と並べてみた様子です。
- [パソコンから家庭用機器をリモコン操作]、[リモコンでパソコンを操作] の2つの機能を持つ
- 赤外線送信用のライブラリやツール・ファームウェアのソースコードが公開されている
- 家電協/ NEC/ SONY の各リモコンコードフォーマットに対応
- 某清涼菓子のケースにぴったり収まるサイズ
- キットは 1,680 円、組立ずみ製品でも 2,480 円と低価格
- PC 側対応 OS はWindows 7, Vista, XP
PC とは A−miniB タイプの USB ケーブルで接続。Windows 上でヒューマンインターフェイスデバイスとして認識され標準の HID ドライバが使用されます。
とりあえず使ってみると・・
さっそく専用の 送信用設定ツール に手元のいくつかの機器のリモコンコードを学習させ本キットから信号を出力し、それを元の機器が認識できるか否かを試してみると下の図の結果となりました。 続きを読む
Android の GCM をプライベートな目的に使う
自分の端末を遠隔操作
Android 界隈は依然にぎやかで次々に新しい製品が発売されています。そのため複数の端末を持っている人も少なくないでしょう。まだまだ使える端末を遊ばせておくのはもったいないので、これを外出中の自宅の監視カメラとして使うことにしました。 端末を室内の対象物に向けて固定しておき、出先や仕事場から GCM 経由で端末へメッセージを送出、それをトリガーにアプリが撮影したスナップを Dropbox 経由で確認します。
シンプルな実装の割に結構役に立っています。 アプリ本体とソースコードを以下で公開しています。
RemoteWand - github
処理の流れ
端末の登録
遠隔操作の対象とする端末上で「RemoteWand」を起動して「登録」ボタンを押下すると GCM サーバへ端末が登録されトリガー発信用のフォームを含む HTML ファイルの添付された自分あてのメールが生成されます。
トリガーの送出〜撮影
PC やスマホのウェブブラウザに上記のフォームをロードし登録時に指定したパスワードを添えてサブミットするとアプリケーションサーバから GCM サーバへ要求が送出され GCM サーバが当該端末へ所定のメッセージをプッシュします。端末にインストールずみの RemoteWand はこのメッセージをトリガーにカメラで静止画の撮影を行います。Dropbox の「カメラアップロード」機能は自動的に撮影画像のアップロードを行うためあらかじめ端末にインストールしておけばリモートで簡単に写真を確認できます。
(tanabe)
Google Drive 上の文書をまとめて別アカウントへ移す方法
先日ちょっとした経緯から「アカウント A の Google Drive 上の Docs 文書・フォルダ群をまとめてアカウント B の環境へコピーするにはどうすればいいか?」というテーマに遭遇しました。次のみっつの条件つきです。
今回はこの話題について考えた内容と、道具として作成したコードを紹介します。 続きを読む
Android のプッシュ通知用コネクションに関するメモ(補記)
この記事の文中に、上記の持続接続が「プッシュ通知以外の用途にも利用されている様子でありそちらもおって調査したい」 と注釈を添えています。今回はその内容と関連する話題を控えます。DSAS 開発者の部屋 : 「Android のプッシュ通知用コネクションに関するメモ」
まとめ
端末と mtalk.google.com:5228 との間の接続はプッシュ通知以外に次の用途で使用される
- 他にもあるかも?
- 非公開の内部仕様につき今後変更される可能性も
Google Talk について
$ dig mtalk.google.com
; <<>> DiG 9.2.1 <<>> mtalk.google.com
:
;; ANSWER SECTION:
mtalk.google.com. 81547 IN CNAME mobile-gtalk.l.google.com.
mobile-gtalk.l.google.com. 291 IN A 74.125.31.188
:
Google Play ページからのプッシュインストールについて
mtalk.google.com:5228 に接続できない場合は?
端末上の com.google.process.gapps プロセスが 「mtalk.google.com:5228」 へコネクションを確立できない場合の所作を確認した
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 10.10.0.10:43695 173.194.35.15:80 TIME_WAIT -
tcp 0 0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
tcp 0 0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
tcp 0 1 ::ffff:10.10.0.10:34224 ::ffff:173.194.72.188:5228 SYN_SENT 256/com.google.proc
:
(時間の経過・・・)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 10.10.0.10:43695 173.194.35.15:80 TIME_WAIT -
tcp 0 0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
tcp 0 0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
:
(時間の経過・・・)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 10.10.0.10:43695 173.194.35.15:80 TIME_WAIT -
tcp 0 0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
tcp 0 0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
tcp 0 1 ::ffff:10.10.0.10:32648 ::ffff:173.194.72.188:5228 SYN_SENT 256/com.google.proc
:
Note: If your organization has a firewall that restricts the traffic
to or from the Internet, you'll need to configure it to allow
connectivity with GCM. The ports to open are: 5228, 5229, and 5230.
GCM typically only uses 5228, but it sometimes uses 5229 and 5230.
付録:端末上で拾ってみたパケットの様子
Google Talk
プッシュインストール
(tanabe)
Android のプッシュ通知用コネクションに関するメモ
Android のプッシュ通知機構(GCM, 旧 C2DM)は有用なしくみですが、オープンソースではないソフトウェア要素が関わっているためか内部仕様に近い情報をあまり見かけないのが残念です。手元での観察結果をもとにプッシュ通知で使用されるネットワークコネクションまわりの情報をいくつかまとめてみました。
まとめ
(通常は 5228 番ポートだが 5229, 5230 番ポートが使用される場合もある)
※[A] はプッシュ通知以外の用途にも利用されている様子でありそちらもおって調査したい
端末− Google サーバ間のコネクションについて
コネクションの存在確認
GCM に関する開発者向け情報へアクセスすると次の記述が見つかります。[GCM Architectural Overview | Android Developers] より
Note: If your organization has a firewall that restricts the traffic to or from the Internet, you'll need to configure it to allow connectivity with GCM. The ports to open are: 5228, 5229, and 5230. GCM typically only uses 5228, but it sometimes uses 5229 and 5230.GCM では端末と所定の外部サーバとの接続に通常 5228 番ポートを使うということですね。 さっそく Android 端末実機上で netstat コマンドを実行してみます。
※ここで使用しているのは root 化ずみの実験用端末であり、 各コマンドは Android オリジナルのものではなくBusyBox 版を利用しています。
pid=3547 のプロセスが 「173.194.72.188:5228」との間に TCP コネクションを張っていることがわかります。
# netstat -p Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 10.10.0.10:60272 74.125.235.111:80 ESTABLISHED 6337/berserker.andr tcp 1 0 ::ffff:10.10.0.10:33375 ::ffff:74.125.235.145:443 CLOSE_WAIT 3547/com.google.pro tcp 0 0 ::ffff:10.10.0.10:22 ::ffff:10.10.0.6:3667 ESTABLISHED 6385/dropbear tcp 1 0 ::ffff:10.10.0.10:52412 ::ffff:74.125.235.180:443 CLOSE_WAIT 7318/com.google.and tcp 0 0 ::ffff:10.10.0.10:22 ::ffff:10.10.0.6:3911 ESTABLISHED 6879/dropbear tcp 0 0 ::ffff:10.10.0.10:44984 ::ffff:173.194.72.188:5228 ESTABLISHED 3547/com.google.pro tcp 0 0 ::ffff:10.10.0.10:36091 ::ffff:74.125.235.122:80 ESTABLISHED 6337/berserker.andr Active UNIX domain sockets (w/o servers) Proto RefCnt Flags Type State I-Node PID/Program name Path unix 2 [ ] DGRAM 10725 3445/system_server /data/misc/wifi/sockets/wpa_ctrl_3445-307 unix 2 [ ] DGRAM 10727 3445/system_server /data/misc/wifi/sockets/wpa_ctrl_3445-308 unix 2 [ ] STREAM 1293 103/rild /dev/socket/rild-debug unix 2 [ ] STREAM 1295 103/rild /dev/socket/rild unix 4 [ ] DGRAM 10722 3762/wpa_supplicant /dev/socket/wpa_wlan0 unix 3 [ ] STREAM CONNECTED 17159 108/adbd unix 3 [ ] STREAM CONNECTED 17158 108/adbd :
当該コネクションを保持するプロセスについて
pid=3547 は「com.google.process.gapps」というプロセス。# ps | grep 3547 3547 app_1 0:05 com.google.process.gapps
「com.google.process.gapps」は「Google Services Framework」のプロセス名。
(関連記事)
"Fix for Google services Framework (process com.google.process.gapps) has stopped unexpectedly"
「Google services Framework」の実体は以下のシステムパッケージ。(※ソース未公開)
# ls -l /system/app/GoogleServicesFramework.apk -rw-r--r-- 1 root root 2867063 Feb 6 2012 /system/app/GoogleServicesFramework.apk
接続先サーバについて
「173.194.72.188」に該当するホスト名は「mtalk.google.com」。$ host -v mtalk.google.com Trying "mtalk.google.com" ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30177 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mtalk.google.com. IN A ;; ANSWER SECTION: mtalk.google.com. 85662 IN CNAME mobile-gtalk.l.google.com. mobile-gtalk.l.google.com. 66 IN A 173.194.72.188 Received 79 bytes from 10.10.0.18#53 in 0 ms※2012 年 8 月現在、「mtalk.google.com」の分散先として「173.194.72.188」「74.125.31.188」の 2 サーバの存在を確認
接続維持用の Keep-Alive パケットについて
tcpdump を走らせ 5228 ポートをめぐる応酬を監視。沈黙状態の 15 分ごとに端末から Keep-Alive パケットが送出される様子が見てとれます。# tcpdump -nl -s 256 port 5228 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on wlan0, link-type EN10MB (Ethernet), capture size 256 bytes 13:16:23.504179 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 78:103(25) ack 541win 11236 <nop,nop,timestamp 9585688 3638038533> 13:16:23.560095 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 541:566(25) ack 103 win 274 <nop,nop,timestamp 3638938605 9585688> 13:16:23.560168 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 566 win 11236 <nop,nop,timestamp 9585694 3638938605> 13:31:23.568391 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 103:128(25) ack 566 win 11236 <nop,nop,timestamp 9675694 3638938605> 13:31:23.624242 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 566:591(25) ack 128 win 274 <nop,nop,timestamp 3639838674 9675694> 13:31:23.624371 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 591 win 11236 <nop,nop,timestamp 9675700 3639838674> 13:46:23.637744 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 128:153(25) ack 591 win 11236 <nop,nop,timestamp 9765701 3639838674> 13:46:23.696340 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 591:616(25) ack 153 win 274 <nop,nop,timestamp 3640738749 9765701> 13:46:23.696469 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 616 win 11236 <nop,nop,timestamp 9765707 3640738749> 14:01:23.712443 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 153:178(25) ack 616 win 11236 <nop,nop,timestamp 9855709 3640738749> 14:01:23.770009 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 616:641(25) ack 178 win 274 <nop,nop,timestamp 3641638829 9855709> 14:01:23.770164 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 641 win 11236 <nop,nop,timestamp 9855715 3641638829> 14:16:23.783100 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 178:203(25) ack 641 win 11236 <nop,nop,timestamp 9945716 3641638829> 14:16:23.841258 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 641:666(25) ack 203 win 274 <nop,nop,timestamp 3642538904 9945716> 14:16:23.841349 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 666 win 11236 <nop,nop,timestamp 9945722 3642538904> 14:31:23.853398 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 203:228(25) ack 666 win 11236 <nop,nop,timestamp 10035723 3642538904> 14:31:23.912470 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 666:691(25) ack 228 win 274 <nop,nop,timestamp 3643438980 10035723> 14:31:23.912555 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 691 win 11236 <nop,nop,timestamp 10035729 3643438980>
GCM, C2DM で通知を送ってみる
以下は自作アプリから C2DM, GCM の順に端末へプッシュ通知を送った時の様子です。
いずれも「173.194.72.188:5228」経由で通知が端末へ配信されていることがわかります。
11:49:49.268419 IP 173.194.72.188.5228 > 10.10.0.10.44984: P 974497881:974498015(134) ack 436376500 win 272 <nop,nop,timestamp 1641184578 3415344>
11:49:49.269687 IP 10.10.0.10.44984 > 173.194.72.188.5228: . ack 134 win 11236 <nop,nop,timestamp 3483319 1641184578>
11:49:59.146970 IP 173.194.72.188.5228 > 10.10.0.10.44984: P 134:262(128) ack 1 win 272 <nop,nop,timestamp 1641194459 3483319>
11:49:59.147103 IP 10.10.0.10.44984 > 173.194.72.188.5228: . ack 262 win 11236 <nop,nop,timestamp 3484307 1641194459>
(tanabe)
Android パッケージインストール処理のしくみを追う
まとめ
※PackageInstaller を起動した状態での関連プロセスの例
$ ps USER PID PPID VSIZE RSS WCHAN PC NAME root 1 0 268 180 c009b74c 0000875c S /init root 36 1 812 244 c02181f4 afd0b45c S /system/bin/installd root 33 1 60900 16628 c009b74c afd0b844 S zygote system 62 33 126452 28368 ffffffff afd0b6fc S system_server app_30 372 33 74876 18852 ffffffff afd0c51c S com.android.packageinstaller :※Package Manager Service と installd プロセスの連携用 UNIX ドメインソケット
$ ls -l /dev/socket/installd srw------- system system 2012-07-04 11:43 installd
- Android OS 上でパッケージインストールにもっとも深い関わりを持つシステムサービスは、system_server プロセス内で稼動する Package Manager Service と、単独のネイティブプロセスとして動作する installd デーモンである。両者はいずれもシステムブート時に動作を開始する。
- Package Manager Service は起動時に /data/system/packages.xml ファイルより現システム上のパッケージに関する情報を読み込んで実状と照合し、何らかの不整合があれば再インストール処理を含む自動復旧を試みた上で同ファイルへ最新の状態を反映する。
- Package Manager Service は起動時に /system/etc/permissions/platform.xml を参照し既定の Android パーミッションに関する情報をロードし、同ディレクトリ下の他の *.xml より当該端末がカバーする機能の情報をロードする。
- installd デーモンは UNIX ドメインソケット /dev/socket/installd 経由で Package Manager Service から処理要求を受信し、パッケージのインストール・アンインストールまわりの一連の手順のうち root 権限を要する処理を主に担当する。
- PackageInstaller は通常のパッケージを対話的にインストールするための Android 既定のアプリケーションである。ユーザからインストール指示を受けると PackageInstaller は InstallAppProgress アクティビティを呼び出し、InstallAppProgress は Package Manager Service の installPackage() API 経由で当該パッケージのインストールをシステムへ依頼する。
- Package Manager Service はパッケージインストール要求を受けると当該パッケージの情報を取得し以下を実施する
- インストール処理用のキューへパッケージ情報を追加〜順番待ち
- 当該パッケージの適切なインストールロケーションを判定
- 新規インストール/更新インストールの判別
- 所定のディレクトリへの apk ファイルのコピー
- 当該アプリの UID の決定
- installd デーモンへ処理を要求
- アプリケーションディレクトリの作成とパーミッション設定
- dex コードのキャッシュディレクトリへの切り出し
- 最新の状態を /data/system/packages.xml および packages.list へ反映
- インストール完了の旨をパッケージ名を添えてシステムへブロードキャスト
(新規の場合:Intent.ACTION_PACKAGE_ADDED
更新の場合:Intent.ACTION_PACKAGE_REPLACED) - 上記の PackageInstaller 経由でのインストール経路とは別に、Package Manager Service は内部クラス「AppDirObserver」による非対話式のインストール経路を用意している。 AppDirObserver は android.os.FileObserver の派生クラスであり、所定のディレクトリ直下に .apk の拡張子を持つファイルが配置/削除されると自動的にシステムへのインストール/アンインストールを行う機能を持つ。(※別経路でインストールが行われた場合に重複処理発生を抑制するための機構を備える) Package Manager Service は起動時に /system/app, /data/app, /data/app-private, /system/framwrorks, /vendor/app の各ディレクトリを対象に AppDirObserver クラスのインスタンスを生成する。なお、これらのディレクトリへの書き込みには特権が必要であるため一般のアプリケーションプロセスから直接 AppDirObserver の提供する機構を利用することはできない。
コード読み
パッケージインストール処理を構成する一連のソースコードはかなりのボリュームがありますが、コードリーディングの一助として注釈を添えた抜粋を以下に掲載します。コードはいずれも 2012-07-06 現在の内容です。 なお、オンラインソースへのリンクには便宜上 http://android.git.linaro.org/ を使用しています。 続きを読む
inotify で Android 上のファイル I/O を監視する
Android の Linux カーネルには inotify が含まれており、Android SDK には inotify を利用した FileObserver クラスが用意されています。
Android では一般のアプリケーションプロセスからアクセスできるファイル・ディレクトリが制限されるため、FileObserver を使い Android アプリの形でツールを用意しても今回の目的にはあまり役に立たないでしょう。こういう時には CUI のコマンドラインツールの方が何かと融通がききます。Android エミュレータ環境への adb shell 接続で得られる root 権限のコンソール上でツールを動かすことにしました。
Android の notify コマンドを使う
Android には inotify API を使ってファイル I/O をモニタするコマンドラインツール「notify」が含まれています。 [platform/system/core/toolbox/notify.c]
# notify Usage: notify [-m eventmask] [-c count] [-p] [-v verbosity] path [path ...]この notify コマンドを使って "/data/local/tmp" ディレクトリを監視しつつ別プロセスから以下のコマンドを実行した時の出力例を示します。
# pwd /data/local/tmp # echo aaa > test.txt # mv test.txt aaa.txt
# notify -c 10 /data/local/tmp /data/local/tmp: 00000100 00000000 "test.txt" ; 0x00000100:IN_CREATE /data/local/tmp: 00000020 00000000 "test.txt" ; 0x00000020:IN_OPEN /data/local/tmp: 00000002 00000000 "test.txt" ; 0x00000002:IN_MODIFY /data/local/tmp: 00000008 00000000 "test.txt" ; 0x00000008:IN_CLOSE_WRITE /data/local/tmp: 00000040 00000033 "test.txt" ; 0x00000040:IN_MOVED_FROM /data/local/tmp: 00000080 00000033 "aaa.txt" ; 0x00000080:IN_MOVED_TO※ I/O イベントの定義は [platform/bionic/libc/kernel/common/linux/inotify.h]
inotifywait コマンドを使う
Android の notify コマンドはコンパクトで手軽なツールですが、出力の読みにくさに加え、ディレクトリ配下を再帰的に監視できない点が不便です。改造したりツールを自作するのも面白そうではありますが、Linux 界隈には inotify-tools というパッケージがあることを知り、そこに含まれる inotifywait コマンドを利用することにしました。
すでに誰かが Android 上で動作する inotifywait をビルドし配布しているのではないかとネット上を探したところ見当たらなかったため手元でビルドを行いました。inotifywait 本体と NDK でのビルド用に手を加えたプロジェクト一式を以下に公開します。
md5sum [55BF0FD8365A4139D679CE0D6A3A07B3]
プロジェクト一式(※) - github
(※) 以下の著作物が含まれます。利用に際しては各ソフトウェアのライセンス規約を遵守して下さい。・ inotify-tools
著作権者:Rohan McGovern, Radu Voicilas
ライセンス:GPL version 2
サイト:https://github.com/rvoicilas/inotify-tools/wiki/
・ GNU glibc POSIX 正規表現関数群 (glibc-2.11.3 より)
著作権者:Free Software Foundation, Inc.
ライセンス:LGPL version 2.1 or later
サイト:http://www.gnu.org/software/libc/
inotifywait に /data /cache /system の各ディレクトリを再帰的に監視させた状態で、自作のアプリ「HelloApp」を起動した際の出力例を以下に示します。
# ./inotifywait -r -m /data /cache /system Setting up watches. Beware: since -r was given, this may take a while! Watches established. /system/framework/ ACCESS framework-res.apk /system/framework/ ACCESS framework-res.apk /data/app/ OPEN com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/app/ OPEN com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/dalvik-cache/ OPEN data@app@com.example.helloapp-1.apk@classes.dex /data/dalvik-cache/ ACCESS data@app@com.example.helloapp-1.apk@classes.dex /data/dalvik-cache/ ACCESS data@app@com.example.helloapp-1.apk@classes.dex /data/app/ OPEN com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /data/app/ ACCESS com.example.helloapp-1.apk /system/framework/ ACCESS framework-res.apk /data/app/ ACCESS com.example.helloapp-1.apk /system/fonts/ OPEN DroidSans-Bold.ttf /system/lib/hw/ OPEN gralloc.default.so /system/lib/hw/ ACCESS gralloc.default.so /system/lib/hw/ ACCESS gralloc.default.so /system/framework/ ACCESS framework-res.apk /system/framework/ ACCESS framework-res.apk /data/app/ CLOSE_NOWRITE,CLOSE com.example.helloapp-1.apk :
(tanabe)
Android アプリ「SundayPad」を公開しました
予定のない休日の午後に PC の前でごろりと横になって DVD を観たりあちこちのサイトを見て回ったりするのは楽しいのですが、ひとたび「のんびりモード」に浸るとキーボードに触るのが億劫になります。ウェブ検索用にちょっとキーワードを入力したいのだけれど今の体勢を崩すのはなんだか悔しい。こんな時スマホなら音声入力を手軽に使えるので便利です。でも外出先ならともかく休日の自宅でわざわざ端末の小さな画面を追いかけるのはあまり嬉しくない気もする。不精者はあれこれ悩みます。
ある時ふと思いました。スマホには得意で賢い音声認識だけをやらせて、その結果を簡単に PC 上で利用できるようにすれば何かと便利ではないか?
さっそく Google 音声認識と連携し認識結果文字列を送信する簡単な Android アプリと、それを受信して PC 上のアプリの所定のフィールドへその文字列を送出する Windows プログラムを作ってみました。期待通りそれはシンプルな割に便利なものになりましたが、しばらく使っている内に「PC のマウスカーソルを操作できればもっと便利になる」ということに気づき、タッチパッド機能と必要最小限のソフトウェアキーボードを加えることにしました。
同僚たちに試作版を見せるとなるほどと受けもよく、そこで出されたアイディアを含めて機能追加と改良を行いひと通り形になったのが Android アプリ「SundayPad」と、PC 用のエージェントプログラム「SpAgent」です。興味のある方はお試し下さい。
改訂履歴
v1.0.1 (2012-06-07)
- 要求元の Android 端末のアドレスが前回認証ずみのものと同一であれば PC 側での受け入れ確認ダイアログの表示をスキップ
- Windows 環境でのカーソルの細かい移動をなめらかに
インストール・ダウンロード
Android アプリ「SundayPad」 (2012-06-07 更新)
SundayPad - Google Play のページ
PC 用エージェントプログラム「SpAgent」
Windows 版 - SpAgent.zip (2012-06-07 更新)
Mac 版 - SpAgent.dmg (2012-06-07 更新)
動作環境
SundayPad は Android OS 2.1 以上に対応します。
バージョン 2.2 以上の環境をお勧めします。
SpAgent の動作は以下のそれぞれ単一のテスト環境においてのみ確認しています
Windows 版
- Windows 8 Consumer Preview 32 bit/ 64 bit (デスクトップ環境)
- Windows 7 Professional SP1 32 bit/ 64 bit
- Windows Vista Business SP2 32 bit/ 64 bit
- Windows XP Professional SP3 32 bit
- Windows XP Home SP3
- Windows 2000 SP4
Mac 版
- Mac OS X バージョン 10.6.8
Android アプリ「AppNetBlocker」を公開しました
この記事に掲載の「AppNetBlocker」は Android 5.0 以降の環境では正しく動作しません。記録としてアプリ本体へのリンクは当面残しますが、コメント欄に何度か記載の通りこのアプリケーションの開発はすでに終了しており、今後改訂を行う予定はありません。ご了承下さい。
以前から自分自身がほしいと思っていた Android アプリが形になったためマーケットで公開しました。 今回はそのアプリ、「AppNetBlocker」をご紹介します。
AppNetBlocker は、所定のアプリから「完全なインターネットアクセス」の許可を除去するツールです。実行に root 権限は必要ありません。Android 1.6 以上の環境で動作します。興味のある方はご利用下さい。もちろん無料です。
(2011/12/26 追記)
本アプリは、現時点では安全面において不安要素の少なくない Android をめぐる状況において Android 利用者が自分自身を守るためにとり得る対策のひとつを形にしたものであり、他者の権利を脅かすことを目的とするものではありません。
もし、Android を今よりもさらに安全に利用することが可能となればより多くの利用者・開発者の利益につながることでしょう。本アプリはたとえ僅かでもその一助になればと手がけたものであり、開発の動機もそこにあります。
しかしながら、一部の方から本アプリと Android マーケット規約とのかねあいを懸念するご指摘がありました。その話題については判断の余地があるものと認識していますが、少なくとも利用者の不安を誘引することはまったく本意ではなく、マーケットでの配布という形態は一時中断することとします。
実験用のいわゆる「野良アプリ」として apk のダウンロードリンクを当面残しておきます。このリンクから端末へ直接インストールすることはできません。意図を理解される方のみ自己責任でご利用下さい。
[ AppNetBlocker.apk ]
md5sum [CC8104C9DDE44AD308F09FF22B551575]
AppNetBlocker とは?
Android 端末上のデータを狙うマルウェアの問題が取り沙汰されていることもあり、アプリに付与された「許可」の内容は何かと気になります。 特に、それがネットワークアプリやバナー広告を表示するアプリではなく、また、機能面でインターネットへのアクセスが必須とは考えにくい内容のアプリであるにもかかわらず「完全なインターネットアクセス」許可を持っている場合は悩ましいですね。 そんな時には AppNetBlocker が役に立つかもしれません。 続きを読む
エンコードされた AndroidManifest.xml を読む
1. データ例
(A) テストアプリ「MyApp」用に記述した生の AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.klab.sample.myapp" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="4" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MyApp" android:label="@string/app_name" android:launchMode="singleTask" android:excludeFromRecents="false" android:configChanges="orientation|keyboardHidden"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> </manifest>
(B) パッケージ後の MyApp.apk に含まれる エンコードずみ AndroidManifest.xml のダンプ
(C) 試作コードにより上記 (B) に含まれる情報を XML ツリー形式に再構成したもの
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="jp.klab.sample.myapp"> <uses-sdk android:minSdkVersion="4" /> <application android:label="@0x7F050001" android:icon="@0x7F020000"> <activity android:label="@0x7F050001" android:name=".MyApp" android:excludeFromRecents="false" android:launchMode="2" android:configChanges="0x000000A0"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> </manifest>
- エンコードされたデータには元の XML 記述中の名前空間情報・要素名・属性名・属性値・タグの階層情報等が含まれており、属性値として指定されたリソース ID や定数に対するシンボル名は値に解決された状態で保持される
- エンコードの目的はサイズ圧縮ではなく端末においてより簡単にデータをパース可能とすることにある ("This is designed to express everything in an XML document, in a form that is much easier to parse on the device.")
Android アプリケーションが起動するまでの流れ
まとめ
※クリックすると大きな図が開きます
※プロセスの親子関係に注意
$ ps USER PID PPID VSIZE RSS WCHAN PC NAME root 1 0 268 180 c009b74c 0000875c S /init root 33 1 60924 16448 c009b74c afd0b844 S zygote system 62 33 130944 27412 ffffffff afd0b6fc S system_server app_29 122 33 82996 21988 ffffffff afd0c51c S com.android.launcher app_9 476 33 78500 20568 ffffffff afd0c51c S jp.klab.stone :
コード読み
以下はソースコードを追った記録です。
なお、例の一件以来 android.git.kernel.org のダウン状態が続いているため、記事中のコードは android.git.linaro.org 上の最新版(2011年 9月末現在)からの引用です。
続きを読む
Android 上のアプリから SSL クライアント認証の必要なサーバへアクセスする方法
それから半年、本家の Android 標準ブラウザは現在も未対応のままですが、「SandroB」のようにクライアント認証に独自対応したブラウザも登場し始めています。こうした動きは Android ユーザとしてとても喜ばしいことで、対応環境の拡大や機能面の向上など今後の進化が大いに期待されます。
一方で、たとえば KLab の「VPN-Warp」のような SSL-VPN システム経由での利用が想定されるアプリケーションは Web ブラウザばかりではありません。私の場合、出先から自宅 PC へのアクセスには VNC を VPN-Warp+stone 経由で使っています。VNC ビューワそのものは SSL に対応していませんが、通信に stone を介在させることで所定のアプリ本体が SSL まわりの実装を持っていなくてもそういう使い方が可能となるわけですね。もちろん、人によってはここで言う「所定のアプリ」に、「普段使い慣れているブラウザ」が該当する場合もあるでしょう。
冒頭の記事を書いた時点では Android 用 stone は何かと面倒な CUI ベースでしたが、現在は通常の Android アプリとしてマーケットで公開しています。社内からの声もあり、今回はこのアプリ版に固有の Android キーストアへ証明書をインストールする機能に関する話題に触れながら、stone for Android でクライアント証明書を利用する手順等を紹介します。 続きを読む
root 化ずみ端末に対応した Android アプリを書く方法
一部の開発者向け製品以外では root 権限を取得するための公式の手段は提供されていないので、その方法を自分で探索するにせよ誰かが開拓した手順をトレースするにせよすべては自己責任であるわけですが、一方で root 権限を必要とするアプリはマーケットにもあれこれ出回っており、つまりはこの件に関する安全性と利便性のトレードオフに満足できない向きが世界中に大勢いるということでしょう。
その状況自体もいろいろ興味ぶかいのですが、ところで、その「root 権限を利用するアプリ」というものはどうやって書けばいいのでしょう?Google 公式の開発者向け資料は言うに及ばず、その他のリソースにも今のところほとんどこの話題に関する情報は見当たりません。そこで今回は、実際に手元のアプリを root 権限での実行に対応させる試みを通じて得た情報やノウハウを紹介したいと思います。 続きを読む
GUI 版「stone for Android」公開のお知らせ
本アプリは Android NDK を使用してビルドした 32ビットのネイティブバイナリを含んでいます。 先般 Google はこのようなアプリの Google Play での扱いについて以下の発表を行いました。
- 今後の Google Play でのアプリのセキュリティおよびパフォーマンスの改善について - developers-jp.googleblog.com
2019 年 8 月に、Play ではネイティブ ライブラリを含む新しいアプリとアプリのアップデートは、32 ビット版に加えて 64 ビット版を提供することが義務づけられます。
- (関連記事)
Androidアプリ、2019年8月に64bit対応を義務化。2018年にはOreo以降をターゲット化へ - japanese.engadget.com
この話題を受け今後の対応を検討してきましたが、現時点での結論として、本アプリの 64ビット対応を行う予定はありません。そのため、現行のバージョン 1.0.3 が最終版となる見通しです。 そういった事情により、念のため現行版の apk のコピーを以下へ配置しています。
Android OS での 32ビットバイナリサポートがいつまで維持されるか現時点では不明ですが、必要な場合にはこれをご利用下さい。本アプリをご利用の皆様へ謹んで御礼申し上げます。
SSL 対応のパケットリピータ「stone」を Android 上で動かすことにはさまざまなメリットがあります。 以前このブログに次の記事を書きました。
・「パケットリピータ stone を Android へポーティング 」
・「Android 上のブラウザから SSL クライアント認証の必要なサーバへアクセスする方法」
当時は Android NDK でビルドしたネイティブの stone バイナリとビルド用リソースの一式を公開しましたが、その後 GUI 版の開発を手がけ、「stone for Android」として Android マーケットへの登録を行いました。
これはいわゆる「普通の Android アプリ」ですから端末へのインストールも取りまわしもとても簡単です。
興味のある方はぜひご利用下さい。ソースコードも公開しています。
ブラウザから端末へ PUSH インストールの可能なページ
Android上のブラウザからSSLクライアント認証の必要なサーバへアクセスする方法
■ 初めての Android 端末(IS01)のブラウザで困ったこと
KLab では、社員が社外から社内 LAN 上のサーバへ安全にアクセスするために、自社製の SSL-VPN システムである「VPN-Warp」を使っています。これにより、社員はインターネット上の専用の中継サーバへアクセスすることで所定の社内サーバと通信することが可能です。この中継サーバは不正利用を防ぐためにクライアントからの接続要求時に所定の電子証明書の提示を求める「SSL クライアント認証」を行います。KLab の発行した有効な証明書を提示するクライアントからの要求のみがこれを通過できるというわけですね。
さて、筆者は 2010 年末に IS01 を入手し、あれこれ試していたところひとつ困ったことがありました。Web ブラウザが SSL クライアント認証に対応していないため上記の VPN-Warp 中継サーバ経由で社内のサーバへアクセスすることができないのです。スマートフォンに馴染みが薄いためとりあえずの実験用と割りきって確保した端末ではありますが、その点以外は結構気に入っていたため残念に思いました。
■ 「stone」を併用することで解決
先日このブログで Android 用の「stone」一式を公開しましたが、実のところ stone を Android 上で動かしたいと考えたのは、手元の IS01 のブラウザから VPN-Warp 経由で社内サーバへアクセスすることが目的でした。パケットリピータ stone の SSL 対応機能はクライアント認証をサポートしているため、ブラウザと VPN-Warp 中継サーバの間に stone のプロセスを介在させてやれば SSL まわりの処理一式を stone にまかせることができるのです。
stone に クライアント認証対応を含めて SSL 処理を代行させる
実機でこの操作を行った様子を以下に示します。
# 図中の stone コマンドラインは次の内容です stone -d -q pfx="cert.pfx" -q passfile="pass.txt" \ relay.klab.org:443/ssl localhost:8888
■ 実は最近の端末のブラウザもクライアント認証未対応?
さて、ブラウザがクライアント認証に対応していないという制約は筆者の持つ IS01 が旧い Android 1.6 ベースであることに起因するものであって Android 2.x 端末ではきっと解消されているのだろうと漠然と想像していたのですが、実際にはそうではないことを最近になって知りました。
この冬の新機種ブームも相まって社内の Android ユーザが増加する中、「外からイントラにアクセスできたよ!」という声が聞こえてこないのです。利用者に事情を尋ねるとやはりブラウザがクライアント認証をハンドルしない様子とのことでした。しばらく Android から離れていたため事情がわからず情報を探してみると、どうやら本件への対応は今後の課題という扱いのようです。
Android プロジェクト公式フォーラムより
・Issue 11231: Provide support for managing CA and client certificates
・Issue 8196: Enhancement: Client Certificate Authentication in Browser
ご存知の方はとっくにご存知の話だと思いますがちょっと意外に感じました。世間一般に SSL クライアント認証を活用したサービスがあまり多くないことの影響なのかもしれません。
■ 同じ問題で困っている方へ
本件は Android の近い将来のバージョンでの解決が期待されますが、自分と同じく今現在の制約に困っている方の参考になればとこの記事を書くことにしました。Android 用 stone のダウンロードリンクと導入・実行方法を次の記事に掲載しています。
・DSAS開発者の部屋:パケットリピータ「stone」を Android へポーティング
今のところ単体の ELF 実行形式であるため CUI に慣れていないと多少扱いにくいという点と、Android では hosts ファイルの書き換えが一筋縄ではいかない事情もあり接続先によってはブラウザでの localhost 指定が通らない可能性がありますが、本記事を最後まで読み進められた知見と技術力で上手くご利用下さい。なお、stone の詳細については下記サイトの解説記事が参考になるでしょう。
(tanabe)
パケットリピータ「stone」を Android へポーティング
当初は個人的に使用することが目的でしたが、原作者の仙石 浩明 (Hiroaki Sengoku) さんの了承のもと、ビルドずみバイナリを含むリソース一式を非正式版として公開します。他のプラットフォームで stone の便宜に馴染んでいる方はお試し下さい。なお、stone 本体の使用方法等についてはこの記事では触れません。公式サイトの解説記事を参照して下さい。 続きを読む
Android NDK でネイティブ CUI プログラムを書く!
2014年10月に公開された Android 5.0 (Lollipop) 以降では PIE (Position Independent Executable) 以外のネイティブ実行形式がサポート外となったため注意が必要です。詳細は本ブログの次の記事を参照して下さい。
「Android で今後ネイティブ実行形式を扱う際に注意すべきこと」(2015年6月17日掲載)
今ならとても有利な条件で au の Android 端末を入手できることをネットで知り、先週の休みに地元の家電量販店へ足を運んでみました。 Android 1.6 搭載のこの「IS01」に今後 OS のバージョンアップサポートが適用されない旨の発表を聞いた時はお気の毒に・・と思っていましたが、極端に低いコストで実機を持てるのなら話は別です。とりあえずの実験用としていろいろ使えることでしょう。
そんなわけでこの何日間か Android SDK を勉強しながら Java のプログラムを書いたりしていたのですが、Android NDK を使えば ネイティブコードの JNI 用ライブラリだけではなく スタンドアロンの CUI プログラムも作成できることを知りました。
それはそれで面白いので Hello world! ではなくさっそく変なプログラムを C 言語で書いてみました。Android 端末上で次のように動作します。
・ 簡易 HTTP サーバとして振る舞い PC 上の web ブラウザと対話できる
・ フォームから指定された場所の地図を Android 端末上に表示する
# プログラム作成中の自問自答
Q:「もしかすると普通に PC で Google Maps を使う方が便利ではないか?」
A:「GPS の恩恵あり!入力の楽な PC キーボードを使えるのも良し!」
Q:「なら普通に SDK でそういうアプリを作ればよいのではないか?」
A:「Android の根っこは Linux!C でさくさく書くのが粋というものだ!」
粋かどうかはさておき興味のある方はご覧下さい。ソースコードも掲載します。 続きを読む
Windows用フリーウェア「MyCloudFile」をバージョンアップしました (2010/11/16)
新しいバージョン 1.0.0.8 は、ご要望の多かった「プロキシサーバ経由での通信」に対応しています。
MyCloudFile のページをご参照の上、同ページより新しいバージョンをダウンロードしてご利用下さい。
(tanabe)
Windows用フリーウェア「MyCloudFile」を公開します
2010年に公開した本ソフトウェアの実装には Google 固有の認証方式である「ClientLogin」を使用していますが、Google 社は本年 4月20日をもってこの ClientLogin のサポートを終了する旨を表明しています。本ソフトウェアの公開当時にはストレージサービスとしての「Google ドキュメント」のユーザインターフェイスは発展途上の段階にありこうした補助的なソフトウェアにも相応の役割がありましたが、その後同社サービスのブラウザでの操作性が格段に向上した事情も踏まえ、総合的に判断した結果、今回の Google 社側の措置に伴い本ソフトウェアの配布・サポートをこの機に一旦終了させて頂きたいと考えています。何卒ご理解・ご了承下さいます様お願い致します。本ソフトウェアをご利用頂いた皆様に謹んで御礼申し上げます。
(※以下は過去の補足記事です)
※2012.08.25 追記
本日バージョン 1.0.0.9 ベータ2 を公開しました。ダウンロードしたファイルを復号できないケースのある不具合が修正されています。
※2011.10.21 追記
現在、バージョン 1.0.0.8 でファイルをアップロードできない問題が発生しています。
これは Google サービス側の所作に以前と異なる部分があるためですが、現時点ではそれが一時的なものであるか恒久的なものであるか判然としません。とり急ぎ、この問題に対処したバージョン 1.0.0.9 ベータのダウンロードリンクを記事に追加しました。ぜひ、テストにご協力下さい。
「MyCloudFile」は Windows 用のソフトウェアです。このソフトウェアの概要は以下の通りです。
- Google アカウントひとつで自分専用のオンラインストレージ空間に簡単にファイルやフォルダをアップロードすることができる
- アップロードファイルの名前と内容を暗号化+圧縮した状態でストレージ上に配置し、ダウンロード時には自動的に復号する。さらに任意のパスワードによる保護を加えることも可能
- アップロード時に生成されるショートカットファイルを使って手早くリモートファイルを参照 / 削除することが可能
- アップロード時に生成される専用コードを特定の他者に伝えることで安全にファイルを受け渡すことができる
- ネットワーク通信には SSL のみを使用
- Windows 2000, XP, Vista, 7 での動作を確認しています
クラウドを利用した手軽で安全なファイル保管ツールとして、また、有名な「宅ふぁいる便」のように、メールに添付するには大きすぎるファイルを共有するためのツールとして使うことができるでしょう。実験用に開発した試作版をフリーソフトとして公開します。以下の説明記事をよく読んでお試し下さい。 続きを読む
「このプログラムは正しくインストールされなかった可能性があります」を回避する方法 (Windows 7, Vista)
実際にプログラムを動かしてみると異常のない場合が多いのですが、不審に思いネットを検索してみると非常に多くのページが見つかります。検索結果の見出しを眺めながらあれこれ覗いてみると、どうやらこのメッセージはソフトウェアの利用者だけでなく開発者にとってもちょっとしたジレンマのようです。
今回は、このメッセージが表示される原因とプログラム側での回避方法を調べてわかったことを書いてみます。 続きを読む
Windows実行形式のMachineタイプを判別する方法
64 ビット環境の場合は所定のバイナリを実行してタスクマネージャで確認すれば、32 ビットプロセスならプロセスイメージ名の末尾に「*32」の符丁が表示されるためそれで見分けることもできますが、わざわざそのためにプロセスを起動するというのも何だか妙な話だし、また、バイナリが DLL の場合にはその方法は使えません。好ましいのは「バイナリの内容をチェックして判別する」というもっとも単純なやり方だと思いました。また、そういうツールがあれば 32 ビット環境でも 64 ビット環境でも所定のバイナリを手軽に識別できるはずです。
Windows に標準でそういう機能がないかとざっと見渡したところではどうも見当たらず、ネット上にもそれらしいツールが見つからなかったため簡単なコードを書いてみることにしました。 だいぶ前にこのブログに Windows 実行形式のヘッダ構成を書いたことがあり、その中のどこかにそういう情報があった記憶がありました。あらためて探してみるとこれでした。
MSDN: IMAGE_FILE_HEADER Structure
|Members | Machine | IMAGE_FILE_MACHINE_I386 (0x014c) x86 | IMAGE_FILE_MACHINE_IA64 (0x0200) Intel IPF | IMAGE_FILE_MACHINE_AMD64 (0x8664) x64そのまんまですね^^;
これらの情報をもとに C 言語で短い Windows プログラムを書きました。 引数で渡されたファイルを読んで実行形式であれば Machine タイプを表示する内容です。 ビルドずみのバイナリ (x86 & x64)
筆者は SendTo フォルダにショートカットをコピーし右クリックメニューから呼び出して使っています。
// // 引数で渡された Windows 実行形式ファイルの Machine タイプを判別する // #if !defined(UNICODE) #define UNICODE #define _UNICODE #endif #include <windows.h> #include <stdio.h> #include <sys/stat.h> #pragma comment(lib, "user32.lib") // IMAGE_FILE_HEADER から Machine タイプを取得 int MyCheckMachineType(LPCWSTR pszFileName) { FILE *fp; struct _stat st; IMAGE_DOS_HEADER idh; IMAGE_NT_HEADERS inh; if (_wstat(pszFileName, &st) < 0 || _wfopen_s(&fp, pszFileName, L"rb") != 0) { return -1; } if (fread(&idh, sizeof(IMAGE_DOS_HEADER), 1, fp) < 1 || idh.e_magic != IMAGE_DOS_SIGNATURE || // "MZ" idh.e_lfanew <= 0 || idh.e_lfanew >= st.st_size) { fclose(fp); return -2; } if (fseek(fp, idh.e_lfanew, SEEK_SET) != 0 || fread(&inh, sizeof(IMAGE_NT_HEADERS), 1, fp) < 1 || inh.Signature != IMAGE_NT_SIGNATURE) { // "PE\0\0" fclose(fp); return -3; } fclose(fp); switch (inh.FileHeader.Machine) { case IMAGE_FILE_MACHINE_I386: return 0; case IMAGE_FILE_MACHINE_AMD64: return 1; case IMAGE_FILE_MACHINE_IA64: return 2; } return 3; // Unknown } // 現プロセスが WOW64 下で実行中のプロセスか BOOL MyIsWow64Process() { typedef BOOL (WINAPI *DEC_ISWOW64PROCESS)(HANDLE, BOOL*); BOOL bWow64Process = FALSE; DEC_ISWOW64PROCESS pIsWow64Process = (DEC_ISWOW64PROCESS) GetProcAddress(GetModuleHandle(L"Kernel32.dll"),"IsWow64Process"); if (!pIsWow64Process || !pIsWow64Process(GetCurrentProcess(), &bWow64Process)) { return FALSE; } return bWow64Process; } int APIENTRY wWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nCmdShow) { int sts; WCHAR *pName; WCHAR *m[] = { L"x86", L"x64", L"IA64", L"Unknown"}; WCHAR *e[] = { L"broken?", L"not executable", L"open error"}; #ifdef WIN32 if (MyIsWow64Process()) { MessageBox(NULL, L"WOW64 によるリダイレクトの影響を避けるために\n" \ L"Windows x64 環境では x64 版を使用して下さい", L"Warning", MB_OK); } #endif if (__argc < 2) { return 0; } sts = MyCheckMachineType(__wargv[1]); pName = wcsrchr(__wargv[1], L'\\') + 1; if (sts >= 0 && sts <= 3) { MessageBox(NULL, m[sts], pName, MB_ICONINFORMATION|MB_TOPMOST); } else { MessageBox(NULL, e[sts+3], pName, MB_TOPMOST); } return 0; }
たとえばこのプログラムでこのプログラム自身の x86, x64 バイナリをチェックすると図のように表示されます。
(tanabe)
Windows用フリーウェア「HookDate」をバージョンアップしました
新しいバージョン 1.0.2.0 での改訂内容は以下の通りです。
- 64ビット Windows 環境 (x64) への対応
- exe ファイルへのショートカットファイルを HookDate.exe にドラッグ&ドロップできるように
- HookDateRegister.exe を追加しインストール手順を簡素化
以下のテスト環境において動作を確認しています。
- Windows 2000 Professional SP4 (x86)
- Windows XP Home SP3 (x86)
- Windows XP Professional SP3 (x86)
- Windows Vista Business SP2 (x86)
- Windows 7 Professional (x86)
- Windows XP Professional SP2 (x64) *仮想環境
- Windows Vista Business SP2 (x64)
- Windows 7 Professional (x64)
本バージョンからインストール・アップデート方法を変更しています。
HookDate のページの記事をご参照の上、同ページからダウンロードしてご利用下さい。
(tanabe)
Windows仮想プリンタプログラムを作ってみる
システムは本物のプリンタだと信じているのに実はそれはソフトウェアへのインターフェイスにすぎず、印刷ジョブを渡したら最後、データは隅から隅までなめまわされ好きなように処理されてしまう。ということは、その気になればあんなことやこんなこともできてしまうはず・・・。 あらためて考えてみるとなかなか面白い話なので、仮想プリンタのしくみを調べて何かプログラムを書いてみたいと思いました。
手はじめに、定番の題材として所定のドキュメントを PDF や画像に変換しファイル出力する仮想プリンタを作ってみることにしました。 ひとつの仮想プリンタを自作のコードで構築し、データ出力部分を著名なオープンソースソフトウェアの Ghostscript と RedMon、ImageMagick で処理する内容です。
作成したプログラムとそのソースコードをフリーソフトウェアとして公開します。 こういった具体的な開発情報は案外見あたりませんので興味のあるかたはご覧下さい。
MyVirtualPrinter - github.com/mkttanabe |
今回はこの MyVirtualPrinter を切り口に、Windows での印刷処理の概要と仮想プリンタをプログラムで構成する方法について説明します。続きを読む
起動不能なPCからインポートずみ証明書を救出せよ!
1. ことの始まり
先日、遠くの知人から「手元の PC で Windows XP を起動できなくなった」と連絡がありました。 最初のロゴ表示が終わったところでフリーズする状態で、セーフモードでの起動もできないそうです。
彼は言います。
「HDD を取り出し別の PC につないで最新版のデータファイルを全部拾い上げたので、普段使ってるソフトの数を考えればもとの環境の復旧に時間をかけるよりも新しい環境へデータを移行する方が早そうだ」
幸い HDD に物理的損傷はなかったようです。
よかったね今後は HDD のまるごとバックアップもこまめにやるべし、と答えると、「実はひとつだけ問題が残っている」とのことでした。
話はここから始まります。 続きを読む
Windowsでfopenを使ってはいけない!?
Windows環境でsvn+ssh:// からの bzr branch に失敗するという報告があり、調べてみたところ面白いことが判ったので記事にしておきます。
まず、bzr-svnはsubvertpyというlibsvnのバインディングを利用していて、svn+ssh://プロトコルのハンドリングはlibsvnが行っています。 このため、bzr+ssh://ではPython製のSSHクライアントであるparamikoを利用している環境でも、svn+ssh://の場合はlibsvnがssh.exeやSVN_SSH環境変数に設定されたsshクライアントを起動しています。 私はputtyを利用しているので、SVN_SSH=plinkと設定してsvn+sshが利用できる環境を用意して問題が再現することを確認しました。
ログやトレースバックを追ってみたところ、あるファイルを構築する際に一時ファイルに書き込みしていって最後にクローズしてから目的のファイル名にリネームする、というロジックのリネームの部分でPermission Errorが発生していました。
しかし、エラーの原因になっているファイルは、リネーム直前に確実にclose()されています。 なぜPermission Errorが発生しているのかを調べるためにProcess Monitorで該当ファイルに関するシステムコールを追ってみたところ、
- Bazaarがファイルをclose()しているタイミングで、CloseFile()が発行されていない
- なぜかエラー発生後にplink.exeからCloseFile()されている
という事が判りました。
最初は「なんでplink.exeがBazaarの使っているファイルのハンドルを持っているんだ?」と混乱していたのですが、BazaarのIRCでこの事を話してみたところ、 "<jelmer>naoki: It's inheriting handles from the parent process perhaps?" と言われ、Windowsのプロセスとファイルハンドルについて調べてみました。
まず、WindowsのCreateProcess()システムコールは、bInheritHandlesという引数を持っていて、この引数がTRUEの場合ハンドルを子プロセスに引き継ぎます。 引き継がないとパイプが利用できないので、libsvnはbInheritHandles=TRUEでsshクライアントを立ち上げます。
CreateProcess()側でパイプ以外のハンドルを引き継がないような事ができないのですが、 CreateFile()のlpSecurityAttributes引数にSECURITY_ATTRIBUTES構造体を渡すことができ、この中にbInheritHandleというフラグがあります。 このフラグをFALSEにすると、CreateFile()で作ったファイルハンドルは子プロセスに引き継がれないようです。
Pythonの中からCreateFile() API を直接利用するのはちょっと面倒なので、MSVCRTのopen()やfopen()でもできないか調べてみたところ、
- fcntl.h 内で定義されている O_NOINHERIT フラグを open() に渡す
- fopen() の mode として、 "rbN" のように後ろに N をつける
という方法でファイルハンドルを引き継がないようにファイルを開くことが出来ることが判りました。
ここまで判ったところで、Bazaarの修正に取りかかりました。問題になっているファイルはビルトインのopen()関数を使っていて、 この関数のmode引数はそのままfopen()のmode引数になるので、 "wb" となっているところを "wbN" と書き換えるだけで良いかなと思ったのですが、 次のような問題がありました。
- "N" は現在のglibcでは無視されているけれども、将来何かに利用されるかも知れないし、他のlibcで利用されているかも知れない。 なのでWindowsでのみ"N"を使うように修正しないといけない。
- しかも、"N"が有効なのはVC++2005以降のMSVCRTで、それより前のMSVCRTでは無視される。Windows版のPython2.4やPython2.5では使えない。
なので、C言語のopen()関数に相当するPythonのos.open()関数と、os.O_NOINHERITフラグを利用してビルトインのopen()の代わりになる関数を作成し、ファイルをクローズした後にそのファイルを削除やリネームする場所でその関数を使うようにしました。
参考に、今回の修正のうち、open()の代替になっているopen_file()関数の定義部分だけ掲載しておきます。 実際の修正はこの修正のマージリクエスト で見ることが出来ます。また、Python標準ライブラリのtempfileモジュールもos.O_NOINHERITを利用して、一時ファイルを利用した後にファイルを削除できるようにしているので、そちらも参考にして下さい。
O_NOINHERIT = getattr(os, 'O_NOINHERIT', 0) if sys.platform == 'win32': def open_file(filename, mode='r', bufsize=-1): """This function works like builtin ``open``. But use O_NOINHERIT flag so file handle is not inherited to child process. So deleting or renaming closed file that opened with this function is not blocked by child process. """ writing = 'w' in mode appending = 'a' in mode updating = '+' in mode binary = 'b' in mode flags = O_NOINHERIT # see http://msdn.microsoft.com/en-us/library/yeby3zcb%28VS.71%29.aspx # for flags for each modes. if binary: flags |= O_BINARY else: flags |= O_TEXT if writing: if updating: flags |= os.O_RDWR else: flags |= os.O_WRONLY flags |= os.O_CREAT | os.O_TRUNC elif appending: if updating: flags |= os.O_RDWR else: flags |= os.O_WRONLY flags |= os.O_CREAT | os.O_APPEND else: #reading if updating: flags |= os.O_RDWR else: flags |= os.O_RDONLY return os.fdopen(os.open(filename, flags), mode, bufsize) else: open_file = open
この修正が問題なく取り込まれれば、Bazaar 2.1.1からは svn+ssh:// からの bzr branch ができるようになります。