d.sunnyone.org
sunnyone.org

ページ

2016-11-02

Vue.js 1.x から 2.0 への移行のポイント

先日Vue.js 1.xから2.0への移行を行った。まるっと書き換えないといけないというレベルではないが、メジャーバージョンアップだなぁと感じる程には違いを感じられたのでいくつかポイントを書いておく。

もちろん、Vue.jsの機能を全て使いこなしているわけではないため、すべて網羅しているとはとても考えていないので、詳しくは「Migration from Vue 1.x」を参照してほしい。

大きな違い:テンプレートが事前コンパイルできるようになった

Vue 1.xと2.xの大きな違いは、テンプレートのコンパイルと実行を分離できるようになったこと。実際に移行してみた感じ、このコンパイル処理の邪魔になるようなことに関しては排除するような形で変更されているように見える。

このコンパイルというのがどのようなものかは、browserify + vueifyの処理結果を見るとわかりやすい。

Single File Components (.vue)という形でコンポーネントを記述した場合、スクリプトやスタイル、テンプレートを同一のファイルに記述できる。browserifyを使う場合、vueifyというプラグインがこの.vueをJavaScript から読める形に変換してくれるのだが、この処理結果が1.xから2.xで大きく変わっている。

コンパイル対象のテンプレートが以下だとする。

Vue 1.xの場合は以下の形。

template変数に文字列として代入するだけになっている。

Vue 2.xの場合は以下の形。

render関数に、DOM要素を作っていくような関数がセットされていることがわかる。JSX中の要素がReact.createElement関数に変換される構造に近い。

移行の方法

Migration from Vue 1.x」の頭のほうに書いてあるが、vue-migration-helperを実行し表示されたメッセージの対処後、実行時に表示される警告を見つつ対処していく形になる。

移行時に変更する必要のあった点

browserify + vueifyで利用しているため、それを前提とする。webpackでも似たようなことをしないといけないはず。

npmインストールのデフォルトがRuntime-only Buildとなることへの対応

npmを利用すると、「Runtime-only build」を利用するようにインストールされる(Standalone vs. Runtime-only Build)。このRuntime-only buildは、template: によるテンプレートコンパイルが行えないため、HTML中にテンプレートを書いて実行する方式で実装されているアプリケーションは実行できなくなる。対処する方法は2通り。

render関数の実装(Runtime-only buildで実行できるようにする)

「Failed to mount component: template or render function not defined」と言われるので、その通りrender関数をnew Vue()の指定につける。この場合、ルートとなるコンポーネントも.vueに記述し、HTML側にはマウントされる要素しか用意しない。

HTMLは、

などとし、.js側は

というようにする。

SPA的なアプリケーションであれば十分であるかもしれないが、ちょいちょいVueを利用しているようなアプリケーションの場合、マウントの呼び出しがちょいちょいあるということなので、propsを受け取れるようなマウント関数を作っておくと便利である。


詳しくは「Render Functions」を参照。

Standalone buildの利用

既存のHTML中のテンプレートもコンパイルする場合、Standalone buildを利用するように変更する。

browserify + vueifyの場合は、aliasifyによってimport Vue from "vue"の動きを書き換える

まずaliasifyをインストール。
$ npm install aliasify --save-dev

package.jsonに以下の記述を追加。

browserifyの呼び出しに「.transform(aliasify)」を追加する。

属性中の{{ }}をv-bindと式に変更する

属性中の{{ hoge }}は解釈されなくなったため、v-bindと式に変更する必要がある。

今まではこのような形だったものは


このように変更する必要がある。


v-model中のフィルタの廃止

今まではv-model="obj.val | filter"という形で、v-modelにもフィルタが適用できていたのだが、これが廃止になった。

「v-model="something"」が「<input v-bind:value="something" v-on:input="something = $event.target.value">」のsyntax sugarである(Form Input Components using Custom Events)ということを利用して、コンポーネントを作る必要がある。

実際の例は、Migration from Vue 1.xに書いてあるが、v-modelに対するフィルタに比べると手間なので、多用していた場合は、結構痛いと思う。

Vue.config.delimitersをコンポーネントレベルに変更する

Vue.config.delimitersは廃止になり、"{{"と"}}"の変更はコンポーネントレベルでしか受け付けなくなった。

サーバサイドテンプレートとの混乱を避けるため、デリミタは変更していたのだが、このような場合new Vue()の呼び出しにそれぞれつける必要がある。

ただし、先述の通りvueifyがテンプレートをコンパイルするようになったのだが、vueifyが.vueに設定されたdelimitersを解釈しないため、変更ができなくなった。そのため、.vueで変更されたデリミタを使用している場合、デリミタ自体を全て書き換える必要がある。

propsでのJavaScript予約語の利用不可

コンパイル結果を見たらそれはそうかという感じだが、コンポーネントプロパティに予約語を使うと怒られるようになった。地味に痛いのが"class"で、注意が必要。

