win

2013年10月24日

iPhone 用アプリ「ExifSafe」について

はてなブックマークに登録

先日の記事 「Exif データにアクセスするコードを自作してみる」 では、よく知られた存在でありながらプログラムを書く際にブラックボックスとして扱われがちな Exif の仕様と構造の整理を行い、既存のライブラリへ依存せずに Exif データを操作することを目的に作成したコードを紹介しました。当初はパーサとしての機能がメインでしたが、後日以下の機能を実装しました。

  • 所定の IFD の削除・追加
  • Exif タグフィールドの削除・追加・変更
  • Exif サムネイルデータの取得
ソースコード: GitHub - exif

また、このコードを実際に使って次のような iPhone 向けアプリを試作してみました。
「ExifSafe」という名前をつけています。

  • アルバム上の所定の画像に含まれる Exif データを一覧表示
  • Exif データに GPS 位置情報が含まれる場合はマップで撮影地点を表示可能
  • 画像をメールで送信
    • Exif データを丸ごと削除して添付、またはプライバシーに関わりのあるデータのみ削除して添付、または元のまま添付
    • 画像の添付ファイル名には Exif データから取得した撮影日時情報が反映される
      ※「写真.jpg」のような識別性に乏しいシステム既定の添付名の不便さを補うもの
  • 画像から Exif データを削除したコピーを作成
    • Exif データを丸ごと削除してコピー、またはプライバシーに関わりのあるデータのみ削除してコピー
    • コピーした画像は 専用のアルバムから参照可
次の場所で公開しています。興味のある方はお試し下さい。
iTunes App Store - ExifSafe
ソースコード: GitHub - ExifSafe

2018年6月追記:諸般の事情により App Store 上の本アプリの更新は今後当面見送らせて頂きます。本アプリをご利用下さった皆様に謹んで御礼申し上げます。


(tanabe)
klab_gijutsu2 at 21:00|この記事のURLComments(0)
2013年08月30日

Exif データにアクセスするコードを自作してみる

はてなブックマークに登録

Exif (Exchangeable image file format) は現在出回っているほとんどのデジタルカメラや携帯電話・スマートフォン等での撮影画像のファイル形式として利用されています。画像データそのものに加え機器本体や撮影時の設定や環境など多くの情報が記録されることを活かしたソフトウェアやサービスも増えていますね。

先日ちょっとしたアプリを書いていた折に JPEG ファイル内の Exif データを参照する処理が必要になりました。初めての機会だったのでやり方を調べたところ 開発環境向けに用意されたこれこれのライブラリを使うのが常道とのことで、リファレンスを読みながらそういうコードを書きました。プログラムは期待通りに動作しその時はそれで特にどうと言うこともなかったのですが、後日別のプラットフォームで Exif データを利用するアイディアを思いつき、その環境でまた別のライブラリの使い方など調べているうちに何だか面倒になりました。

ここでのターゲットはあくまでも一介のデータ形式です。既存のソフトウェア資産を利用することは生産性の上でも効率の面でも間違った選択ではないものの、「普通のファイルに記録されたデータ」にアクセスするための道具立てにいろいろ左右されることが煩わしく感じられたのです。Exif の規格は公開されているため仕様を確認して必要なコードを自作すれば今後この形式を既存のライブラリごしに「ブラックボックス」として扱う必要はなくなるはずで、また、自作のコードであれば拡張や応用の融通が利きやすく必要なら別のプログラム言語への移植も楽でしょう。

デジタルカメラという今日ではごく身近な道具を通じてその存在を知りながら中身をずっと知らずにいた Exif のしくみに触れることはそれ自体も興味ぶかい経験でした。その一端をご紹介します。C 言語で試作したソースコード一式を記事の最後に掲載しています。

Exif の仕様

まず Exif のデータ形式の仕様をできるだけコンパクトに整理してみます。もっとも詳しく正確なのはもちろん公式の規格書ですから不明な点があればそこでの記述を参照して下さい。

規格書

電子情報技術産業協会 (JEITA) ホームページ上の「JEITA 規格総合検索」フォームから、「規格名」に "Exif" を指定して検索を実行すると「JEITA CP-3451C デジタルスチルカメラ用 画像ファイルフォーマット規格 Exif 2.3」を電子文書として閲覧できます。 ※ 2013年8月31日現在

一般社団法人 電子情報技術産業協会(JEITA) 公式サイト

全体の構造

下の図は規格書をもとに JPEG ファイル中の Exif データの構造をまとめてみたものです。要点をピックアップしてみます。

  • Exif データは JPEG ファイル内に配置可能な「アプリケーションセグメント」のひとつである APP1 セグメントに格納される
  • 当該 APP1 セグメントが Exif のセグメントであるか否かは当該セグメントのヘッダ内容で識別可能
  • Exif セグメント内に格納された数値データのバイトオーダーは big-endian, little-endian の両方があり得る
  • Exif セグメントには一件以上の IFD (Image File Directory) が含まれる
    • 0th IFD:主画像に関する付属情報を保持(Exifセグメントに必ず存在)
    • 1st IFD:主画像のサムネイルに関する情報およびサムネイル画像データを保持
    • Exif IFD:Exif 固有の付属情報を保持
    • GPS IFD:GPS 等の位置情報を保持
    • Interoperability IFD:互換性を保証するための情報を保持
  • 各 IFD は任意の数のタグフィールドを保持する
  • タグの種類は規格で定義されており各タグには識別用の番号が割り当てられている
  • 各タグの保持する値には複数の種類がありそのタイプとデータ長によって値の格納方法が規定されている

