コマンドの入出力が繋がった端末を移動させる

時間のかかるコマンドを起動した後、「あ、tmux(とかscreen)の中で実行すればよかった…」と気づくことがたまにあるわけですが、そんな時に強制的に端末を移動させる方法として、gdbをアタッチし、そのコマンドのファイルディスクリプタ(fd)の示す先を変更してしまうという手があります。


具体的な手順としては、open(2)で移動先の端末を開き、そのfdをdup2(2)の第1引数、移動前の端末に繋がっているfdを第2引数に指定して差し替え、最後にclose(2)で最初にopenした端末を閉じます。


ただし厳密には、対象が端末の場合、各種の状態を持っているので、これをsttyコマンドで合わせる必要があります。そうでないと、対象がviやemacsのような端末制御を行うソフトウェアの場合、正しく操作できなくなります。


またコマンドが入力を受け取る場合、移動先の端末でシェル等がreadしていると、入力が部分的にシェルに吸われてしまって操作が著しく困難になってしまうので、そのようなプロセスは止めておく必要があります。以下のスクリプトではSTOPシグナルを送って眠らせるようにしてあるのですが、screenやtmuxは、端末の子プロセスが寝たことを検知するとCONTシグナルを送ってすぐに起こしてしまうようです。仕方ないので手動でsleepでもさせておくしかなさそうです。。とりあえずスクリプトが実行された端末が移動先になっている場合にはスクリプト内でsleepさせています*1


このほかの工夫として、移動元の親プロセス(通常はシェル)を待機状態から復帰させるために、一旦対象コマンドにSTOPシグナルを送ってサスペンドしたのち、少し待ってCONTシグナルで復帰させています。ついでに、端末のサイズが変わったことをWINCHシグナルでコマンドに通知してあげます。これにより画面の再描画も必要に応じて行われます(あるいは単に無視されます)。


上記の処理をするのが本記事末尾のスクリプトです。

./switch-tty.sh 移動するブロセスID 移動先の端末またはファイル

のように指定して使います。


試してみるには、Linux上(またはssh)で端末を2つ開き、一方で何かのコマンド (例えば vi ) を起動し、もう一方で (必要ならtmuxなどを実行したのち)

./switch-tty.sh `pidof vi` `tty`

などと実行すると、

Current stdin: /dev/pts/5
Target fds: 0 1 2
session leader: 23965
Switching to this TTY
Continue? (y/N) 

と確認が出ますので、yを入力します。するとおもむろにgdbがごにょごにょ動いたのち、スクリプトを実行した端末にviが移動してきます。あとは、移動元の端末で disown を実行すれば、閉じてしまうことができます。


(なお、移動先でviを終了してもすぐにはシェルは帰ってきません。これはコマンドの終了を1分に1回しかチェックしていないためです*2。対象のコマンドの挙動次第ですが、 ctrl-C を入力すればsleepが中断されてすぐにシェルが戻ってくるかもしれません。)


以下、スクリプト
https://gist.github.com/NeoCat/f662cfd71c65eed7b59baed14eb3400c

*1:元々いたプロセスを終了してしまうわけにはいきません。セッションリーダーが閉じると端末そのものが閉じられてしまうからです

*2:自分の子プロセスでないプロセスの終了を待機するいい方法がないため

iPhoneのバッテリー残量を取得

iPhoneのバッテリーがかなりヘタってちょっと使うとすぐに電源が落ちてしまうようになってしまったので、自分で交換してみました。やり方を紹介したページを見つつ、バッテリー\2000 + 工具セット \1000 で無事交換成功し、日中そこそこ使っても丸一日持つようになりました。



DIGIFORCE 交換用PSEバッテリー iPhone6用 1810mAh LPB-DIGI6

DIGIFORCE 交換用PSEバッテリー iPhone6用 1810mAh LPB-DIGI6


さて、バッテリーの寿命を良くする方法として、80〜90%ほど充電されたら繋ぎっぱなしにせずに充電をやめると良いという説があるようです。真偽のほどは定かではありませんし、大きな効果があるならそういった機能が実装されているでしょうから、おそらくいちいち気にするほどの効果はないのでしょう。とはいえ、意識しなくとも自動的に充電が止まってくれるのならそうしてみても良いかな?という気になったので、やり方を考えてみました。