readyライフサイクルイベントをmounted+αに

"ready"ライフサイクルイベントが廃止になった。

一番近いのがmountedで、必要であればvm.$nextTickでDOM要素が構築されてから処理を実行する必要がある。

v-for中の$indexの代わりに(value, index)で受ける

$indexは削除されたので、(value, index) in valuesの形で受ける必要がある。

v-elとv-refはref="hoge"とvm.$refs.hogeへ

テンプレート中のDOM要素及びコンポーネントをスクリプトから利用できるv-refはref属性に変更になった。ref="hogehoge"とつけると、this.$refs.hogehogeと参照できる。

transitionの構造変更

transitionの構造が変更になった。

今までは"transition"属性をつけると適宜v-transition, v-enter, v-leaveクラスが付与されるという形だったが、transition要素で囲むと〜-enter, 〜-enter-active, 〜-leave, 〜-leave-activeが付与されるようになった。

タイミングも違うので、「Transition Classes」の図を見て対処する必要がある。

おわりに

自分のところではまったのはこんな感じ。ガイドはもっと長いので、もっと大きなアプリケーションでは他にもあると思う。感想としては、それ削らなくてもいいじゃないという点もあるが、基本的にはテンプレートコンパイル結果を見て納得という感じだった。

Migration from Vue 1.x」の他に、Vue.js 2 アップデートメモも参考になる。

2016-08-08

Java → Kotlin 連携時は検査例外に注意する

Kotlinには検査例外はない(Kotlin does not have checked exceptions)。しかし、Javaにおいては検査例外として取り扱われるクラスを普通にthrowすることができる。

コード例にすると、以下の通り。




もともとJavaでも宣言していなくても検査例外を投げてくるときは投げてくる(ERR06-J. 宣言されていないチェック例外をスローしない)が、throwsが目印にもならないという点で注意が必要である。

2016-06-15

Rust で Git クライアントを書き始めた

Twitterでちょいちょいスクリーンショットをあげてたので、もはや見慣れた人もいるかもしれないですが、Xで使うためのGitクライアントを書き始めました。
https://github.com/sunnyone/metal-git

リリースにUbuntu 14.04でビルドしたバイナリが置いてありますが、おまけレベルです。


言語はRust。以前にチュートリアルっぽいものを書いたgtk-rsと、libgit2のラッパーであるgit2-rsを使って書いています。最初はコマンドのラッパーで行こうかなと思ったけど、今にして思うとgit2-rsの手助けがあって本当によかった。

Rustはまだまだ慣れてないので、ここはこうするとすっきり行くみたいなのは、細かいところでも教えてもらえるとうれしいです(ただマクロは控えられるところは控えようかなと思っていますが)。

もうちょっとそこそこ使えるようになってから公開しようかなとも思いましたが、まぁコミットの部分はとりあえず使えるようになったので書き始めたということで出そうかなと。無駄なコードが多いので結構恥ずかしいのだけど。

一応gtk-rsもgit2-rsのどちらもmingwでビルドできるみたいなことは見たので、Windowsでもビルドできるかもしれませんが、とりあえず当面は考えない方向でいきます。

ひとまずMetal Gitという名前にしました。良い名前があれば変えるかもしれません。

---

今のソースの規模はこんな感じ。モデルの分離もうまくできていないし、もう少し分割したい。
metal-git/src$ wc -l *.rs
  621 commit_window.rs
   81 gobject_utils.rs
   70 gtk_utils.rs
  166 history_window.rs
   40 main.rs
  337 railway.rs
   29 repository_manager.rs
   16 static_resource.rs
  241 station_cell_renderer.rs
   98 station_renderer.rs
  107 station_wrapper.rs
   62 window_manager.rs

作り始めたのはこれがきっかけなので、なんやかんやもう3ヶ月。長かったなぁ。


2016-05-10

Xubuntu 16.04 をインストールして最初に行う設定

Xubuntuは、XFceを使ったUbuntuのフレーバー。ここからダウンロードできる。デフォルトではGNOME2ライクな見た目の画面が使える。


そのままでも使えるのだが、自分はもうちょっとWindowsライクに使いたいので、いつもやっている設定を書いておく。

パネルを下側に移動する(いわゆる「タスクバー」を下に)

デフォルトでは上側にある黒いバー「パネル」には、デフォルトではアプリケーションを呼び出すメニュー、ウィンドウ一覧、インジケータ、時計が置いてある。要はタスクバーのようなものだ。これを下側に移動する。


下側に移動するには、パネルを右クリックし、パネル>パネルの設定を選択し、「パネル」ウィンドウを開く。

そして「パネルをロックする」チェックボックスをオフにして、パネル左側に出る取っ手を掴んで下に移動する。

Whisker Menu を押しやすくする(「スタート」ボタンを作る)