タグフィールドの扱い方

どこにどのような情報が配置されているかは上の図を追えば把握できるでしょう。構造を順に辿って最後に到着するのは具体的なデータの格納された末端のタグフィールドです。もちろんこれらが最も重要な要素であり、ここではタグの値にアクセスする方法を説明します。

フィールドの構成
一件のタグフィールドには以下の内容が含まれます
  • タグ 番号    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"

■      :
    :
タイプについて

タグの値には以下のタイプがあります。値の取得方法は後述のカウント・オフセットのルールとの組合せで決まります。

  • 1 = BYTE      8 ビット符号なし整数
  • 2 = ASCII     NULL 文字で終端する ASCII 文字列。ASCII のカウントは NULL 文字分を含む
  • 3 = SHORT     16 ビット符号なし整数
  • 4 = LONG     32 ビット符号なし整数
  • 5 = RATIONAL     LONG 2 個で表現する値。ひとつめの LONG は分子、ふたつめは分母を表す
  • 7 = UNDEFINED     任意のバイト列
  • 9 = SLONG     32 ビット符号つき整数
  • 10 = SRATIONAL     SLONG 2 個で表現する値。ひとつめの LONG は分子、ふたつめは分母を表す
カウントについて

カウントは「値の個数」を示します。「バイト数の合計」を表すものではありません。以下に例を示します。

  • ASCII タイプの値 "Apple\0" のカウントは 6
  • SHORT タイプの値 10 のカウントは 1
  • LONG タイプの値 10 のカウントは 1
  • LONG タイプの値 10, 11 のカウントは 2
  • RATIONAL タイプの値 3/5 のカウントは 1 (※ 2 ではない)
  • SRATIONAL タイプの値 3/5, 1/2 のカウントは 2
  • UNDEFINED タイプの値 0x03, 0x21, 0x00, 0xFF のカウントは 4
オフセットについて

4 バイトのオフセット領域にはタグの値が記録されている位置(上図中の★が起点)が保持されています。ただし、値が 4 バイト以内で表現できる場合はその値がオフセット領域に直書きされます。4 バイト以内の値の例を以下に示します。

  • BYTE タイプの値 1    -> 1 バイト
  • BYTE タイプの値 1, 3, 5, 7    -> 4 バイト
  • ASCII タイプの値 "abc\0"    -> 4 バイト
  • SHORT タイプの値 10    -> 2 バイト
  • SHORT タイプの値 10, 11    -> 4 バイト
  • LONG タイプの値 10    -> 4 バイト
  • UNDEFINED タイプの値 0x03, 0x21, 0xFF    -> 3 バイト
さらに、値の長さが 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 言語で書いたソースコード一式を以下で公開しています。

ソースコード: GitHub - exif

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)
klab_gijutsu2 at 14:55|この記事のURLComments(2)TrackBack(1)
2013年05月28日

GCM で Wake On WAN

はてなブックマークに登録

ソーシャルな用途ばかりではなく GCM (Google Cloud Messaging for Android) は自分が自分の端末へ通知を送るために使うのにも便利です。その応用例として、以前このブログで 遊んでいる端末を遠隔操作可能な監視カメラとして留守宅で使うアイディア を紹介しました。 そこでは Android 端末を "ファイアウォール越しに外からのプッシュ通知を受信できる消費電力の小さなコンピュータ" として利用したわけですが、今回個人的な必要からそれと同じ考え方で別のアイディアを形にしてみました。GCM 経由で端末へ指示を送り所定の PC を Wake On LAN させるというものです。

Android 端末を一台 LAN に接続した状態で待機させておけば、Wake On LAN の設定とアプリへの登録を済ませた任意の PC をルータの設定に手を加えることなく外から起動することができます。起動さえできれば PC の遠隔操作そのものには TeamViewer など既存のソリューションを柔軟に利用できますね。

このところ出先から自宅 PC へのアクセスが必要となるケースが増えていたもののルータに穴を開けるのはあまり気が進まずやむなく PC を起ち上げっぱなしにしたりしていたのですが、不経済な上にこれからの夏場には密室での連続稼動に一抹の不安がありました。興味のある方はお試し下さい。

アプリ本体: Google Play - WakeOnLan GCM

ソースコード: GitHub - WakeOnLanGCM

使い方

以下に使い方を簡単に控えます

対象 PC をアプリへ登録

  • 「WakeOnLan GCM」のインストール後、Wake On LAN 設定済みの対象 PC の情報をアプリに登録する
  • エントリ名には重複のない任意の名前を指定する
  • エントリのタップでマジックパケットを送出、長押しすると編集・削除へ

端末を GCM へ登録〜遠隔操作用 HTML フォームの生成と使用

  • 対象 PC の登録を終えたらアプリケーションメニューから「GCM 登録画面」へ移動
  • 端末を GCM へ登録するとパスワード設定を経て遠隔操作用 HTML フォームとそれを添付した自分あての新規メールが生成される
  • このフォームにはアプリに登録した PC の「エントリ名」のチェックボックスが並ぶ
  • ネットへ接続した環境でブラウザへこのフォームをロード。ボックスを適宜チェックしパスワードを投入してサブミット〜GCM 経由で LAN 上の Android 端末がメッセージを受信しマジックパケットを送出する


(tanabe)
klab_gijutsu2 at 19:47|この記事のURLComments(5)
2013年05月02日