まず、iPhoneのバッテリー残量を取得する方法ですが、なんらかのアプリを使えないでしょうか? しかしiOSの場合、アプリがバックグラウンドで定期的に処理をするのは、アクセサリへのアクセスや、音楽再生や位置情報を使用するアプリに限られており、電池監視のためにはトリッキーなことをする必要があるので候補から外しました。


次に考えられるのはUSB越しで取得する方法です*1。最近のLinuxデスクトップ(Fedora 25で確認)にiPhoneをUSB接続すると、upowerコマンドでiPhoneのバッテリー値を取得できます。下記はiPadの場合ですが、まず一覧で名前を確認し、

$ upower -e
/org/freedesktop/UPower/devices/computer_3_1
/org/freedesktop/UPower/devices/mouse_0003o046Do1024x000A
/org/freedesktop/UPower/devices/keyboard_0003o046Do2011x000B
/org/freedesktop/UPower/devices/DisplayDevice

それっぽいデバイスを指定すると、

$ upower -i /org/freedesktop/UPower/devices/computer_3_1
  native-path:          /sys/devices/pci0000:00/0000:00:14.0/usb3/3-1
  vendor:               Apple_Inc.
  model:                iPad
  serial:               *****************************************
  power supply:         no
  updated:              20161231221652(14 seconds ago)
  has history:          yes
  has statistics:       no
  computer
    warning-level:       none
    percentage:          100%
    icon-name:          'battery-full-charged-symbolic'

という感じで情報が出力されるので、このpercentageを見るというのが一つの方法です。GUIで設定(gnome-control-center)→電源 で残量を確認できるのもこの情報を表示しています。


これを見て、80%を超えたらUSBの電源をOFFにする方法が取れるでしょう。LinuxからのUSBの電源制御には、最近だと以下のハブが使えるそうです。
LinuxからUSB HUBの電源のON/OFFを制御してみる - memoメモ


ただ欠点として、USBから切断してしまうとそれ以降の情報は取れないので、充電完了後に使用していると電池が減っていってしまうということになります*2


別のやり方として、iTunesをインストール済みのMacまたはWindowsであれば、libimobiledeviceを導入することで、Wi-Fi経由でも電池残量などの情報が取得できます。たまたまWiFiで電源をON/OFFできるコンセントを作ってあったので、全てWiFi越しでやれるということもあり、今回はこの方法を試してみました。


まず、libimobiledeviceを導入します。Macであればbrewで一発です。とはいえiOS10のせいか、HEADでないとうまく動作しませんでした。

$ brew install --HEAD libimobiledevice

これで、
idevice_id -l コマンドでUSBまたはWiFiで繋がっているiOSバイスのシリアルを調べておき、ideviceinfoでバッテリー情報を取得します。

$ ideviceinfo -u ****************(idevice_idで表示されたシリアル) -q com.apple.mobile.battery
BatteryCurrentCapacity: 100
BatteryIsCharging: false
ExternalChargeCapable: true
ExternalConnected: true
FullyCharged: false
GasGaugeCapability: true
HasBattery: true

BatteryCurrentCapacityが%単位でのバッテリー残量です。
rubyであれば

$IDEVICEINFO_CMD = '/usr/local/bin/ideviceinfo'

def get_batt(device_id)
  result = `#{$IDEVICEINFO_CMD} -u #{device_id} -q com.apple.mobile.battery`
  if result =~ /BatteryCurrentCapacity:\s*(\d+)/
    return $1.to_i
  end
  nil
end

というような関数でバッテリー残量を取得できるようになります。


ただし,iPhoneがスリープ状態になっている場合、WiFiには90〜300秒に一度、数秒間しか接続されないようで、この瞬間しか情報がとれません。とりあえず、2秒に一度くらいポーリングをかけることにします*3。また、それでもかなり長いことWiFiに接続してこないこともあるようなので、そのような場合には一度充電をOFF→ON(またはON→OFF)することで、iPhoneをウェイクアップさせれば情報がとれます*4


この結果を見て、80%以上で充電停止、80%未満で再開するようにします。また、充電中にMacがスリープしてしまうと充電されっぱなしになるので、充電中はcaffeinateコマンドでスリープを抑制しています。全体的にはこんなスクリプトになりました。


30%充電の状態からこのスクリプトを動かしながら充電を試して見た結果、こんな感じになりました。運悪く80%になるわずか手前でWiFi接続が切れたらしく、80%を3%ほど超えたところで充電が止まりました。



そのままiPhoneを使っていると、79〜80%で維持されるように充電がON/OFFされます。