左下にある(左下に移動された)青いボタンを押すといわゆるスタートメニュー的なもの(Whisker Menuという名前らしい)が出てくるが、青いアイコンだけなので押しにくい。領域を増やして押しやすくする。

領域を増やすには、まずWhisker Menuを右クリックして「プロパティ」を選択し、「Whisker Menu」ウィンドウを開く。


「パネルボタン」の「表示」を「アイコンとタイトル」に変更して、「タイトル」を表示するようにする。「タイトル」はデフォルトでは「Application Menu」であるが、これはちょっと大きすぎるので自分は「Start」にしている。

時計の表示内容を変更

右下の時計はデフォルトでは「4 5月, 15:03」みたいな変な感じなので、カスタマイズしてそれっぽくする。

時計の表示内容をカスタマイズするには、時計を右クリックして「プロパティ」を選択して「時計」ウィンドウを表示する。


あとは「表示形式」を「カスタム」に設定し「%m/%d(%a) %H:%M:%S」などとする。選べる値はヘルプボタンを押せば飛べるがhttp://docs.xfce.org/xfce/xfce4-panel/clockを見ればわかる。

クイック起動(ピン)のショートカットを追加

クイック起動やピン留めのように、アプリケーションをパネルから起動したい。

パネルにアプリケーションの起動ボタンを配置するには、Whisker Menuを起動し、アプリケーションを右クリックして「パネルに追加」を選ぶ。

すると、パネル右側にアイコンが現れるので、右クリックして移動を選び、好きな場所に配置する。

これでだいたい定番の設定はおしまい。パネルを右クリックして「新しいアイテムの追加」を選ぶと、他にも増やせるものがあるので、好きに選ぶといいかも(ワークスペーススイッチャーとか)

おまけ: $HOMEのディレクトリを英語にする

デフォルトでは$HOMEに置いてある「デスクトップ」や「ピクチャ」はカタカナでcdしにくい。Xubuntuに限った話ではないが、これでは不便なのでいつもこれをしている。

$ LANG=C xdg-user-dirs-gtk-update

変えていいか確認するウィンドウが現れるので、従えばOK。

おまけ2: キーボードショートカットの変更(2017/6/1追記)

画面では設定>キーボード>アプリケーションショートカットキーから設定できる。

毎回やるのはだるいのでコマンドで覚えておく

# 一覧
xfconf-query -c xfce4-keyboard-shortcuts -lv

# Ctrl+Fn でのワークスペース切り替えを無効化
xfconf-query -c xfce4-keyboard-shortcuts -l | grep '/xfwm4/custom/F' | xargs -l1 xfconf-query -c xfce4-keyboard-shortcuts -r -p

# Ctrl+Alt+Lでのロックを無効化し、Super+Lに変更
xfconf-query -c xfce4-keyboard-shortcuts -p "/commands/custom/<primary><alt>l" -r
xfconf-query -c xfce4-keyboard-shortcuts -p "/commands/custom/<super>l" --create -t string -s xflock4

参考:ターミナルなどからXfceの設定をする

2016-04-21

waifu2xAvisynthをY8に対応させた

要望があったので、気分転換に実装して、v0.0.2をリリースした。
http://waifu2x-avisynth.sunnyone.org/

あまり更新することは考えていなかったのだけど、おまけのようなU/V planeの処理を省けば良いだけだったので。

最初のリリースが2015/5/30なので、もうほぼ1年になるのか、というのが衝撃。正直この実装はwaifu2x-converter-cppとAvisynthのつなぎをしただけで、たいしたことはしていないのだけど、それでも今でもStarされたりするあたり、知名度というものはすごいなぁと思う次第である。

2016-04-13

gtk-rs のチュートリアル的文書を書いた

Gtk+のRustラッパーであるgtk-rsの入門を書いた:RustとGtk+で開発するGUIアプリケーション

ほんとはもう少しやるといいことがあるのだけど(GResource、スレッド対応など)、まぁとりあえずここまで書いてあればGUIアプリケーション書いたことある人であれば感じは掴めるのかなと。ということでRev0.1。

(少なくともいまのところは)ニーズはほとんどないと思うけど、やる気があるうちにある程度形にしておかないと絶対書かないので。自分がこれから書いていくアプリケーションを読み進めるにあたっての基礎知識となれば。

2016-04-10

メタプログラミングについて話してきました

ゆるスタック勉強会 #20 で「メタプログラミングとは」という内容で話してきました。「メタプログラミングって何?イメージがつかない」という話を受けて、イメージをつくようにするのがこの話の目的です。



本当はそれぞれの言語のやりかたについて手順を書けばよかったのですが、力つきたので、実際のライブラリから例を持ってくる方向性にしました。話している間はコードや別の資料も見ながら話していたので、このスライド自体というよりは、各スライドでポイントしているライブラリの実装あるいは類似するライブラリの実装を読んでもらうのがイメージをつかみやすいんじゃないかと思います。