QR コードを機器間での秘密情報の輸送に利用する試み

はてなブックマークに登録

QR コードのもうひとつの利点

QR コードはインターネットアドレスなど所定のテキストデータを簡単に機器に取り込むための手段として広く利用されています。印刷物に限らずさまざまな媒体で扱えるのも便利ですね。

先日、公開鍵暗号を使う Android アプリを試作していた折に、自分の端末 A で生成した秘密鍵を手持ちの別の端末 B へ安全に輸送する手段の検討が必要になりました。やり方はいろいろありそうですが、よりシンプルな方法をとあれこれ考えている内に、机上に無造作に置いたチラシの QR コードにふと目が留まりました。

QR コードからのデータ取り込みは通常光学的に行われます。このことは電子機器の視覚を利用したデータ通信と考えることができるでしょう。第三者による情報窃取の可能性がしばしば問題となるネットワーク通信とは異なり、「目」を使っての情報伝達には物理的に介入することが難しいため、デジタルデータを画像で表現する 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 コードを構成する最小の点)構成の最大値である 177 セル×177 セルから成る「バージョン 40」の器に、誤り訂正用の冗長データの最も薄い「レベル L」でエンコードしたデータを格納する際の最大文字数が記載されています。すべての半角英数記号を使う場合は 2953 文字です。この容量の多寡はさておき、普段目にする QR コードのそれとはかけ離れた規模のデータを納めた場合、実用的にはどうなのでしょう。

下の QR コードには半角英数記号 1675 文字を格納しています。これを一般的なサイズ・解像度の PC のモニタで表示した状態であればおそらく多くの携帯端末の QR コードリーダーで読み取ることが可能でしょう。しかし、スマートフォンの小さな画面にこれを表示した状態だと読み取りに失敗するリーダーが出てくるかもしれません。また、一般に情報の密度が高くなるほど読み取りにより多くの時間がかかるため、たとえ成功しても別のストレスが残る可能性もあります。

そういった事情を考え合わせると、今回のような目的で QR コードを利用する場合は、一件のコードに強引に多くのデータを押し込むのではなく、複数のコードに分けて利用することを前提に余裕を持ってデータを扱うほうが賢明でしょう。

実は上の表にも記述があるように、QR コードには一件のデータを最大 16 件のコードに分割して格納することの可能な「コード連結」という仕様があります。次の記事には分割 QR コードの実例が掲載されています。 「IT4206,QRコードの連結機能に対応していますか。」 - 株式会社エイポック様 公式サイトより -

残念ながらすべてのリーダー・ライブラリがこの機能に対応しているわけではなく、現在手元で使っているメジャーな ZXing のライブラリも未対応のようです。しかし、今回はリーダーだけではなくライターも自作することが前提なので、両者間で整合性のとれる内容で独自の分割プロトコルを用意すれば事足りると判断しました。また、二台の端末を操作しながら複数のコードを順番に処理していくのは想像するだけで非常に面倒なので、両者の連携にネットワーク通信を併用し、リーダーが正しくコードを読み取ったらライターが自動的に次のコードを表示することにしました。

ちなみに、先日の記事で操作性の良い QR コードリーダーを自作するために行った取り組みを紹介しましたが、そのきっかけは今回の一連の話題にありました。リーダーを自分で実装すれば上記のような細かい取り回しも柔軟に実現できるわけですね。QR コードの読み取りが主目的ではないアプリへ補助機能としてリーダー処理を組み込むことにはちょっと新鮮な印象があります。

試作と実験

そんなわけで次のような QR コードライターアプリと QR コードリーダーアプリを作ってみました。

  • ライターとリーダーは QR コードの出入力処理とネットワーク通信の併用によって連携する
  • リーダーは起動時に UDP ブロードキャストにより LAN 上のライターを探索、ライターはこれに呼応する
  • ライターはダミーの RSA 秘密鍵 PEM データを 200 文字ごとに分割しそれを格納した 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)
klab_gijutsu2 at 14:25|この記事のURLComments(0)TrackBack(0)
2013年04月15日

「ZXing QR コードスキャナー」の内部処理を追う

はてなブックマークに登録

ZXing ("Zebra Crossing") Team による「QR コードスキャナー (以下、"ZXing スキャナ"と略記) は操作性が良く人気の高い定番 Android アプリのひとつです。同アプリでは ZXing Team 自らが開発を継続しているオープンソースのバーコード処理用ライブラリが使用されています。
zxing Multi-format 1D/2D barcode image processing library with clients for Android, Java
このライブラリの優秀さはスマートフォンでバーコード/QR コードを処理する際の実質標準の座にある状況からも裏打ちされていますが、ZXing スキャナの「使いやすさ」は本ライブラリの性能のみに依るものではなくアプリの実装に大きく支えられています。ZXing スキャナのソースコードは公開されているので、それに学べば本家と同等の使いやすさを備えた QR コードリーダーの自作が可能となるはずですね。操作性の良いリーダーを自作できるのであれば QR コード読み取り機能をアプリへ組み込む際にわざわざ ZXing スキャナをインテントで呼び出す必要はなく、独自の処理を柔軟に組み込むこともできるでしょう。また、上記ライブラリを知り尽くした ZXing 謹製スキャナのコードはライブラリの性能を最大限に引き出すための最良のお手本となりそうです。

ZXing スキャナのソースコードを正面から分析した情報はほとんど見当たらないようですが、手元での調査を通じて得られた情報と、そのエッセンスを小さくまとめた形で試作した自作リーダーのリソース一式を公開します。なお、この記事では ZXing 2.1 Release 中の zxing-2.1/android/ 配下のソースコードを対象としています。

