‘Programming’ カテゴリーのアーカイブ

PHPからarpテーブルを参照する

PHPからarpコマンドを実行してLAN内のMACアドレスとIPアドレスの変換テーブルを参照できるようにしてみました。単純に結果を正規表現にかけているだけです。

ちなみにひとつのMACが複数のIPを持つ場合には対応していません。それと、arpの性質上、頻繁にキャッシュクリアされるので、用途によっては注意が必要です。

<?php

$table = array();
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    exec('arp -a', $output); // for Windows
}
else {
    exec('/usr/sbin/arp -a -n', $output); // for Linux(debian)
}
foreach ($output as $line) {
    if (preg_match('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*([0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2})/i', $line, $matches)) {
        $ip = $matches[1];
        $mac = strtoupper(str_replace(array('-', ':'), '', $matches[2]));
        $table[$mac] = $ip;
    }
}

var_dump($table);

結果

array(2) {
  ["123456789ABC"]=>
  string(13) "192.168.0.1"
  ["0003FFFFFFFF"]=>
  string(15) "192.168.131.254"
}

Linuxのarpコマンドがarp -aで実行するとすごく遅いので、原因を調べていたら、デフォルトではIPアドレスからDNSの逆引きをしているということでした。-nオプションを付けることで、DNSリクエストを送らなくなるので一瞬で表示できます。


MediaPlayerで通知音を鳴らす

AndroidのMediaPlayerで通知音を鳴らすときに、音量設定を反映させます。

MediaPlayer.createメソッドでインスタンスを作成した状態だと、「メディア音量」というものが反映されるので、setAudioStreamTypeメソッドで再生するオーディオのタイプを指定してやります。これでシステムの設定画面で指定した音量が反映されます。

が、setAudioStreamTypeメソッドをMediaPlayer.createで作成したインスタンスに対して実行すると、
error (-38, 0)
とか
prepareAsync called in state 8
とか
setAudioStream called in state 8
とかエラーが起こります。

これはMediaPlayer.createメソッドが、内部的にすでにprepare(準備)メソッドを呼んでいるためで、ストリームタイプの変更は、準備の前に実行しておく必要があります。

