Raspberry Pi 2 + systemd + udevで、USBデバイス挿入時にサービスを起動する

以前Raspberry Pi 2にsystemdを入れたので、次はその動作を試してみることにしました。

何にしようか考えながらsystemdに関する記事を調べていたところ、

バイスを表すdeviceタイプのUnitも動的に生成されます。これは、udevが新しいデバイスを認識したタイミングで、「systemd」というudevタグを付けると、それを受け取ったsystemdが対応するUnitを生成して有効化します

Systemd入門(3) - cgroupsと動的生成Unitに関する小ネタ - めもめも

というのを見かけ、気になりました。

そこで、以下を参考に、「USBデバイス挿入時にサービスを起動する」ことを試してみました。
Linux: Start daemon on connected USB-serial dongle - Stack Overflow

 
目次

 

環境

Windows 7

 

Raspberry Pi

 

事前確認

systemdのUnitの一つ、.deviceについて、

システムが認識しているデバイス情報を保持する udevデーモンによって自動作成される

「Systemd」を理解する ーシステム起動編ー | ギークを目指して

という説明がありました。

そこで、デフォルトの場合、パソリを挿入した時に systemctl コマンドで自動的に表示されるか試してみました。

pi@raspberrypi ~ $ systemctl --t device -a --full
UNIT                                                               LOAD   ACTIVE SUB     DESCRIPTION
...
sys-devices-platform-...x2d1-1\x2d1.1-1\x2d1.1:1.0-net-eth0.device loaded active plugged ec00
sys-devices-platform-...-target0:0:0-0:0:0:0-block-sda-sda1.device loaded active plugged Silicon-Power8G
sys-devices-platform-...-target0:0:0-0:0:0:0-block-sda-sda2.device loaded active plugged Silicon-Power8G
sys-devices-platform-...host0-target0:0:0-0:0:0:0-block-sda.device loaded active plugged Silicon-Power8G
sys-devices-platform-...0-mmc0:59b4-block-mmcblk0-mmcblk0p1.device loaded active plugged /sys/devices/platform...
...

デフォルトのままでは表示されないようです。

 

udevによるUSBデバイス挿入の検知

USBデバイス挿入を検知してsystemctlに表示するには、udevのruleファイルの作成が必要そうだったため、その作業から行います。

 

idVendorとidProductの値の確認

ruleファイルを作成するためには、対象のUSBデバイスidVendoridProductの値が必要となります。

そこで、dmesgコマンドでusbで絞り込んで確認すると、それっぽい行がありました。
Linuxコマンド集 - 【 dmesg 】カーネルのリングバッファの内容を出力する:ITpro

pi@raspberrypi ~ $ dmesg | grep usb
...
[    3.867886] usb 1-1.4: new low-speed USB device number 5 using dwc_otg
[    3.994069] usb 1-1.4: New USB device found, idVendor=054c, idProduct=01bb
[    4.002768] usb 1-1.4: New USB device strings: Mfr=0, Product=0, SerialNumber=0

 
LinuxのUSBデバイスIDのリストを見ても そのベンダーIDとデバイスIDの組み合わせがFeliCa S320 [PaSoRi] だったため、やはりこれがパソリのもののようでした。
List of USB ID's - linux-usb.org

 

USBデバイス挿入時に動作するかのテスト

udevではUSBデバイス挿入時の動作について設定するところがあったため、まずはsystemdを使わずにudevだけで動作しているかのテストを行います。

以下の記事を参考に、USBデバイス挿入時に日付をログファイルへ出力するruleを作成します。

pi@raspberrypi ~ $ sudo nano /etc/udev/rules.d/90-rc-s320.rules

 

/etc/udev/rules.d/90-rc-s320.rules

表示の都合上複数行に見えますが、実際は改行なしの一行です。もし改行を入れたい場合は、\を使います。

SUBSYSTEM=="usb", ACTION=="add", ATTRS{idProduct}=="01bb", ATTRS{idVendor}=="054c", RUN+="/bin/sh -c 'echo Hello at `date` > /home/pi/hello.log'"