注目すべき要素

なぜ ZXing スキャナは QR コードリーダーとして使いやすいのでしょう?その要素こそが実装を調べる上で注目すべきポイントとなるでしょう。特長をみっつピックアップしてみます。

  • コードの認識が早い
    ターゲットにレンズを向けてじっとしていればすぐに認識が完了する
  • 対象とするコードをファインダ枠内に留める以外の操作が不要
    フォーカスを合わせたり画面をタップするといった操作をする必要がない
  • コード識別パターン検出位置を示すポイントが描画される
    プレビュー表示にコード識別パターンを発見した位置を示すポイント (ResultPoint) がリアルタイムで描画されるため端末の位置・角度を加減し易い
ResultPoint の描画

ソースコードの追跡とまとめ

コード読みの途中で迷子にならないための道具としてホワイトボード代わりにエクセルシートを使いました。あくまでも作業用なので決して見た目の良いものではありませんが記録としてそのまま掲載します。

   コードリーディング時のメモ

以下、ソースコードから得られた情報を整理してみます。

※クリックすると大きな図が開きます

  • プレビュー表示中に裏側で走っている太い処理の内容
    ※カメラには 2 秒ごとに autoFocus() が適用される
    1. 現在プレビュー表示中のフレームイメージを取得
    2. 取得したイメージをバックグラウンドスレッドへ渡す
    3. バックグラウンドスレッドはイメージを ZXing ライブラリ処理に渡す
    4. ライブラリはコードの検出過程で QR コード画像の識別パターンである可能性のある箇所(ResultPoint)を見つけるとその座標を UI 側へ逐次通知する
    5. ResultPoint を受け取とった UI 側はそれを表示中のプレビュー画面に重ねて描画する
    6. ライブラリ側処理が今回のフレームイメージから QR コードを検出しなかった場合、1. からの処理が繰り返される
    つまり、ZXing スキャナ は QR コードを見つけるまで絶えずフレームイメージの取得とその画像分析を繰り返している

  • 重要なクラスとその処理の概要
    - 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 を伝達する
    - com.google.zxing.client.android.camera
    • CameraManager
      カメラのオープン・クローズや PreviewCallback へのフレームイメージ取得指示など
    • PreviewCallback
      Camera.PreviewCallback の実装クラス。onPreviewFrame() 発生時に DecodeHandler へキャプチャイメージを渡す
    • AutoFocusManager
      バックグラウンドで 2 秒ごとにカメラに autoFocus() を適用
    • CameraConfigurationManager
      カメラまわりの解像度や各種パラメータの取得・設定

  • 使いやすさを支えているもの
    • 「コードの認識が早い」
      → プレビュー表示中、コードを検出するまで絶えずフレームイメージの取得とバックグラウンドスレッドでの分析を繰り返している
    • 「対象とするコードをファインダ枠内に留める以外の操作が不要」
      → カメラへのオートフォーカスの適用を効果的に利用している
    • 「コード識別パターン検出位置を示すポイントが描画される」
      → 検出位置を直ちに通知するためにライブラリの深部に用意されたコールバック機構と UI への描画処理を適切に組み合わせて使用している

リーダーの試作

以上のように、全体像が見えてしまえば ZXing スキャナの操作性を支えているのは意外なほどシンプルなしくみであることがわかります。それを自作のコードに組み込むことは難しくなさそうですね。そこで ZXing スキャナの実装に倣いつつできるだけ短いコードでざっくりとリーダーを作ってみることにしました。認識結果はダイアログ表示のみとしています。動作確認は一部の環境でのみ行っており、不具合があれば適宜手を入れて下さい。

MyQRCodeReader - github

ソースコードの一覧と概要を以下に示します。

  • 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)
klab_gijutsu2 at 17:00|この記事のURLComments(0)
2013年01月18日

親指サイズの USB 赤外線リモコンが面白い

はてなブックマークに登録

2013.01.29 追記    Mac OS X 用の試作コードを掲載しました

昨年末、調べごとをしていた時にちょっと気になる商品が目に留まりました。 株式会社ビット・トレード・ワン 様の「USB 接続 赤外線リモコン KIT」という製品です。

特徴をざっくりまとめてみるとこんな感じです。

  • [パソコンから家庭用機器をリモコン操作]、[リモコンでパソコンを操作] の2つの機能を持つ
  • 赤外線送信用のライブラリやツール・ファームウェアのソースコードが公開されている
  • 家電協/ NEC/ SONY の各リモコンコードフォーマットに対応
  • 某清涼菓子のケースにぴったり収まるサイズ
  • キットは 1,680 円、組立ずみ製品でも 2,480 円と低価格
  • PC 側対応 OS はWindows 7, Vista, XP
PC から制御可能な学習型赤外線リモコンといえば 2006 年の発売以来ロングセラーを続ける PC-OP-RS1 がとても有名ですが、このキットは昨年(2012年)発売された製品とのことで、ソフトウェア要素の多くがオープンであることに魅力を感じました。一方で 公式フォーラムを覗いてみるとエアコンまわりをはじめいろいろ制約もあるようで、それをどう考えるかは価格とのバランスの解釈次第でしょう。そういった話題も含めて好奇心をそそられ、年明け早々に入手しました。画像は「フリスク」と並べてみた様子です。