でもこれ結局充放電してるわけで、バッテリーに優しい気はしない。。まあ、あくまで実験ということで。

*1:Bluetooth LEのBattery Serviceを使ってとれないか試したのですが、いざ取得しようとすると認証を要求されてしまい失敗しました。

*2:定期的に再接続して充電残量をチェックすればいいかも

*3:本当はlibimobiledeviceから接続イベントを取れれば良いのですが、ポーリングでも大した負荷ではないのて今回は適当に済ませてしまいました。

*4:この時、いちいち接続音やバイブレータが鳴りますが…

マイナンバーカードでSSHしてみた

マイナンバーカードでSSHする - AAA Blog


という記事が出ていたので、マイナンバーカードを使ったSSHログインをLinux上でやってみました。環境は出たばっかりのFedora 24です。

カードリーダーは、上記の記事のような非接触式ではなく、接触式のICカードリーダー NTTCom SCR3310を利用しました。


SCM ICカードリーダー/ライター B-CAS・住基カード対応 SCR3310/v2.0 【簡易パッケージ品】

SCM ICカードリーダー/ライター B-CAS・住基カード対応 SCR3310/v2.0 【簡易パッケージ品】


まずは必要なパッケージをインストール。カードリーダーをUSB接続し、スマートカードデーモンpcscdを起動します。

$ sudo dnf install openssl-devel readline-devel zlib-devel pcsc-lite pcsc-lite-devel pcsc-lite-ccid pcsc-tools
$ sudo systemctl start pcscd


開発中の個人番号カード対応版OpenSCをcloneしてきて、ビルド、インストールします。

$ git clone https://github.com/jpki/OpenSC.git
$ cd OpenSC
$ ./bootstrap
$ ./configure
$ make
$ sudo make install

ではカードをセットして、公開鍵を読み出しましょう。カードの裏表を間違えないように注意(ICチップの端子のある面が上)。間違ってると読めないというエラーになります。。

$ pkcs15-tool --read-ssh-key 1
Using reader with a card: NTT Communications Corp. SCR3310-NTTCom USB SmartCard Reader [Vendor Interface] 00 00
ssh-rsa *******...... User Authentication Certificate
zsh: bus error  sudo =pkcs15-tool --read-ssh-key 1