それぞれの言語でニワカなところがあるので、全方位からツッコミを受けてもおかしくない内容だと思っています。何かあれば補足していきたいのでぜひどうぞ。

2016-04-01

Rust と C言語 をコールバックで行き来する(Cブリッジが必要なVer)

Rustは C FFI が強いので、はっきりしたstructとCの関数を呼び出すなんていうケースでは、それっぽいstructとexternでの関数の定義を書けば呼び出せる。コールバックもできる。
他言語関数インターフェイス

しかし、以下のようなケースではちょっとやりにくい。
  • 必要な関数の呼び出しにCマクロが必要
  • opaqueなstructの下のほうのメンバにアクセスが必要
要はC言語を解釈してもらえると楽だよねという話なので、「Cでラッパー書いてautotoolsだMakefileだなんだかんだ、ようやくつなげる」となるかと思いきや、このようなCラッパーが必要な状況についてもRustには補助がある。今回はそのやりかたについて紹介する。

自分が必要だったところがC世界からのコールバック呼び出しだったので、それについても触れる。基本的には、RustのアプリケーションがCライブラリを使うという想定。コールバックなので、行ったり来たりなのだけどね。

やりかたの概要

やりかたは概ね以下の通り。
  1. ラッパー関数をCで用意する
  2. CargoのビルドスクリプトでCソースのコンパイル方法を指示する
  3. ラッパー関数をexternで定義する
  4. ラッパー関数を呼び出すstruct & traitを定義する
  5. 実際に呼び出す
実際のソースはhttps://github.com/sunnyone/rust-bridgesampleに置いてあるので、ソースがあればいいという方はこちらでどうぞ。

やりかた

ラッパー関数の準備

Rustから呼び出したい関数を、普通の関数呼び出しにできる形で定義する。本来は何かのコードのラッパーの想定なのだが、そっち側の知識が必要となると話がややこしくなるので、ここでは単なるCソースを例として使う。



上記がsrc/bridge.cに置かれているとする。

Cargo.toml + build.rsにCソースのコンパイル方法の指示する

ここがこの記事のキモ。cargoに、置いたCソースのコンパイル方法を指示する必要がある。

まず、Cargo.tomlにビルドスクリプトが「build.rs」にあるよ、ということを指示する。またコンパイラの実行の面倒を見てくれる「gcc」crateをbuild dependenciesに入れる(gccと言っているが、MSVCも対応しているらしい。試してないが)。

[package]
(略)
build = "build.rs"

[build-dependencies]
gcc = "0.3"

[dependencies]
libc = "0.2"

dependenciesのlibcは、ビルドスクリプト的には必要ないが、C FFIではほぼ使うのでここで入れておく。

上記で定義した「build.rs」に、コンパイラの呼び出しを定義する。

extern crate gcc;

fn main() {
    gcc::compile_library("libbridgesample.a", &["src/bridge.c"]);
}

このあたりの記述、詳しくはCargoのドキュメント「Build Script Support」を参照。
本来ライブラリのリンクの指示も必要だが、ここでは一旦省く。詳しくは上記ドキュメントを参照(下でもすこし触れる)。

ラッパー関数をexternで定義する(いわゆる-sys部分)

ここからはもうRust onlyでのFFIと一緒。externでfnとstructを定義する。opaqueなstructをenumを定義するのがイディオムだそうだ。

Cargoの流儀的には、FFIのためのRustらしくない定義はなんちゃら-sysというcrateに定義するほうがそれらしい。この部分だけの差し替えがしやすいから、とのこと。

ラッパー用のstructを用意する

ffi層を叩く、Rust 的構造にあわせた呼び出し部を用意する。今回は、コールバックをクロージャでもらい、それを実際のCコールバックから呼ぶ形にした。

「外側」と「内側」の2つのstructを作る

Rust世界のコード用のデータを入れておく「内側」と、その「内側」とC関数からもらったポインタを格納しておく「外側」を用意する。


必ずしもこうでなくてよいが、こうしておくことで「内側」をBox(ヒープ)で持つことが出来、ポインタに変換してC関数のポインタに渡すことができる。

git2-rsのsrc/transport.rsでやっていたので参考にした。

Drop traitを実装しポインタが自動で捨てられるようにする

後始末大事。


コールバックとしてC側に渡す関数をexternで定義する

今回はライフタイムをRustが持っているので、キャストするだけ。ここでは、Rust側がライフタイムを握っているのでポインタを渡しているが、もしC側がライフタイムを握るようであれば、Box::into_rawBox::from_rawをうまく使うといいと思う。ライフタイムの制御から外せる。mem::forget というのもあるかな。


ラッパーを実装

あとはffi関数を呼び出すようMyStructにimplする。


呼び出しコードを書く

実際呼ぶコードはこんなかんじになる。 結果はこんな感じ。
mystruct_new
Hello, World: 11
Hello Result: 12345
mystruct_free
これでだいたい使えるはず。