なお、udevのキーRUNにおいて、/bin/shに引数を渡すためにシングルクォート(')で囲んでいます。
variables - passing arguments to shell script from udev rules file - Stack Overflow

 
結果を確認するために、USBポートにパソリを挿入したままRaspberry Piを再起動し、起動時に実行されるかを確認します。

# 再起動前にファイルが無いことを確認
pi@raspberrypi ~ $ ls
python_games
pi@raspberrypi ~ $ sudo reboot

# 再起動後
pi@raspberrypi ~ $ ls
hello.log  python_games
pi@raspberrypi ~ $ cat hello.log
Hello at Tue Jun 23 09:30:03 UTC 2015

実行されているようでした。

 

systemdが認識するためのruleファイルの作成

どのようなruleファイルを作ればよいのか調べてみたところ、

バイスを表すdeviceタイプのUnitも動的に生成されます。これは、udevが新しいデバイスを認識したタイミングで、「systemd」というudevタグを付けると、それを受け取ったsystemdが対応するUnitを生成して有効化します

Systemd入門(3) - cgroupsと動的生成Unitに関する小ネタ - めもめも

とありました。

 
そこで、上記で作成した /etc/udev/rules.d/90-rc-s320.rules

  • バイスの所属するグループであるキーGROUPusb をセット
  • udevタグであるキーTAGに、systemd をセット
  • systemdのサービスと依存関係を持たせるため、ENV{SYSTEMD_WANTS}="rc-s320.service" をセット
    • systemdのdeviceへ表示させるだけなら不要
  • キーNAMEに、任意の名前(例えば、pasori320)をセット
    • バイスを挿すUSBポートによりdeviceファイルの名前が変わってしまうことから、デバイス名を固定するため
      • sys-devices-platform-bcm2708_usb-usb1-1\x2d1-1\x2d1.2.devicesys-devices-platform-bcm2708_usb-usb1-1\x2d1-1\x2d1.4.deviceなど

のように修正します。

 
なお、キーの意味などは以下が参考になりました。

 

/etc/udev/rules.d/90-rc-s320.rules
SUBSYSTEM=="usb", ACTION=="add", ATTRS{idProduct}=="01bb", ATTRS{idVendor}=="054c", GROUP="usb", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rc-s320.service", NAME="pasori320"

 
再度RaspberryPiを再起動し、systemctlで表示されるかを確認します。

pi@raspberrypi ~ $ systemctl --type=device --all
UNIT                                                               LOAD   ACTIVE SUB     DESCRIPTION
...
dev-pasori320.device                                       loaded active plugged 01bb
dev-root.device                                            loaded active plugged Silicon-Power8G
...
sys-devices-platfor...target0:0:0-0:0:0:0-block-sda.device loaded active plugged Silicon-Power8G
sys-devices-platfor...2708_usb-usb1-1\x2d1-1\x2d1.4.device loaded active plugged 01bb
...

元々のsys-devices-platform-bcm2708_usb-usb1-1\x2d1-1\x2d1.4.deviceの他、dev-pasori320.deviceが追加されました。

これでsystemdでUSBデバイスの挿入を検知できました。

 

systemdによるudevのdeivceの検知

.serviceファイルの作成

次は、systemdにて「deviceファイルを認識したらサービスを起動する」ことを試してみます。なお、ファイルの作成の流れでは以下が参考になりました。
【Serf】systemdでserfを自動起動する方法 | Pocketstudio.jp log3

 
今回は、/etc/systemd/system/rc-s320.serviceというファイルを作成し、その中にUnitに関する設定を行います。

pi@raspberrypi ~ $ sudo nano /etc/systemd/system/rc-s320.service

 
作成するUnitの仕様は、

  • udevがパソリを認識後に、サービスを起動
  • USBデバイスシステム起動時の自動起動は無効
    • USBデバイス接続時に初めて起動するため
    • systemctl enableコマンドは不要
  • 他のUnitからの固定的な依存関係になるので、staticなサービスにする
    • [Install]セクションは記述しない
  • 動作確認のため、サービスが起動時にログファイルへ書き込み

としました。

なお、staticなサービスについては、以下の記事が参考になりました。

一方、[Install]セクションを持たないserviceは、有効化/無効化の対象にはなりません。先の出力で「static」と表示されたものがこれにあたります。一般には、他のUnitからの固定的な依存関係で起動することになります。 Systemd入門(1) - Unitの概念を理解する - めもめも

 
また、Restart=alwaysを追加すると、systemctl status rc-s320.serviceでサービスの状態を見た時に、以下の様なエラーが出ます。今回のサービスでは再実行不要なことから、追加していません。

pi@raspberrypi ~ $ systemctl status rc-s320.service
rc-s320.service - Pasori RC-S320 service
   Loaded: loaded (/etc/systemd/system/rc-s320.service; static)
   Active: failed (Result: start-limit) since Wed 2015-06-24 **:**:** UTC; 43min ago
  Process: 1282 ExecStart=/bin/sh -c echo Hello by `date` >> /home/pi/hello2.log (code=exited, status=0/SUCCESS)

Jun 24 **:**:** raspberrypi systemd[1]: rc-s320.service holdoff time over, scheduling restart.
Jun 24 **:**:** raspberrypi systemd[1]: Stopping Pasori RC-S320 service...
Jun 24 **:**:** raspberrypi systemd[1]: Starting Pasori RC-S320 service...
Jun 24 **:**:** raspberrypi systemd[1]: rc-s320.service start request repeated too quickly, refusing to start.
Jun 24 **:**:** raspberrypi systemd[1]: Failed to start Pasori RC-S320 service.
Jun 24 **:**:** raspberrypi systemd[1]: Unit rc-s320.service entered failed state.

 
以下を参考にしつつ、最終的なのは次のものとなります。
systemd.service - freedesktop.org

/etc/systemd/system/rc-s320.service
[Unit]
Description=Pasori RC-S320 service
Requires=dev-pasori320.device
After=dev-pasori320.device

[Service]
Type=simple
ExecStart=/bin/sh -c "echo Hello by `date` >> /home/pi/hello2.log"

 

systemdの設定の反映と確認

必要に応じて、設定の反映と確認を行います。

# 設定の再読み込み
pi@raspberrypi ~ $ sudo systemctl --system daemon-reload

# 状態確認
pi@raspberrypi ~ $ systemctl status rc-s320.service

 

動作テスト

Raspberry Piをシャットダウンして、USBポートからパソリを抜きます。

その後、Raspberry Pi を起動して、サービスが起動していないことを確認します。

pi@raspberrypi ~ $ systemctl status rc-s320.service
rc-s320.service - Pasori RC-S320 service
   Loaded: loaded (/etc/systemd/system/rc-s320.service; static)
   Active: inactive (dead)

 
続いて、USBポートへパソリを挿入し、サービスが起動したことを確認します。

pi@raspberrypi ~ $ systemctl status rc-s320.service
rc-s320.service - Pasori RC-S320 service
   Loaded: loaded (/etc/systemd/system/rc-s320.service; static)
   Active: inactive (dead) since Wed 2015-06-24 10:29:07 UTC; 58s ago
  Process: 1308 ExecStart=/bin/sh -c echo Hello by `date` >> /home/pi/hello2.log (code=exited, status=0/SUCCESS)

# ログファイルの中身
pi@raspberrypi ~ $ cat hello2.log
Hello by Wed Jun 24 10:29:07 UTC 2015

 
以上で、USBデバイス挿入時にサービスを起動することができました。

 

ソースコード

使用した3つのファイルはgistsに上げました。
https://gist.github.com/thinkAmi/3726e0cc9bd009419b65

 

悩んだこと

Linuxまわりが詳しくないので...。

Raspbianで使われているshellの確認

以下を参考にして、確認してみました。
デフォルトのShell を変更する

 

使用できる種類の確認
pi@raspberrypi ~ $ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash

 

ユーザごとのデフォルトのshell
pi@raspberrypi ~ $ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
pi:x:1000:1000:,,,:/home/pi:/bin/bash
sshd:x:101:65534::/var/run/sshd:/usr/sbin/nologin
ntp:x:102:104::/home/ntp:/bin/false
statd:x:103:65534::/var/lib/nfs:/bin/false
messagebus:x:104:106::/var/run/dbus:/bin/false
usbmux:x:105:46:usbmux daemon,,,:/home/usbmux:/bin/false
lightdm:x:106:109:Light Display Manager:/var/lib/lightdm:/bin/false

 

その他の部分

 

参考