ってなんかバスエラーを起こしました。がとりあえず読めたから気にしない(何


これをコピペしてSSHサーバの ~/.ssh/authorized_keys に追加します。同一マシン(ローカルホスト)でもいいでしょう。


そうしておいて、ビルドしたOpenSCのPKCSモジュールを指定してSSHログインしてみると...

$ ssh 192.168.xx.yy -I /usr/local/lib/opensc-pkcs11.so
Enter PIN for 'JPKI (User Authentication PIN)': **** ← 公的個人認証用の4桁のPIN番号
Last login: Fri Jun 24 00:37:23 2016 from 192.168.xx.zz
$ 

無事ログインできました。


なおPIN番号は3回間違えると閉塞されてしまい役所で手続きが必要になるのでご注意を。。
(3種類ある4桁の暗証番号のうちの、利用者証明用電子証明書の(マイナポータルへのログイン、コンビニでの公的な証明書の交付などで使う)PIN番号です。)


他に

$ pkcs15-tool -c 

で認証用証明書のシリアル番号などを確認したり、

$ pkcs15-tool -r 1 

で認証用証明書の情報を読み出し、ファイルにコピペして(リダイレクトするとSEGVするせいで書き出されない…)

$ openssl x509 -text -noout -in <ファイル名>

などとすると有効期限や発行者の情報(自治体とか)が確認できます。認証用証明書の方にはマイナンバーはもちろん、住所や氏名などは入っていないことが確認できます。

近接センサでRaspberryPi LCDのバックライトを点灯

Raspberry Pi 2に7インチタッチディスプレイをつけて、パッと見たい情報(天気予報とか時計とかバス時刻表とか)を表示するのに使っているのですが、常時つけっぱなしになっているのが気になっていました。周囲に人がいるときだけオンになったらいいなと思い、近接センサで一定範囲内(だいたい1.5m以内)に反応があった場合にバックライト点灯→しばらく反応がないと消灯するようにしました。


バックライトの制御コマンド

発売当初はバックライトON/OFFが制御できませんでしたが、最近のカーネルなら/sys/class/backlight/rpi_backlight/bl_power に0/1をwriteすることで ON/OFFができるようになっています(輝度変更は効きませんが)。また、XのDPMS (Energy Star)とも連動してON/OFFされます。今回は一定時間で消灯したいのと、タッチしたらONになって欲しいので、DPMSを使います。

Xが起動している状態で、

$ xset dpms force on
$ xset dpms force off

でバックライトのON/OFFが可能です。また、

$ xset dpms 120 120 120

とすると120秒間操作しないと自動でOFFになります。現在の状態確認はxset qを使います。

$ xset q
...
DPMS (Energy Star):
  Standby: 120    Suspend: 120    Off: 120
  DPMS is Enabled
  Monitor is Off

近接センサの接続

近接時の検出は以下の測距センサを使いました。距離に応じた電圧がアナログ出力されるものです。
シャープ測距モジュール GP2Y0A02YK: センサ一般 秋月電子通商 電子部品 ネット通販

Raspberry Piには直接アナログ入力することができないため、一旦Arduino Pro Miniを通して一定距離内に入った時にデジタルで信号を出力するようにしています。動画のRasPiの後ろで近接時にLEDが点灯しているのがArduino Pro Miniです。(別にデジタル変換はArduinoである必要は全くなく、ただのコンパレータでいいのですが、手持ちがなかったので。。)


上記のように接続してGPIOの17を読めば、近接検出ができます。GPIOへのアクセスは

# echo 17 > /sys/class/gpio/export

を実行しておき、

# cat /sys/class/gpio/gpio17/value

のようにすれば0または1として値を読み出すことができます。

近接センサでバックライト制御

以下のスクリプトでGPIOを定期的に監視し、近接時にxsetでバックライトの点灯させます。なお、点灯中にforce onしても無操作タイマーが更新されず、すぐに自動消灯してしまったりするので、近接中は定期的に時間を再設定することでタイマーをリスタートしておきます。

#!/usr/bin/perl
use Time::HiRes "sleep";
open my $in, "/sys/class/gpio/gpio17/value";
while (true) {
    seek $in, 0, 0;
    my $val = <$in>;
    if ($val == 1) {
        `xset dpms force on`;
        `xset dpms 120 120 120`;
        print "ON\n";
        sleep 60;
    } else {
        sleep 0.1;
    }
}

Open vSwitchやNamespaceの関係図をコマンド一つで描く

LinuxでSDNっぽいことをOpen vSwitchでやろうとすると、BridgeやらNamespaceやらInterfaceやらが増えまくって全貌の把握が困難になってきます。また、なぜかpingが通らないときなど、あっちこっちでtcpdumpして回るのは大変です。

そこで、コマンド一つでOpen vSwitchやNamespaceの関係図を描き、ついでに全interfaceをtcpdumpしてpingがどこを通っているかも色で示すツールを作りました。


今のところ単一ホスト内のみなので複数nodeあるときは各nodeで実行する必要がありますが。

使い方

ソースは https://github.com/NeoCat/ovsimager にあります。sudo できるユーザかrootで

 $ sudo gem install ovsimager
 $ sudo yum install graphviz
 $ ovsimager

とインストールして実行すると、interfaces.png というファイルに関係図が出力されます。


以下はOpenStack Neutron DVR(all-in-one構成)でinstanceにFloating IPを振った状態です。
Open vSwitch Virtual SwitchとLinuxBridge内のInterface、およびNamespaceとの関係(routingに使われうる、IPが振られたインターフェイスのみ)が点線で描かれます。vethやOpen vSwitch間のpeerは実線で繋がります。


pingをトレースするときは、400byteのpayloadのpingを打ちながら、

 $ sudo ovsimager -d

とします。すると、全インターフェイスtcpdumpが数秒間起動され、400byteのping (vxlanも含む) が通った場所を記録、図にpingの宛先・送信元IPアドレスが追加され、色もつきます。(黄: ICMP Echo Request/Response両方が通過、ピンク: Requestのみ通過、赤:Responseのみ通過)


なお ovsimager 自身に ping を実行させることもできます。

 $ sudo ovsimager -d -f 10.0.0.1 -t 192.0.2.1

pingのトレース時は全interfaceでtcpdumpを同時実行するとか割と荒っぽいので、大規模な環境で実行しないほうがいいと思います。。が、学習用にはいいかな、と。

FedoraやCentOS 6/7、RHEL 6/7のinitramfsを展開する

FedoraCentOS 6/7、RHEL 6/7のinitramfsでは、先頭にEarly CPIO image(CPUのmicrocodeなどが入っている)がつくようになり、本体の起動RAMディスクイメージ(圧縮cpio image)はその後に来ます。

本体を展開するには、先頭部分を飛ばす必要があるため、dracutが提供している skipcpio というユーティリティを通してから展開します。

mkdir img
cd img
/usr/lib/dracut/skipcpio /boot/initramfs-XXX.img | gunzip -c | cpio -i -d

なお中のファイルリストが見たいだけなら

lsinitrd /boot/initramfs-XXX.img

でOK。ちなみにlsinitrdの中身はskipcpioなどを使ったshell scriptです。

Linuxカーネル空間で2048で遊ぼう

最近大ヒットしたゲームと言えば2048。上下左右にタイルを動かして数字をくっつけ、2048を作るだけのシンプルなゲームですが、それだけにハマりやすい。
[参考記事: 「2048」:19歳のイタリア人が、ミニゲームでインターネットを征服!|WIRED.jp
2の累乗が大好きなエンジニアの間でももちろん大人気なようです。


そんなわけで、2048をLinuxカーネルの中で動作させてみたいと思います。


といってもそのためにカーネルビルドしたりは面倒なので、SystemTapでランタイムにカーネルにロードすることにしましょう。

動かし方

以下、Fedoraの場合です。他のディストリビューションでの動かしたい方はマニュアル等でSystemTapの動かし方を調べてください。


1. 下記のソースを2048.stpという名前で保存します。
https://gist.github.com/NeoCat/9947862


2. 必要なパッケージとdebuginfoをインストールします。

% sudo yum update
% sudo yum install systemtap
% sudo debuginfo-install kernel

カーネルがアップデートされた場合はrebootしてください。


3. では実行しましょう。

chmod +x 2048.stp
./2048.stp

または

% sudo stap 2048.stp

上下左右キーで操作するだけです。頑張ってタイルの移動や出現のアニメーションも付けました。高速操作にも対応できるよう、動いてる途中で次のキーが押された場合にはアニメーションをスキップするようになっています。


押したキーが端末に表示されると画面が乱れることがあるので、フォーカスを端末から外しておいた方が良いと思います。
もしくは、stty -echo ; sudo stap 2048.stp ; stty +echo などとして、端末への入力文字の表示を抑制すると良いでしょう。*1


なお、残念ながらcolor256に対応した端末以外では色がつきません。そのためLinuxのコンソール(tty0など)では白黒になってしまいます。gnome-terminalなら大丈夫です。


ちなみに、複数の端末で同時に走らせると、1度キーを押すだけで全ての端末のタイルがそれぞれ動きます。全ての端末でクリアを目指すとさらに難易度が上がりますよ!?

Q&Aコーナー

Q1. アニメーションがスムーズじゃないんだけど?

デフォルトだとSystemTapは200msごとにしかカーネルモジュールからのデータを読み込んで表示してくれないようです。
下記のようにして手動でカーネルモジュールをビルドし、staprunコマンドを使えば更新間隔を短く設定できるため、アニメーションがスムーズになります。*2

# カーネルモジュールのビルド
% stap -p4 2048.stp   
/home/neocat/.systemtap/cache/69/stap_698d22858bd5aec36102a998101b3485_7368.ko
 # 保存された*.koをロード. 30ms間隔で更新
% sudo staprun -T 30 /home/neocat/.systemtap/cache/69/stap_698d22858bd5aec36102a998101b3485_7368.ko  
Q2. SSH接続したサーバで動かそうとしたけどキー操作が効かない。

仕様です。キーボードドライバのイベント関数にフックを入れることで動作しているので、本体に接続されたPS/2やUSBのキーボードでないと動きません。

Q3. 全然クリアできない…

仕様です(?)。コツを紹介してくれるページもあるようですから検索してみましょう。AIで自動的に解いてくれるツールもあるので、眺めてみると何か掴めるかもしれませんよ。 http://ov3y.github.io/2048-AI/

Q4. ばぐってるよ。

えいやっと3時間くらいで作ったので勘弁してください。っていうかぜひ直して公開ください。

Q5. ちょっと旬を過ぎちゃってるんじゃない?

2日ほど遅かったですね……。

Q6. そもそもなんでカーネルの中で実行する必要が…?

何でそんなこと聞くんですか? (´・_・`)

*1:./2048.stp で実行すれば自動的に入力抑制します。

*2:./2048.stp で実行すれば自動的に30msに設定します。