おまけ:pkg-configでライブラリを参照させる

本来ラップする対象があるはずなので、リンク対象やincludeパスを指定する必要がある。基本的には、println!()で、cargoが必要な情報を渡してあげればよいが、build-dependenciesでpkg-config crateを使うと、pkg-configの結果を使える。例えばこんな感じ。 with-glib branchで使ってみてある。実際のところは、panicはやっつけなのでちゃんと分岐させたり、pkg-configに関わらず環境変数で設定できるようにしたりと、工夫がされていることが多いので、詳しくはそこらのライブラリのコードを参照。例えば、glib-sys crateはこんな感じ

2016-03-15

Rust の開発環境を作る (Ubuntu + Visual Studio Code)

今回はRustとその開発環境のセットアップの仕方について。Ubuntu 14.04環境に、Visual Studio Codeをインストールして、コード補完ができるようになるまで。Ubuntu Makeのところ以外は、Macでも大体同じじゃないかなと思う。


Rust コンパイラまわり

multirustのインストール

multirustは、stableやnightlyなどrust環境を切り替えられるようにするツール。普通にrustcをインストールしてもよいが、便利なのでmultirustで入れておく。

$ curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh
(略)
$ rustc --version
rustc 1.7.0 (a5d1e7a59 2016-02-29)

rustc的にはこれでおしまい。

Visual Studio Codeまわり

アーカイブを展開するだけでも良いのだけど、便利なUbuntu Makeツールを使う。

Ubuntu Makeのインストール

$ sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make
$ sudo apt-get update
$ sudo apt-get install ubuntu-make
新しいUbuntuではubuntu-makeのppaはいらないかもしれない。

Visual Studio Codeのインストール

$ umake ide visual-studio-code
(インストールパスを聞かれるけどデフォルトでこんな感じに。)
Choose installation path: /home/yoichi/.local/share/umake/ide/visual-studio-code 

便利なので本来のコマンド名codeでシンボリックリンクを貼っておく。
$ ln -s ~/.local/share/umake/ide/visual-studio-code/code ~/.local/share/umake/bin/code

visual-studio-codeのリンクが壊れているようだけど、Gitでは直っているようなので、少しするとvisual-studio-codeでも起動できるようになると思う。

RustyCode extensionのインストール

VSCodeのRust言語用拡張のRustyCodeをインストールする。codeで起動後、Ctrl+Shift+pでコマンドパレットを起動し、install RustyCodeでインストール。

開発支援ツールまわり(racer, rustfmt)

Codeを起動してRustyCodeで書こうとすると、以下のようにエラーが出るので、必要なツールを入れる。
The "racer" command is not available. Make sure it is installed.

racer (コード補完ツール) のインストール

$ cargo install racer
(略)
  Installing /home/yoichi/.multirust/toolchains/stable/cargo/bin/racer
be sure to add `/home/yoichi/.multirust/toolchains/stable/cargo/bin` to your 
PATH to be able to run the installed binaries
cargoに言われる通りPATHを~/.bashrcなどに設定する

export PATH=$PATH:~/.multirust/toolchains/stable/cargo/bin

その後racerを実行するとわかるが、racerには参照用にRustのソースが必要なので、ダウンロードして適当なところに置いておく。

$ racer
RUST_SRC_PATH environment variable must be set to point to the src directory of 
a rust checkout. E.g. "/home/foouser/src/rust/src"
$ mkdir ~/src
$ wget https://static.rust-lang.org/dist/rustc-1.7.0-src.tar.gz
$ tar zxvf rustc-1.7.0-src.tar.gz

racer用にソースの配置場所を環境変数に設定する(~/.bashrcなどに書く)
export RUST_SRC_PATH=~/src/rustc-1.7.0/src

racerコマンドを叩いてusageが出ればOK。

rustfmt (ソースコードフォーマッター) のインストール

なくてもエラーは出ないが、入れておくとCtrl+Shift+I でコードフォーマットできる。cargo fmtでもOK.

インストールはcargoで。
$ cargo install rustfmt

Hello Worldプロジェクトの作成

試すには、適当にプロジェクトを作成してCodeで開く。
$ cargo new hello --bin
$ code hello &

src/main.rsを適当に編集したら、Ctrl+Shift+Rか、コマンドパレット(Ctrl+Shift+p)からRun Debugで実行できる。

2016-02-08

PHPが出力するsyslogの「ool www」の謎を追う

PHPからerror_log()でsyslogを吐いたときに出力される「ool www」という謎の文字列が一体なんなのか調べた。いいから回避策が知りたいという人はこちらへ。おまけが本編という人もいるかもしれない。

発端

とあるPHPアプリケーションがエラーログをPHP標準関数error_log()で吐く。それをフィルタしたくなったのだが、error_log()にはたいしたフックポイントもなく、アプリケーション自体には手を入れたくないので、とりあえずsyslogで受けてSyslogサーバーがなんとかするのがよいかと思って「error_log」の設定を「syslog」にしてみると、なぜかこんな感じに……