PC とは A−miniB タイプの USB ケーブルで接続。Windows 上でヒューマンインターフェイスデバイスとして認識され標準の HID ドライバが使用されます。

とりあえず使ってみると・・

さっそく専用の 送信用設定ツール に手元のいくつかの機器のリモコンコードを学習させ本キットから信号を出力し、それを元の機器が認識できるか否かを試してみると下の図の結果となりました。 続きを読む

klab_gijutsu2 at 11:09|この記事のURLComments(26)
2012年12月11日

Android の GCM をプライベートな目的に使う

はてなブックマークに登録

GCM (Google Cloud Messaging) は所定の端末へメッセージを送るための有用なしくみですが、何も一斉通知やソーシャルな用途ばかりではなくもちろんきわめて個人的な目的のためにも利用できます。今回はその方面でのアイディアをひとつざっくり形にしてみた例を紹介します。いろいろ応用もできるでしょう。

自分の端末を遠隔操作

Android 界隈は依然にぎやかで次々に新しい製品が発売されています。そのため複数の端末を持っている人も少なくないでしょう。まだまだ使える端末を遊ばせておくのはもったいないので、これを外出中の自宅の監視カメラとして使うことにしました。 端末を室内の対象物に向けて固定しておき、出先や仕事場から GCM 経由で端末へメッセージを送出、それをトリガーにアプリが撮影したスナップを Dropbox 経由で確認します。

シンプルな実装の割に結構役に立っています。 アプリ本体とソースコードを以下で公開しています。

RemoteWand - Google Play
RemoteWand - github

処理の流れ

端末の登録

遠隔操作の対象とする端末上で「RemoteWand」を起動して「登録」ボタンを押下すると GCM サーバへ端末が登録されトリガー発信用のフォームを含む HTML ファイルの添付された自分あてのメールが生成されます。

トリガーの送出〜撮影

PC やスマホのウェブブラウザに上記のフォームをロードし登録時に指定したパスワードを添えてサブミットするとアプリケーションサーバから GCM サーバへ要求が送出され GCM サーバが当該端末へ所定のメッセージをプッシュします。端末にインストールずみの RemoteWand はこのメッセージをトリガーにカメラで静止画の撮影を行います。Dropbox の「カメラアップロード」機能は自動的に撮影画像のアップロードを行うためあらかじめ端末にインストールしておけばリモートで簡単に写真を確認できます。


(tanabe)

klab_gijutsu2 at 15:39|この記事のURLComments(2)TrackBack(0)
2012年11月12日

Google Drive 上の文書をまとめて別アカウントへ移す方法

はてなブックマークに登録

Google Docs の文書はウェブブラウザから手軽に扱えるので何かと便利です。OS プラットフォームの違いを意識する必要がないため共同作業にも適していますね。
先日ちょっとした経緯から「アカウント A の Google Drive 上の Docs 文書・フォルダ群をまとめてアカウント B の環境へコピーするにはどうすればいいか?」というテーマに遭遇しました。次のみっつの条件つきです。

  • アカウント A の保持する既存のリソースの オーナーや共有設定の変更は不可
  • アカウント A の保持する既存のリソースに付与されている共有設定もコピーの対象とする
  • アカウント A とアカウント B のペアは複数存在する

今回はこの話題について考えた内容と、道具として作成したコードを紹介します。 続きを読む

klab_gijutsu2 at 10:07|この記事のURLComments(0)TrackBack(0)
2012年09月21日

Android のプッシュ通知用コネクションに関するメモ(補記)

はてなブックマークに登録

前回の記事では、Android のプッシュ通知機構 (GCM) が 「Google 所管のサーバと Android 端末との間の持続接続」 をどのように使用しているかを調べてみました。
DSAS 開発者の部屋 : 「Android のプッシュ通知用コネクションに関するメモ」
この記事の文中に、上記の持続接続が「プッシュ通知以外の用途にも利用されている様子でありそちらもおって調査したい」 と注釈を添えています。今回はその内容と関連する話題を控えます。

まとめ

端末と mtalk.google.com:5228 との間の接続はプッシュ通知以外に次の用途で使用される

  • Android 版 Google Talk クライアントのメッセージ送受信 (ボイスチャットを除く)
  • Google Play アプリのウェブページから端末へのプッシュインストール指示

- 他にもあるかも?
- 非公開の内部仕様につき今後変更される可能性も

Google Talk について

  • Android 版 Google Talk クライアントは、com.google.process.gapps (GoogleServicesFramework) プロセスの管理する 「mtalk.google.com:5228」 とのコネクションを使用する。このコネクションを使用できない場合はエラーとなり、独自に代替ポートが使用されることはない

  • ちなみに PC 版 Google Talk クライアント (googletalk.exe) はメッセージ送受信に 「talk.google.com:5222」 とのコネクションを使用する。5222 番ポートを使用できない場合は代わりに 「talk.google.com:443」 を使用する

  • 「mtalk.google.com」 には 「mobile-gtalk.l.google.com」 という別名がある。ネーミングから想像すると、このホストとのコネクションは本来モバイル端末において 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 ページからのプッシュインストールについて

    

  • Google Play アプリケーションページからのインストール指示は mtalk.google.com:5228 コネクション経由で端末へ送出されパッケージマネージャへ渡される

  • このプッシュインストールは感覚的には GCM によるプッシュ通知とあまり変わらないが、GCM の端末要件が Android 2.2 以上であるのに対し、Play ストアページからのプッシュインストールは 2.2 未満のバージョンでも機能するという違いがある

  • Android 1.6 環境を確認したところ、システムブート後に mtalk.google.com:5228 コネクションが自動的に確立する点は共通しているが、違いとして目に付くところでは 2.2 以降での com.google.process.gapps プロセスの実体である "/system/app/GoogleServicesFramework.apk" が存在しない。中核となる持続接続のハンドリング自体は旧バージョンにおいても実装されているわけで、C2DM, GCM の要件が 2.2 以上とされた背景にはおそらくはモジュール設計上の複合的な要因があるものと察せられる

