MacOSのデーモンをマスターしよう：Launchdプロセス

[サイエンスパーク株式会社](https://sciencepark.co.jp/)でのMac開発ではデバイスの接続の制御などOSが起動してからすぐに行いたいものが多い。その時使うのがlaunchdを使ったプログラムのデーモン化、エージェント化だ。
アンチウィルスなどユーザーのログインよりも前に起動しないといけないプログラムはデーモン化することで、OS起動直後にログインの状態に関係なくプログラムを起動できる。
今回はプログラムをデーモン化するための設定とデーモンと同じような動作をするエージェントについて解説していこう。

### デーモン、エージェントとは？
始めに簡単にデーモンとエージェントとは何か見てみよう。
Appleの[資料](https://developer.apple.com/library/archive/technotes/tn2083/_index.html#//apple_ref/doc/uid/DTS10003794-CH1-SECTION2)には以下が定義されている。
##### デーモン：
> A daemon is a program that runs in the background as part of the overall system (that is, it is not tied to a particular user). 

##### エージェント：
> An agent is a process that runs in the background on behalf of a particular user.

つまり、デーモンもエージェントもバックグラウンドプロセスの一種ということ。バックグラウンドプロセスとはGUIを表示しない、ターミナルにアタッチしていないプロセスのこと。デーモンとエージェントの主な違いはデーモンはシステムのプロセスとして実行されていて、エージェントは特定のユーザーのプロセスとして実行されていること。ユーザーがログインしたタイミングでロードされ、設定ファイルで指定したタイミングで起動する。

### デーモンとエージェントの登録
次にどうやってプログラムをデーモン化するかを見てみよう。
デーモン化は設定ファイル(.plist)を特定のフォルダに格納することで実現できる。launchdが起動時にそのフォルダ内の設定ファイルを読み込みプログラムをデーモンとして登録する。

今回はアンチウィルスプログラムの例を使って設定ファイルの書き方を解説する。
設定ファイルの名称は任意だが分かりやすいようにバンドル名を使うのが一般的。今回は`jp.co.sciencepark.sampleAntiVirus.plist`にする。
どのような設定を設定ファイルに書き込む必要があるか、アンチウィルスの条件をまず挙げていこう。

アンチウィルスはユーザーがログインしていなくても、OS起動時から外部の脅威からMacを守る必要がある。そしてユーザーや悪意のあるプログラムががアンチウィルスのプロセスを終了してもすぐに再起動してMacが無防備な状態にならないようにする必要がある。

そのため以下の条件を満たす必要がある。

* ユーザーがログインしていなくても起動する
* OS起動時に実行される
* 終了時すぐに再起動する

このような条件を満たす設定ファイルをどうやって作成するか順に見ていこう。

#### ユーザーがログインしていなくても起動する
ユーザーがログインしていなくてもプログラムを起動するにはプログラムをエージェントではなくデーモンとして登録する必要がある。
デーモンとしてプログラムを登録するには特定のフォルダに設定ファイルを格納するだけでできる。
デーモンとして登録したい場合は以下のフォルダにファイルを格納する。

* `/System/Library/LaunchDaemons`
* `/Library/LaunchDaemons`


`/System/Library`配下のパスはOSのデーモンやエージェント用の設定plistファイルが格納されている。
サードパーティーの設定plistファイルは`/Library`配下のパスにすることが推奨されている。
そのため今回は`/Library/LaunchDaemons`配下に設定ファイルを配置する。
設定ファイルの権限は644、所有者は`root:wheel`である必要がある。配置したファイルの権限と所有者を以下のコマンドで変更する。

```sh
sudo chmod 644 /Library/LaunchDaemons/jp.co.sciencepark.sampleAntiVirus.plist
sudo chown root:wheel /Library/LaunchDaemonsjp.co.sciencepark.sampleAntiVirus.plist
```

権限と所有者を上記のように設定しておかないと`Program`で設定した実行ファイルが実行されない。
手動で起動しようとしても以下のようなエラーメッセージがでる。

```
/Library/LaunchDaemons/jp.co.sciencepark.sampleAntiVirus.plist: Path had bad ownership/permissions
Load failed: 122: Path had bad ownership/permissions
```
デーモンが自動的に起動しない場合、権限と所有者をちゃんと設定したか確認しよう。

ついでにエージェントの話をすると、エージェントとして登録するには設定ファイルを以下のフォルダに格納する。

* `/System/Library/LaunchAgents`
* `/Library/LaunchAgents`
* `~/Library/LaunchAgents`

#### 共通設定
アンチウィルス用の設定を設定ファイルに書き込む前にまず必須の項目を見ていこう。絶対入れて置く必要がある設定は以下の２つ。

* `Label`：launchdがデーモン、エージェントを判別するためのレーベル。判別のために使われるためユニークである必要がある。
* `Program`：実行ファイルへのパスを指定する。

この項目を書き込んだ設定ファイルは以下のようになる.


```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>Label</key>
		<string>jp.co.sciencepark.sampleAntiVirus</string>
		<key>Program</key>
		<string>/Applications/sampleAntivirus.app/Contents/MacOS/sampleAntivirus</string>
	</dict>
</plist>
```

#### OS起動時に実行
上記の２つの項目が必須と言ったがこれだけではいつプログラムを起動するのか指定していないので設定ファイルは不完全な状態だ。設定ファイルにプログラムの起動タイミングを指定する必要がある。今回のアンチウィルスはOS起動時に実行されるように設定するために以下の項目を追加する。

* `RunAtLoad`：デーモンの実行タイミングを指定する設定。この場合デーモンがロードされた直後に実行される。デーモンはOS起動時にロードされるのでOS起動直後に実行される。

設定ファイルに以下のように追加する。

```xml
		<key>RunAtLoad</key>
		<true/>
```

#### 終了時すぐに再起動
デーモンプロセスの終了時にすぐに再起動するには以下の項目を設定ファイルに追加する。

* `KeepAlive`：終了時に再起動するかを指定する設定。`true`の場合再起動する。

設定ファイルに以下のように追加する。

```xml
		<key>KeepAlive</key>
		<true/>
```

#### 最終設定ファイル
以上の4つの項目を書き込めばアンチウィルス用の条件を満たした設定ファイルが完成する。
最終的に今回のアンチウィルス用デーモン設定ファイルは以下のようになる。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>Label</key>
		<string>jp.co.sciencepark.sampleAntiVirus</string>
		<key>Program</key>
		<string>/Applications/sampleAntivirus.app/Contents/MacOS/sampleAntivirus</string>
		<key>RunAtLoad</key>
		<true/>
		<key>KeepAlive</key>
		<true/>
	</dict>
</plist>
```
このファイルを`/Library/LaunchDaemons`配下に配置することでプログラムがアンチウィルスの条件を満たしたデーモンプロセスとして起動する。

### その他の設定
アンチウィルスの例で紹介した設定の他にもデーモン、エージェントには細かい設定が指定できる。
設定できる項目が多いのでここではよく使う項目だけを紹介していく。

#### `ProgramArguments`

この項目は実行ファイルに引数を渡したい時に`Program`の代わりに設定する。アレー形式で実行ファイルとその引数を指定できる。
設定ファイルに以下のように追加する。

```xml
<key>ProgramArguments</key>
<array>
	<string>jp.co.sciencepark.sampleAntiVirus</string>
	<string>--flag1</string>
	<string>--option1=9</string>
	<string>/Volumes/Macintosh HD</string>
</array>
```

#### `EnvironmentVariables`
Dictionary形式でデーモンが実行される環境の環境変数を指定できる。
設定ファイルに以下のように追加する。

```xml
<key>EnvironmentVariables</key>
<dict>
	<key>PATH</key>
	<string>/bin:/usr/bin:/usr/local/bin</string>
</dict>
```


#### `WorkingDirectory`
デーモンのワーキングディレクトリを指定できる。相対パスはこのワーキングディレクトリからのパスになる。
設定ファイルに以下のように追加する。

```xml
<key>WorkingDirectory</key>
<string>/Users/sciencepark/Documents</string>
```

#### `StartInterval`
上記で紹介した`RunAtLoad`以外にも他の起動タイミングを指定する項目がある。その一つが`StartInterval`。起動タイミングは設定ファイルの必須項目なのでどれか一つは記入しないといけない。
設定ファイルに以下のように追加する。

```xml
<key>StartInterval</key>
<integer>3600</integer>
```
Integerで起動間隔を指定する（秒）。上の設定の場合、毎時間デーモンが実行される。一定間隔で行ってもらいたいタスクはこの起動タイミング設定を使ってデーモンまたはエージェントとして登録しよう。

#### 項目詳細

項目は[Appleの資料](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html)とこの[サイト](https://www.launchd.info/)で紹介されている。2つ目のサイトは分かりやすく自分もよく使っているので是非目を通してほしい。

### 最後に
このようにアプリケーションをデーモンやエージェントに設定できる。
監視・制御系の開発以外で使わないと思っていたが、意外にも色々なアプリケーションがアップデータなどをLaunchdエージェントプロセスに設定して自動アップデート機能を実施している。自動アップデートを切りたいのにアプリケーション側でオフにできない場合も、この記事で紹介したパスにアップデータのplistファイルがないか確認してみると嬉しい発見があるかもしれない。


### 参考資料
https://developer.apple.com/library/archive/technotes/tn2083/_index.html#//apple_ref/doc/uid/DTS10003794#Daemonomicon
https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html
https://www.launchd.info/