Feb  7 22:02:36 phptest ool www: LOG CONTENT

……この「ool www」は一体なんなんだ? Out-of-L...うーん。ここの文字列だけなら実害はないが、なんでこんなことになっているのかわからないので調べることにした。

テストファイル

当該のPHPアプリケーションは置いといて、1ファイルでも再現するので、以下のPHPプログラムでテストすることにする。
<?php error_log("ERRORTEST"); ?>

環境はUbuntu 14.04にnginx + php5-fpm。設定値「error_log」を「syslog」にしておく。あまりバージョンに拘っていないので、リンクが適当だったりするが気になるかたは必要なバージョンのソースを見に行ってほしい。

ソースを追う

error_log()関数の流れ

くだんのerror_log()関数をPHPから呼び出すと、こんな感じで動く。

まず、error_logのPHP_FUNCTIONの記述がext/standard/basic_functions.cにある。ここが実体である_php_error_log_ex()を呼び出す。

ext/standard/basic_functions.cの_php_error_log_ex()は、switchで引数に応じたログ出力(メールなど)を行う。今回のmessageのみの呼び出しではdefault: にたどり着き、php_log_err()を呼び出す。

php_log_err()は、main/main.cにあり、ここで「error_log」変数のチェックを行い、値が「syslog」のときはphp_syslog()関数でsyslogに出力している。

if (!strcmp(PG(error_log), "syslog")) {
 php_syslog(LOG_NOTICE, "%s", log_message);
 PG(in_error_log) = 0;
 return;
}

基本的にメッセージを引き回しているだけで、ここまでに変わった文字列処理は見受けられない。

php_syslog()関数とは

であればポイントはphp_syslog()であろうと、php_syslog()の定義をphp_syslog.hに見つけるも、こうなっていた。

なんとphp_syslog()はsyslog()であった。つまり、PHP内で何か特別な処理をすることなく、そのままsyslog()関数にメッセージを投げているだけである。

syslog関数の使い方

こうなってくるとsyslog(3)が大事になる。manがあり、日本語が古くないようなので日本語のsyslog(3)を見てみる。

プロトタイプとしては、こうなっているようだ。
#include <syslog.h>

void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void); 

呼び出しとあわせると、priorityがLOG_NOTICEで、formatが"%s"、%s部分としてlog_messageが渡ることになる。

説明を読むと
ident で指定した文字列は各メッセージの前に付与される。
とあるので「ool www」の部分はidentのようだ。

処理の中でとくにopenlog()する処理は見受けられなかったので
openlog() は必須ではなく、必要に応じて syslog() から呼び出される。 syslog() が呼び出した場合、 ident のデフォルト値は NULL になる。
の部分に該当し、
通常は ident にはプログラム名が設定される。 ident が NULL の場合、プログラムが ident として使用される
とあるので「ool www」はプログラム名のようだ。しかし「ool www」なんてプログラムはいないぞ?

syslog()関数の実装

こうなると、identがNULLだったときのopenlog()の動きを知りたくなる。そこでglibcを見たところ、openlog()の実体はmisc/syslog.cにある。

openlog()は内部のopenlog_internal()を呼び、ここでidentはLogTag変数に代入される。

LogTagがNULLのときの処理を探してみると、syslog()のログ処理の実体である__vsyslog_chk()に以下の記述があった。

if (LogTag == NULL)
      LogTag = __progname;

__prognameは何かと見ると、頭のほうでexternで宣言されていた。
extern char *__progname; /* Program name, from crt0. */
だいぶあやしい。

「__progname」とは

で、誰が__prognameをセットしているのかと探してみると「misc/init-misc.c」にあった。

void
__init_misc (int argc, char **argv, char **envp)
{
  if (argv && argv[0])
    {
      char *p = strrchr (argv[0], '/');
      if (p == NULL)
         __progname = argv[0];
      else
         __progname = p + 1;
      __progname_full = argv[0];
    }
}
__prognameは「argv[0]の最後の'/'より後」のようだ。なるほどargv[0]の役目を考えると妥当そうに見える。__progname_fullとしてargv[0]も保持されている。

php-fpmのargv[0]ってなんだっけ?

phpが動作するのは、php-fpmプロセス。そういえばphp-fpmは独自設定した文字列がpsで見えてたよなと見てみると、こうなっている。

$ ps -ef | grep '[p]hp-fpm'
root      2664     1  0 22:02 ?        00:00:00 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)                    
www-data  2666  2664  0 22:02 ?        00:00:00 php-fpm: pool www                                                       
www-data  2667  2664  0 22:02 ?        00:00:00 php-fpm: pool www

php-fpmは明らかにargv[0]を書き換えている。勘のいい人ならもうお分かりだろう。一応検証するために、PHPを適当に展開してfpm.cのfpm_init()の頭と末尾(argvが書き換わる前と後)に以下のようにログを仕込んでみる(ヘッダやexternは適宜挿入)と...