mtalk.google.com:5228 に接続できない場合は?

端末上の com.google.process.gapps プロセスが 「mtalk.google.com:5228」 へコネクションを確立できない場合の所作を確認した

  • 実験環境のルータの設定で、「mtalk.google.com」 の既知の分散先サーバである 「173.194.72.188」 「74.125.31.188」 の 5228 番ポートへの LAN 上の各ノードからのアクセスを遮断。この状態で Android 端末を起動し観察を行った

  • 端末から mtalk.google.com:5228 へ接続不可につき connect タイムアウトまでステートは SYN_SENT のまま変化せず。タイムアウト後は何度かリトライが発生。対象は常に 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
                            :
    

  • 開発者向けの公式ページ に以下の記述が見られるためリトライ時に 5229 or 5230 番ポートが対象となることに期待したが今回の手順による観察ではその所作は見られなかった
     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.

  • 以上の結果から Android 端末の参加するネットワークでは必ず外向きの 5228 ポートへのアクセスを許可しなければならないことがわかる

  • この調査の過程で、「mtalk.google.com」 の分散先として、前出の 2 サーバに加え 「173.194.79.188」 の存在を確認した (2012 年 8 月末時点)

付録:端末上で拾ってみたパケットの様子

Google Talk     

プッシュインストール     


(tanabe)
klab_gijutsu2 at 13:36|この記事のURLComments(3)TrackBack(0)
2012年08月07日

Android のプッシュ通知用コネクションに関するメモ

はてなブックマークに登録

Android のプッシュ通知機構(GCM, 旧 C2DM)は有用なしくみですが、オープンソースではないソフトウェア要素が関わっているためか内部仕様に近い情報をあまり見かけないのが残念です。手元での観察結果をもとにプッシュ通知で使用されるネットワークコネクションまわりの情報をいくつかまとめてみました。

まとめ

  • Android 端末上の com.google.process.gapps プロセス は mtalk.google.com:5228 へ TCP コネクション [A] を張る
    (通常は 5228 番ポートだが 5229, 5230 番ポートが使用される場合もある)
  • com.google.process.gapps プロセスは基本的に [A] をずっと張りっ放しにしており接続維持のため無応酬 15分ごとに Keep-Alive パケットを流す
  • GCM, C2DM のプッシュ通知はいずれも [A] 経由で端末へ送られる

※[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)
klab_gijutsu2 at 17:20|この記事のURLComments(4)TrackBack(0)
2012年07月09日

Android パッケージインストール処理のしくみを追う

はてなブックマークに登録

身近な話題でありながら中身のよくわからないことを調べてみるのは興味ぶかいもので、そこから得た知識が意外なところで役に立つことも少なくありません。かねてより 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/ を使用しています。 続きを読む

klab_gijutsu2 at 12:45|この記事のURLComments(2)
2012年05月30日

inotify で Android 上のファイル I/O を監視する

はてなブックマークに登録

先日 Android のソースコードを読んでいた折に、所定の処理においてシステム上で発生するファイル I/O の流れを確認したいと思いました。処理の全体像を把握する上で有用と考えたためです。そこで inotify を利用することにしました。

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 でのビルド用に手を加えたプロジェクト一式を以下に公開します。

inotifywait_bin_android.zip
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)
klab_gijutsu2 at 16:51|この記事のURLComments(0)TrackBack(0)
2012年05月21日

Android アプリ「SundayPad」を公開しました

はてなブックマークに登録

2012-06-07: バージョン 1.0.1 を公開しました


予定のない休日の午後に 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
続きを読む
klab_gijutsu2 at 18:23|この記事のURLComments(2)TrackBack(0)
2011年12月26日

Android アプリ「AppNetBlocker」を公開しました

はてなブックマークに登録

(2015年5月追記)
この記事に掲載の「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 が役に立つかもしれません。 続きを読む

klab_gijutsu2 at 10:00|この記事のURLComments(33)
2011年10月25日

エンコードされた AndroidManifest.xml を読む

はてなブックマークに登録

アプリケーションマニフェストである AndroidManifest.xml をはじめ、Android アプリを構成する各種 XML ファイルは apk へのパッケージングの段階でパースされ独自のバイナリ形式にエンコードされます。 このファイルを扱う処理をコンパクトに実装したいと思ったのですが、現時点では形式に関する公式の資料が存在しないことがわかり aapt を参考に手元でフォーマットの分析を行いました。パーサ試作例とともにその内容を公開します。

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>
続きを読む
klab_gijutsu2 at 08:47|この記事のURLComments(0)TrackBack(0)
2011年09月29日

Android アプリケーションが起動するまでの流れ

はてなブックマークに登録

プログラム開発のために Android 上でアプリが起動するまでの過程を調べてみました。備忘をかねて、ソースコードをひと通り追跡した記録をここに控えます。

まとめ