コードはこんな感じになりました。

    public static void playSound(Context context, String url) {

        if (url != null) {
            Uri uri = Uri.parse(url);
            // MediaPlayer.createはprepareを実行してしまうのでnew MediaPlayer()を使う
            // MediaPlayer mp = MediaPlayer.create(context, uri);
            MediaPlayer mp = new MediaPlayer(); 
            try {
                mp.setDataSource(context, uri);
                // setAudioStreamTypeはprepare前に実行する必要がある
                mp.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); 
                mp.setLooping(false); 
                // prepareの前後で使えるメソッドが異なる
                mp.prepare(); 
                mp.seekTo(0);
                mp.start(); 
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

ちなみにマナーモードのときは、何も状態をチェックしなくてもストリームタイプを通知音にしておけば、音は鳴りません。MediaPlayer.createで作ると、メディアになってしまうので、マナーモードが反映されません。

それと、一部機種(音が長いと?)ではsetDataSourceが失敗する場合があるそうです。詳しくは以下。

Ringtoneを再生する時にFileDescriptorを使いたい – 日本Androidの会 | Google グループ


タスクスケジューラとPHPのコンソールアプリケーション

PHP5.3+Silexを使って、Windows上で定期的に処理をするコンソールアプリケーションを作っていたのですが、タスクスケジューラが起動するタイミングで、デスクトップにコマンドプロンプトのウィンドウ(DOS窓)が表示されてしまう現象に悩んでいました。

ウィンドウを表示させない方法として、ショートカットを作成してウィンドウ最小化するという手で試してみたのですが、taskeng.exeというタイトルのウィンドウが出たままになって止まってしまう状況でした。(多分PHPファイルを引数として認識していない)

結論としては簡単で、php.exeの代わりにphp-win.exeを使うだけです。

PHP: CLI と CGI – Manual

php-win.exeはコンソールを表示させないので、これを実行ファイルとしてタスクスケジューラに登録すればOKです。

ついでにタスクスケジューラに登録する方法も書いておきます。5分おきにPHPを実行し続けるタスクをコマンドプロンプトから登録するコマンドです。最後の/Fは確認メッセージの抑制。

schtasks /create /tn "Test PHP" /tr "C:\php\php-win.exe C:\hoge\console.php args" /sc minute /mo 5 /it /F

削除はこちら。

schtasks /delete /tn "Test PHP" /f


Silexで外部APIにHTTP Requestする

最近Silex始めました。

まずは外部のWebServerにリクエストを投げる方法です。軽量なWebClientであるBuzzを使います。gitでBuzz本体とProviderをダウンロードします。

$ cd vendor/
$ git clone https://github.com/kriswallsmith/Buzz.git Buzz
$ cd Buzz/
$ git checkout v0.5
$ git submodule update --init
$ cd ..
$ git clone https://github.com/marcw/silex-buzz-extension.git silex-buzz-extension

src/bootstrap.phpに追加します。

$app['autoloader']->registerNamespace('MarcW', __DIR__ . '/../vendor/silex-buzz-extension/lib');

$app->register(new MarcW\Silex\Provider\BuzzServiceProvider(), array(
    'buzz.options' => array(
    ),
    'buzz.class_path' => __DIR__ . '/../vendor/Buzz/lib',
));

これで準備完了です。あとはアプリケーションから

$app['buzz']->getClient()->setTimeout(30);
$response = $app['buzz']->get('http://www.google.co.jp');

echo $app['buzz']->getLastRequest()."\n";
echo $response;

として呼び出すことができます。Buzzのデフォルトのタイムアウトが5秒で、早すぎたので30秒に変更しておきました。


Androidのキーストアから証明書をエクスポートする

業務でAndroid開発をしていて、都合上、後から鍵ペアを人に渡さなければいけない場合があります。

新しいapkのたびにキーストアファイルを作っているのなら、別にそれごと渡してしまえばいいのですが、自分用のキーストアに作ってしまった場合、キーストアごと渡すわけにはいきません。

そこでキーストアファイルから証明書をエクスポートして相手に渡すことを考えますが、apkに署名するには、証明書の他に秘密鍵が必要です。

先に結論だけ書いておきます。keytoolコマンドのimportkeystoreを使います。

> keytool -importkeystore -srckeystore .\mykeystore.jks -srcstorepass <<自分のキーストアのパスワード>> -destkeystore app1.jks -deststorepass <<新規作成するキーストアのパスワード>> -deststoretype jks -srcalias app1
<app1> の鍵パスワードを入力してください。

これでapp1.jksというキーストアファイルが新規作成され、自分のキーストアの中から特定のエイリアスの鍵ペアだけをコピーできます。

以下は説明です。

例えば自分用のキーストアの中身がこうなっていたとします。

> keytool -list -v -keystore .\mykeystore.jks -storepass <<キーストアのパスワード>>

キーストアのタイプ: JKS
キーストアのプロバイダ: SUN

キーストアには 2 エントリが含まれます。

別名: app1
作成日: 2012/03/08
エントリタイプ: PrivateKeyEntry
証明連鎖の長さ: 1
証明書[1]:
所有者: C=JP
発行者: C=JP
シリアル番号: 4f582f00
有効期間の開始日: Thu Mar 08 00:00:00 JST 2012 終了日: Wed Jul 10 00:00:00 JST 3011
証明書のフィンガープリント:
         MD5:  00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         SHA1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         署名アルゴリズム名: SHA1withRSA
         バージョン: 3


*******************************************
*******************************************


別名: app2
作成日: 2012/03/08
エントリタイプ: PrivateKeyEntry
証明連鎖の長さ: 1
証明書[1]:
所有者: C=JP
発行者: C=JP
シリアル番号: 4f57f300
有効期間の開始日: Thu Mar 08 00:00:00 JST 2012 終了日: Wed Jul 10 00:00:00 JST 3011
証明書のフィンガープリント:
         MD5:  00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         SHA1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         署名アルゴリズム名: SHA1withRSA
         バージョン: 3

証明書のエクスポートとインポートは以下のコマンドでできますが、秘密鍵がエクスポートできません。

> keytool -exportcert -keystore .\mykeystore.jks -storepass <<キーストアのパスワード>> -alias app1 -file .\app1.cer
証明書がファイル <.\app1.cer> に保存されました。

> keytool -import -keystore app1.jks -storepass <<新規作成するキーストアのパスワード>> -file .\app1.cer
所有者: C=JP
発行者: C=JP
シリアル番号: 4f582f00
有効期間の開始日: Thu Mar 08 00:00:00 JST 2012 終了日: Wed Jul 10 00:00:00 JST 3011
証明書のフィンガープリント:
         MD5:  00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         SHA1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         署名アルゴリズム名: SHA1withRSA
         バージョン: 3
この証明書を信頼しますか? [no]:  yes
証明書がキーストアに追加されました。

この状態のキーストアはこうなっています。

> keytool -v -list -keystore .\app1.jks -storepass <<キーストアのパスワード>>

キーストアのタイプ: JKS
キーストアのプロバイダ: SUN

キーストアには 1 エントリが含まれます。

別名: app1
作成日: 2012/03/31
エントリのタイプ: trustedCertEntry

所有者: C=JP
発行者: C=JP
シリアル番号: 4f582f00
有効期間の開始日: Thu Mar 08 00:00:00 JST 2012 終了日: Wed Jul 10 00:00:00 JST 3011
証明書のフィンガープリント:
         MD5:  00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         SHA1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         署名アルゴリズム名: SHA1withRSA
         バージョン: 3


*******************************************
*******************************************

エントリのタイプがtrustedCertEntryになっています。このエントリでapkを署名しようとするとjarsignerがエラーを出します。

jarsigner: 次の証明連鎖が見つかりません: app1。  app1 は、非公開鍵および対応する公開鍵証明連鎖を含む有効な KeyStore 鍵エントリを参照する必要があります。
(英語の場合)
jarsigner: Certificate chain not found for: app1.  app1 must reference a valid KeyStore key entry containing a private key and corresponding public key certificate chain.

Eclipseの場合は、何も言われず、Finishボタンが押せません。

ここで最初の結論のところで書いたimportkeystoreを使って、キーストアからキーストアへのインポートをすることで、秘密鍵を持ったまま新しいキーストアに証明書をコピーできます。コピー後のキーストアファイルはこうなります。

> keytool -list -v -keystore .\app1.jks -storepass <<キーストアのパスワード>>

キーストアのタイプ: JKS
キーストアのプロバイダ: SUN

キーストアには 1 エントリが含まれます。

別名: app1
作成日: 2012/04/01
エントリタイプ: PrivateKeyEntry
証明連鎖の長さ: 1
証明書[1]:
所有者: C=JP
発行者: C=JP
シリアル番号: 4f582f00
有効期間の開始日: Thu Mar 08 00:00:00 JST 2012 終了日: Wed Jul 10 00:00:00 JST 3011
証明書のフィンガープリント:
         MD5:  00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         SHA1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
         署名アルゴリズム名: SHA1withRSA
         バージョン: 3

エントリタイプがPrivateKeyEntryになっていれば、通常通りapkの署名に使えます。

あと、importkeystoreが使えるのはJDK6以降のようなので、バージョンは確認してください。


sfGuardFormSigninのメッセージをカスタマイズする

Symfony1.4でsfDoctrineGuardPluginを使っていて、ユーザ名かパスワードが違うときに、

The username and/or password is invalid.

と表示されてしまうので、これを変更したいと思います。それと、ラベルも日本語にしておきたいと思います。

sfGuardFormSigninを継承したフォームクラスをひとつ作ります。今回は/app/front/modules/sfGuardAuth/lib/form/mySigninForm.class.phpに作りました。

<?php

class mySigninForm extends sfGuardFormSignin
{
    public function configure()
    {
        parent::configure();
        
        $this->widgetSchema['username']->setLabel('ユーザID');
        $this->widgetSchema['password']->setLabel('パスワード');
        
        $this->validatorSchema['username']->setMessage('required', '入力して下さい');
        // $this->validatorSchema['username']->setMessage('invalid', 'ユーザIDかパスワードが違います'); // 効かない
        $this->validatorSchema['password']->setMessage('required', '入力して下さい');
        
        $validator = $this->getValidatorSchema()->getPostValidator();
        $validator->setMessage('invalid', 'ユーザIDかパスワードが違います');
    }
}

PostValidatorを取得してくるのが肝です。前述のメッセージはsfGuardValidatorUserで定義されているのですが、このバリデーターはBasesfGuardFormSigninでsetPostValidator(new sfGuardValidatorUser())として呼ばれているためです。

ちなみに、mySigninFormを有効にするには、/app/front/config/app.ymlに

all:
  sf_guard_plugin:
    signin_form: mySigninForm

を追加して、デフォルトのフォームクラスを変更する必要があります。


Windows 7 x64にMercurialからgitを操作できるhg-gitをインストールする

Windows 7 x64にhg-gitをeasy_installから入れようとして、ハマったことのメモです。

hg-gitについてはこちら。
Hg-Git Mercurial Plugin

コマンドプロンプトから
C:\> C:\Python27\Scripts\easy_install.exe hg-git
C:\> C:\Python27\Scripts\easy_install.exe -Z hg-git
でインストールできます。(-Zオプションを付けないと、hg_git-0.3.2-py2.7.eggに圧縮されてしまうので、あとでmercurial.iniにパスを指定できなくなる)

error: Setup script exited with error: Unable to find vcvarsall.bat

まずはvcvarsall.batが見つからないと怒られます。これはVisual C++ 2008 Express Edition with SP1をインストールすればOKです。2010は使えないようなので、2008をインストールします。
Visual Studio 2008 Express | Microsoft Visual Studio
ダウンロードリストからC++を選んでダウンロードしてください。

C:\Python27>Scripts\easy_install.exe hg-git
Searching for hg-git
Best match: hg-git 0.3.2
Processing hg_git-0.3.2-py2.7.egg
hg-git 0.3.2 is already the active version in easy-install.pth

中略

  File "C:\Python27\lib\distutils\msvc9compiler.py", line 299, in query_vcvarsall
    raise ValueError(str(list(result.keys())))
ValueError: [u'path']

で、次はValueError: [u’path’]のエラーが出たので、調べると、PythonのIssueに上がっている問題のようです。パッチが出ていますが、未だに取り込まれていない様子。

Issue 7511: msvc9compiler.py: ValueError when trying to compile with VC Express – Python tracker

ここから、vcvars4.diff(最新のdiff)をダウンロードします。

パッチを充てたいので、Windowsで動くpatchを探すと、

UnxUtils | Free software downloads at SourceForge.net

ここからダウンロードできるようです。

が、なぜかpatch.exeが管理者権限を要求するので、ちょっと怖いので結局手作業でmsvc9compiler.pyをdiffに従って編集しました。まぁ、大した量ではないので…



2012/04/18追記:

コメント欄で原因と対策を教えていただきました。Windowsがpatch.exeというファイル名を勝手にブロックするようです。patchという文字列が入っていて、実行可能形式の拡張子(exeとかscrとか)だと発動するようなので、p.exeなどにファイル名を変更すれば大丈夫です。

パッチを当てられないWindows VISTA

ちなみに、install.exeやsetup.exe、update.exeなども同じ原因で権限を要求されます。

MSDNでの公式情報はこちら。

New UAC Technologies for Windows Vista

で、再度実行すると、また新しいエラーが出ました。

Reading http://launchpad.net/dulwich
Best match: dulwich 0.8.3
Downloading http://samba.org/~jelmer/dulwich/dulwich-0.8.3.tar.gz
Processing dulwich-0.8.3.tar.gz
Running dulwich-0.8.3\setup.py -q bdist_egg --dist-dir c:\users\***\appdata\local\temp\easy_install-ghb21p\dulwich-0.8.3\egg-dist-tmp-sc0we9
error: Setup script exited with error: Visual Studio Express: need 64-bit tools from the SDK

Windows SDKの64bit版C++ Compilerをインストールしろ、ということなのですが、罠があります。最新のSDKをインストールしても解消しません。

MathWorks 日本 – How can I set up Microsoft Visual Studio 2008 Express Edition for use with MATLAB 7.7 (R2008b) on 64-bit Windows? – MATLAB & Simulink

こちらの情報によると、「Windows SDK for Windows Server 2008 and .NET Framework 3.5」か、「Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1」でないとダメのようです。

今回は「Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1」をインストールして成功しました。SDKのインストールの最後に致命的エラーとか表示されましたが、特に問題はなかったようです。

これで再度、easy_install hg_gitを実行すれば、

Installed c:\python27\lib\site-packages\dulwich-0.8.3-py2.7-win-amd64.egg

Finished processing dependencies for hg-git

となって、インストールがやっと成功しました。

最後に%userprofile%\mercurial.iniを開いて、

[extensions]

bookmarks =

hggit = C:\Python27\Lib\site-packages\hg_git-0.3.2-py2.7.egg\hggit

のようにhggitの場所を指定してやればOKです。


Conversion to Dalvik format failed with error 1

[2012-03-05 08:32:06 - Test] Dx 
trouble processing "javax/net/SocketFactory.class":

Ill-advised or mistaken usage of a core class (java.* or javax.*)
when not building a core library.

This is often due to inadvertently including a core library file
in your application's project, when using an IDE (such as
Eclipse). If you are sure you're not intentionally defining a
core class, then this is the most likely explanation of what's
going on.

However, you might actually be trying to define a class in a core
namespace, the source of which you may have taken, for example,
from a non-Android virtual machine project. This will most
assuredly not work. At a minimum, it jeopardizes the
compatibility of your app with future versions of the platform.
It is also often of questionable legality.

If you really intend to build a core library -- which is only
appropriate as part of creating a full virtual machine
distribution, as opposed to compiling an application -- then use
the "--core-library" option to suppress this error message.

If you go ahead and use "--core-library" but are in fact
building an application, then be forewarned that your application
will still fail to build or run, at some point. Please be
prepared for angry customers who find, for example, that your
application ceases to function once they upgrade their operating
system. You will be to blame for this problem.

If you are legitimately using some code that happens to be in a
core package, then the easiest safe alternative you have is to
repackage that code. That is, move the classes in question into
your own package namespace. This means that they will never be in
conflict with core system classes. JarJar is a tool that may help
you in this endeavor. If you find that you cannot do this, then
that is an indication that the path you are on will ultimately
lead to pain, suffering, grief, and lamentation.

[2012-03-05 08:32:06 - Test] Dx 1 error; aborting
[2012-03-05 08:32:06 - Test] Conversion to Dalvik format failed with error 1

Package Explorerからプロジェクトを右クリックして、Properties選択。

Java Build Path→Librariesを開いて、表示されているライブラリを全部Remove。

OKでウィンドウを閉じて、再度Package Explorerを右クリックして、Android ToolsからFix Project Propertiesを選択。

解決。


WindowsにISC BIND9をNSISでインストールする

WindowsでDNSサーバを立てるにあたってBINDを使ったのですが、標準のBINDInstall.exeが使いにくいのと、サイレントインストールしたかったので、NSISを使ってインストーラーを作る方向にしました。

基本的には全部展開して設置したあと、VC2005再頒布可能パッケージをインストールして、named.exeをサービス登録するだけです。

ファイアウォールの許可もしています。

プラグインを3つ使っています。
Nsisunz plug-in – NSIS Zipの展開
NSIS Simple Service Plugin – NSIS サービス登録
NSIS Simple Firewall Plugin – NSIS ファイアウォール登録
Zipを二重にするのももったいないので、あらかじめ展開しておくのが妥当かとは思います。

設定ファイルは適当に作ってください。

あと肝心のBINDはこちら。
BIND | Internet Systems Consortium

!include "MUI.nsh"
!include "Sections.nsh"

!define TEMP $R0
!define TEMP2 $R1
!define VAL1 $R2
!define VAL2 $R3

Name "BIND9 Installer"
OutFile "setup.exe"

InstallDir "C:\dns"

!define BIND9_FILENAME "BIND9.8.1-P1.zip"

Section auto_setup
  
  SetOutPath "$TEMP"
  File "${BIND9_FILENAME}"
  
  SetOutPath "$INSTDIR\bin"
  nsisunz::Unzip "$TEMP\${BIND9_FILENAME}" "$INSTDIR\bin"
  
  ; VC++ 2005 redistインストール
  ExecWait '"$INSTDIR\bin\vcredist_x86.exe" /q:a /c:"VCREDI~1.EXE /q:a /c:""msiexec /i vcredist.msi /qb!"" "' 
  
  ; BINDのインストールディレクトリをレジストリに登録
  WriteRegStr HKLM "SOFTWARE\ISC\BIND" "InstallDir" "$INSTDIR"
  
  ; etcとconfに設定ファイルを配置する
  SetOutPath "$INSTDIR\conf"
  File /r "conf\*.txt"
  
  SetOutPath "$INSTDIR\etc"
  File "named.conf"
  ExecWait '"$INSTDIR\bin\rndc-confgen" -a -b 512'
  
  ; Firewall許可
  SimpleFC::AddApplication "ISC BIND" "$INSTDIR\bin\named.exe" 0 2 "" 1
  
  ; BINDをサービスに登録
  SimpleSC::InstallService "BIND" "ISC BIND" "16" "2" "$INSTDIR\bin\named.exe" "" "NT AUTHORITY\NetworkService" ""
  
  ; BINDサービス起動
  SimpleSC::StartService "BIND" "" 30

SectionEnd

Section "Uninstall"
  
  ; サービス停止と解除
  SimpleSC::StopService "BIND" 1 30
  SimpleSC::RemoveService "BIND"
  
  ; レジストリ削除
  DeleteRegKey HKLM "SOFTWARE\ISC\BIND"
  
  ; Firewall解除
  SimpleFC::RemoveApplication "$INSTDIR\bin\named.exe"
  
  ; ファイル削除
  RMDir /r "$INSTDIR"
  
SectionEnd


URLGrabberでFatal Python error: deallocating None

URLGrabberで数千件のURLをkeep-aliveで取得しようとすると、大体同じような回数でFatal Python error: deallocating Noneが出ることがあります。

どうやら、PycURLの参照カウントの問題らしいです。

パッケージでインストールされるものは古いので、PycURLを最新のソースからインストールしなおしましょう。以下、環境はDebian squeeze+python 2.6。

python-pycurlをすでにインストールしている場合はアンインストールしておく必要があります。

# aptitude install python-pycurl
でインストールしている場合は、
# aptitude purge python-pycurl
でアンインストールしておきましょう。

pycurlは最新のものをsourceforgeのCVSからダウンロードします。

SourceForge.net Repository – [pycurl] Index of /pycurl
下の方にある、「Download GNU tarball」のリンクからtarファイルをダウンロードできます。
ダウンロードしたファイルをpycurl.tar.gzなどに名前変更して、tar xvzf pycurl.tar.gzで展開できます。

ビルドにはcurl-configとPython.hなどが必要なので、必要なパッケージをインストールしておきます。

# aptitude install python2.6-dev libcurl4-gnutls-dev

pycurl.tar.gzを展開したら
# cd ./pycurl/
# python setup.py install
で、インストール完了です。

ちなみに、python2.6-devをインストールしていないと、
src/pycurl.c:42:20: error: Python.h
のようなエラーが大量に出ます。

とりあえずこれで、今のところ安定動作しています。