zlog(ZLOG_NOTICE, "argv[0]: %s", argv[0]);

zlog(ZLOG_NOTICE, "__progname: %s", __progname);
zlog(ZLOG_NOTICE, "__progname_full: %s", __progname_full);

こんな風になる。

$ sudo ./sapi/fpm/php-fpm -F -y /etc/php5/fpm/php-fpm.conf 
[08-Feb-2016 00:25:23] NOTICE: argv[0]: ./sapi/fpm/php-fpm
[08-Feb-2016 00:25:23] NOTICE: fpm is running, pid 24381
[08-Feb-2016 00:25:23] NOTICE: __progname: ster process (/etc/php5/fpm/php-fpm.conf)
[08-Feb-2016 00:25:23] NOTICE: __progname_full: php-fpm: master process (/etc/php5/fpm/php-fpm.conf)
もう少しわかりやすくすると、こう。


そう、元々あった最後の/までの文字数分、新しい文字列の開始からスキップされている。ログ出力がだるいので例ではmaster processになっているが、pool wwwでどうなるかを想像するのはたやすいと思う。

結論:syslog tagに使われる__prognameを、プログラムパスであるargv[0]からディレクトリ部分を取り除いた文字列へのポインタとしてlibcが保存したあと、php-fpmがargv[0]を書き換えたため、謎の文字列「ool www」が生まれた。

一応Bug reportは作っておいた。PHPが対処すべき問題のように感じるけど、libcが文字列をコピーしておかないのが悪いって言われるとそうすかって感じなので、正直期待できない:https://bugs.php.net/bug.php?id=71544

回避策:identに妥当な値を設定するには?

暗黙のopenlog()をさせなければいい。つまり、期待のプログラムが動く前にopenlog()すればよい。

具体的には、以下のようなファイルを例えば/etc/php5/fpm/fixsyslog.phpと置いて……

php.iniのauto_prepend_fileで参照する。
auto_prepend_file = /etc/php5/fpm/fixsyslog.php

これで常にopenlog()が呼ばれるので現象は起こらない。

感想

原因に関しては、ソースを追うことですっきりさせることができた。オープンソースのありがたみって、やっぱりこういうところにあると思う。解決策は、もやもやが残るままだけど…

おまけ:php-fpmはどのようにargv[0]を書き換えているか?

php-fpmは見ての通りargv[0]を書き換えるのだけど、argvを全部足しても期待の文字数に足りないなんてこともあるはず。そんなときの処理がfpm_env.cに書かれていた。

ざっくり言うと「environがargvとメモリ空間上に連続で置いてあるから、一応確認しつつenvironにどいてもらって、そこをargv[0]として使いましょう」である。グロい!コメントによると、nginxやpureftpdもそうしているらしい。

この記事も最後なので該当コードを貼り付けておく。


2016-01-25

Ansibleを複数ホストへのコマンド投げツールとしてad-hocに使う

Ansibleは普通「playbook」と呼ばれるファイルを記述し、それを使って動かすのだけど、もっとてきとーに使っても便利なときは便利なので、今日はその使い方の紹介。実行する側は、ansibleがインストールされている必要はあるが(apt-get install ansibleでよい)、対象ホストはsshでアクセスできればOK。

複数ホストにコマンドを投げる

シンプルな例: 適当なファイルをcatする

もっとも理解が簡単なのは、権限だとかは気にせずにただコマンドが実行できればよい、という使い方。サーバがたくさんあるんだけど、ひとつひとつ実行するのはダルい。そんなときは、こんな風に実行すると、実行して結果を並べてくれる。

$ ansible -a "cat /etc/hostname" -i host1,host2 -u yoichi all
host1 | success | rc=0 >>
host1

host2 | success | rc=0 >>
host2

-aにコマンドを指定し、-iで対象ホストをカンマ区切りで並べ、-uにユーザを指定し、allをつける。

ユーザの指定なくsshできる状態なら、-uはなくてもいい。また、playbookを書いていてinventory fileを持っているなら、そのファイルを-iで指定したほうが当然楽。この例ではありがたみが全然ないが、もし1ホストにしたければ「-i hostname,」のように最後に,をつける。

sudoする例: crontabに設定された内容を見る

Ansibleをansible-playbookコマンドで使っている人にはお馴染みの、-sでsudo(-Kでパスワード)、-Sでsuさせることもできる。例えば、crontabに設定された項目を見たい、とか。
$ ansible -s -a "crontab -u root -l" -i host1,host2 all
host1 | FAILED | rc=1 >>
no crontab for root

host2 | success | rc=0 >>
# Edit this file to introduce tasks to be run by cron.
(略)
# m h  dom mon dow   command
30 5 * * * /usr/local/bin/rotate.sh

切り離す例: 一斉シャットダウン