※クリックすると大きな図が開きます

  • Zygote(ザイゴート)プロセスは、Android システムブート時に起動し DalvikVM 本体と Android プログラムの実行に必要なダイナミックリンクライブラリと Java のクラスライブラリをロードした状態で待機する常駐プロセスである
  • Zygote プロセスの目的は、同プロセスを fork することによりプログラム実行用のプロセス環境を素早く効率的にシステムへ提供することにある
  • UNIX ドメインソケット /dev/socket/zygote が Zygote プロセスへのインターフェイスであり、同ソケットにプロセス生成要求を送出すると Zygote はプロセス fork を実行する
  • system_server プロセスは同じくシステムブート時に起動するシステムプロセスである。Activity Manager をはじめ数多くの Android サービスはこの単一プロセス内で稼動する
  • ユーザが Android アプリ起動の操作を行うと Activity Manager 経由で Zygote にプロセス生成要求が送出される。ここで fork されるプロセスがアプリ用のプロセスとなる。つまり、すべての Android アプリケーションプロセスは Zygote の子プロセスである
  • アプリケーションプロセスの uid, gid は、当該アプリのインストール時にシステムが割り当てたものを Activity Manager が Zygote へのプロセス生成要求メッセージ内で指定し、Zygote がそれを設定するしくみ
  • Activity Manager 経由で Zygote から fork された アプリ用新規プロセスは、初期処理の段階で Activity Manager とのプロセス間通信を通じて起動対象アプリの情報を取得し、それをもとに所定のアプリをロード〜実行する。また、起動後のアプリケーションプロセスはライフサイクル管理を含め Activity Manager を中心とする文脈の中で一元管理される
※プロセスの親子関係に注意
$ 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月末現在)からの引用です。
続きを読む

klab_gijutsu2 at 14:09|この記事のURLComments(0)TrackBack(1)
2011年08月31日

Android 上のアプリから SSL クライアント認証の必要なサーバへアクセスする方法

はてなブックマークに登録

Android の Web ブラウザが SSL クライアント認証に対応していないのは不便だという話を以前このブログに書きました。記事では、Android 上の stone に SSL まわりの処理を代行させる方法について触れました。
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 でクライアント証明書を利用する手順等を紹介します。 続きを読む
klab_gijutsu2 at 10:06|この記事のURLComments(0)TrackBack(0)
2011年08月09日

root 化ずみ端末に対応した Android アプリを書く方法

はてなブックマークに登録

Android 端末を root 化するユーザが増えています。
一部の開発者向け製品以外では root 権限を取得するための公式の手段は提供されていないので、その方法を自分で探索するにせよ誰かが開拓した手順をトレースするにせよすべては自己責任であるわけですが、一方で root 権限を必要とするアプリはマーケットにもあれこれ出回っており、つまりはこの件に関する安全性と利便性のトレードオフに満足できない向きが世界中に大勢いるということでしょう。

その状況自体もいろいろ興味ぶかいのですが、ところで、その「root 権限を利用するアプリ」というものはどうやって書けばいいのでしょう?Google 公式の開発者向け資料は言うに及ばず、その他のリソースにも今のところほとんどこの話題に関する情報は見当たりません。そこで今回は、実際に手元のアプリを root 権限での実行に対応させる試みを通じて得た情報やノウハウを紹介したいと思います。 続きを読む

klab_gijutsu2 at 21:00|この記事のURLComments(4)TrackBack(0)
2011年06月24日

GUI 版「stone for Android」公開のお知らせ

はてなブックマークに登録

重要なお知らせ (2018年8月2日)

本アプリは Android NDK を使用してビルドした 32ビットのネイティブバイナリを含んでいます。 先般 Google はこのようなアプリの Google Play での扱いについて以下の発表を行いました。

この話題を受け今後の対応を検討してきましたが、現時点での結論として、本アプリの 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 インストールの可能なページ
続きを読む
klab_gijutsu2 at 17:55|この記事のURLComments(13)
2011年02月15日

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 の詳細については下記サイトの解説記事が参考になるでしょう。

stone 公式サイト:「Simple Repeater 'stone'」


(tanabe)
klab_gijutsu2 at 13:24|この記事のURLComments(8)TrackBack(0)
2010年12月13日

パケットリピータ「stone」を Android へポーティング

はてなブックマークに登録

オープンソースの多機能パケットリピータソフトウェア「stone」を Android 用にビルドしてみました。
当初は個人的に使用することが目的でしたが、原作者の仙石 浩明 (Hiroaki Sengoku) さんの了承のもと、ビルドずみバイナリを含むリソース一式を非正式版として公開します。他のプラットフォームで stone の便宜に馴染んでいる方はお試し下さい。なお、stone 本体の使用方法等についてはこの記事では触れません。公式サイトの解説記事を参照して下さい。 続きを読む
klab_gijutsu2 at 17:34|この記事のURLComments(4)TrackBack(0)
2010年12月02日

Android NDK でネイティブ CUI プログラムを書く!

はてなブックマークに登録

(2015年6月追記)
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 でさくさく書くのが粋というものだ!」

粋かどうかはさておき興味のある方はご覧下さい。ソースコードも掲載します。 続きを読む

klab_gijutsu2 at 21:22|この記事のURLComments(6)TrackBack(0)
2010年11月16日

Windows用フリーウェア「MyCloudFile」をバージョンアップしました (2010/11/16)

はてなブックマークに登録

先日このブログでご紹介した「MyCloudFile」をバージョンアップしました。

新しいバージョン 1.0.0.8 は、ご要望の多かった「プロキシサーバ経由での通信」に対応しています。