停電準備で一斉シャットダウンしたいです、とかいうこともあると思う。shutdownのケースでは、結果を待てないので-Bオプションを使ってバックグラウンドで切り離す。

$ ansible -s -K -B 1 -a "shutdown -h now" -i host1,host2 all
SUDO password: 
background launch...

host1 | success >> {
    "ansible_job_id": "867312552566.13878",
    "results_file": "/root/.ansible_async/867312552566.13878",
    "started": 1
}

host1 | success >> {
    "ansible_job_id": "867312552566.2418",
    "results_file": "/root/.ansible_async/867312552566.2418",
    "started": 1
}

Ansibleのモジュールを活用する

上述の例はすべてコマンドだったが、当然ながら、Ansibleのモジュールも活用できる。

モジュール活用例1: サービスを再起動させる

例えば、サービス再起動では、serviceコマンドでもいいが、serviceモジュールを使うとこのようになる。

$ ansible -s -m service -a "name=myservice state=restarted" -i host1,host2 all
host1 | success >> {
    "changed": true,
    "name": "myservice",
    "state": "started"
}

host2 | success >> {
    "changed": true,
    "name": "myservice",
    "state": "started"
}

-mでモジュール名を指定し、-aでモジュールの引数を指定する。実は-aはいつでもモジュールの引数なのだが、-mのデフォルトが「command」なので、コマンドを実行できるというわけだ。

モジュール活用例2: copyモジュールでファイルを配置する

ad-hocに使う例ではない気がするが、その気になればファイルを持っていくことも可能。

$ echo hoge > hogefile
$ ansible -m copy -a "src=hogefile dest=/tmp/hogefile1" -i host1,host2 all
host1 | success >> {
    "changed": true,
    (略)
}

host2 | success >> {
    "changed": true,
    (略)
}

注意

あくまで一回限りで使うようなものであって、残したいものだったり、ansibleコマンドを3つも4つも打つようであれば、playbookを作るのがよい。上述のコマンドを羅列すればyaml書かなくても呼べるじゃん?っていうのはナシで。ベストプラクティス構成にしておくのがよいと思うが、1ファイルplaybookでもまぁansibleコマンドがたくさん書いてあるよりは良いと思う。

参考

こういうのをad-hoc commandsと呼ぶらしい→Introduction To Ad-Hoc Commands
他にも例がいくつかあるので、参考にされたい。

おわりに

「ansible」というソフトウェア名がついているコマンドがこの動作であるところから、もともとはこういう風に使うように始まったのではないかと私は勝手に思っている。現時点ではメインではない使い方になった(と思う)ので、知らなくても全然使えないことはないが、ベースの考え方はこのエージェントなしにリモートホストに任意にモジュールを投げ込んで実行するツールだと思うと、理解も応用もしやすいと思うので、触ってみておいて損はないかなと思う。

2016-01-10

avahi-browse で mDNS/DNS-SD のホスト・サービスの探索をする

apt-get install avahi-utilsして「avahi-browse -alr」とすればよい。たいした話ではないけれど、多分忘れるので。

出力例はこんな感じ。
$ avahi-browse -alr
+   eth0 IPv6 sample [00:11:22:33:44:55]                      Workstation          local
+   eth0 IPv4 sample [00:11:22:33:44:55]                      Workstation          local
+   eth0 IPv4 Jenkins                                         Web Site             local
+   eth0 IPv4 jenkins                                         _jenkins._tcp        local
+   eth0 IPv4 jenkins                                         _hudson._tcp         local
=   eth0 IPv6 sample [00:11:22:33:44:55]                      Workstation          local
   hostname = [sample.local]
   address = [fe80::82ee:1234:5678:9999]
   port = [9]
   txt = []
=   eth0 IPv4 sample [00:11:22:33:44:55]                      Workstation          local
   hostname = [sample.local]
   address = [192.168.1.152]
   port = [9]h
   txt = []
=   eth0 IPv4 Jenkins                                       Web Site             local
   hostname = [jenkins.local]
   address = [192.168.1.101]
   port = [8005]
   txt = ["version=1.643" "url=http://jenkins.example.local:8005/" "slave-port=46298" "path=/" "server-id=5ca870f7e5eb41888299af4853b2021b"]
=   eth0 IPv4 jenkins                                       _jenkins._tcp        local
   hostname = [jenkins.local]
   address = [192.168.1.101]
   port = [8005]
   txt = ["version=1.643" "url=http://jenkins.example.local:8005/" "slave-port=46298" "path=/" "server-id=5ca870f7e5eb41888299af4853b2021b"]
=   eth0 IPv4 jenkins                                       _hudson._tcp         local
   hostname = [jenkins.local]
   address = [192.168.1.101]
   port = [8005]
   txt = ["version=1.643" "url=http://jenkins.example.local:8005/" "slave-port=46298" "path=/" "server-id=5ca870f7e5eb41888299af4853b2021b"]