MyCloudFile のページをご参照の上、同ページより新しいバージョンをダウンロードしてご利用下さい。


(tanabe)
klab_gijutsu2 at 15:23|この記事のURLComments(0)TrackBack(0)
2010年10月15日

Windows用フリーウェア「MyCloudFile」を公開します

はてなブックマークに登録

※2015.04.02 追記
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 での動作を確認しています

クラウドを利用した手軽で安全なファイル保管ツールとして、また、有名な「宅ふぁいる便」のように、メールに添付するには大きすぎるファイルを共有するためのツールとして使うことができるでしょう。実験用に開発した試作版をフリーソフトとして公開します。以下の説明記事をよく読んでお試し下さい。 続きを読む
klab_gijutsu2 at 09:57|この記事のURLComments(27)TrackBack(0)
2010年10月01日

「このプログラムは正しくインストールされなかった可能性があります」を回避する方法 (Windows 7, Vista)

はてなブックマークに登録

Windows 7 や Vista で UAC を有効にしていると、たまに原因のよくわからないメッセージに遭遇することがあります。 プログラムをインストールした時に「このプログラムは正しくインストールされなかった可能性があります」というメッセージを目にしたことはありませんか?


実際にプログラムを動かしてみると異常のない場合が多いのですが、不審に思いネットを検索してみると非常に多くのページが見つかります。検索結果の見出しを眺めながらあれこれ覗いてみると、どうやらこのメッセージはソフトウェアの利用者だけでなく開発者にとってもちょっとしたジレンマのようです。
今回は、このメッセージが表示される原因とプログラム側での回避方法を調べてわかったことを書いてみます。 続きを読む
klab_gijutsu2 at 16:36|この記事のURLComments(8)
2010年06月22日

Windows実行形式のMachineタイプを判別する方法

はてなブックマークに登録

粗忽な性格の筆者の場合、Windows 上であるプログラムの 32 ビット版・64 ビット版のふたつを開発していると、ときどき「あれ、このバイナリはどっちだっけ??」という状態になることがあります。開発を行っているプラットフォームが 32 ビットであればクロスコンパイルした 64 ビットバイナリをそのまま実行することはできないためパッと判別できますが、64 ビット環境だとかしこい仮想機構 (WOW64: Windows 32bit On Windows 64bit) のおかげで 32ビットコードもすんなり実行できてしまうので逆にちょっと面倒です。

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)
klab_gijutsu2 at 19:59|この記事のURLComments(0)TrackBack(0)
2010年06月16日

Windows用フリーウェア「HookDate」をバージョンアップしました

はてなブックマークに登録

以前このブログでご紹介した「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)
2010年05月24日

Windows仮想プリンタプログラムを作ってみる

はてなブックマークに登録

普段よく使っているソフトウェアであっても、どういうしくみで機能を実現しているのかよくわからないものが結構あります。 筆者は主に Windows 環境で作業をしていますが、PDF ファイルを作成するたびに目にする「仮想プリンタ」もそのひとつでした。

システムは本物のプリンタだと信じているのに実はそれはソフトウェアへのインターフェイスにすぎず、印刷ジョブを渡したら最後、データは隅から隅までなめまわされ好きなように処理されてしまう。ということは、その気になればあんなことやこんなこともできてしまうはず・・・。 あらためて考えてみるとなかなか面白い話なので、仮想プリンタのしくみを調べて何かプログラムを書いてみたいと思いました。

手はじめに、定番の題材として所定のドキュメントを PDF や画像に変換しファイル出力する仮想プリンタを作ってみることにしました。 ひとつの仮想プリンタを自作のコードで構築し、データ出力部分を著名なオープンソースソフトウェアの GhostscriptRedMonImageMagick で処理する内容です。

作成したプログラムとそのソースコードをフリーソフトウェアとして公開します。 こういった具体的な開発情報は案外見あたりませんので興味のあるかたはご覧下さい。
MyVirtualPrinter - github.com/mkttanabe
プログラム開発には無償で利用できる以下のソフトウェアを使用しました。

  • Microsoft Visual C++ 2008 Express Edition (*)
  • ResEdit (*)

今回はこの MyVirtualPrinter を切り口に、Windows での印刷処理の概要と仮想プリンタをプログラムで構成する方法について説明します。続きを読む

klab_gijutsu2 at 11:27|この記事のURLComments(21)
2010年04月05日

起動不能なPCからインポートずみ証明書を救出せよ!

はてなブックマークに登録

毎日普通に使っている PC が突然起動できなくなるといろいろ困ったことになりますね。今回はそういう話のひとつです。

1. ことの始まり

先日、遠くの知人から「手元の PC で Windows XP を起動できなくなった」と連絡がありました。 最初のロゴ表示が終わったところでフリーズする状態で、セーフモードでの起動もできないそうです。

彼は言います。 「HDD を取り出し別の PC につないで最新版のデータファイルを全部拾い上げたので、普段使ってるソフトの数を考えればもとの環境の復旧に時間をかけるよりも新しい環境へデータを移行する方が早そうだ」
幸い HDD に物理的損傷はなかったようです。

よかったね今後は HDD のまるごとバックアップもこまめにやるべし、と答えると、「実はひとつだけ問題が残っている」とのことでした。

話はここから始まります。 続きを読む

klab_gijutsu2 at 18:00|この記事のURLComments(0)TrackBack(0)
2010年03月02日

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 ができるようになります。


@methane
klab_gijutsu2 at 18:31|この記事のURLComments(0)TrackBack(0)
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント