NetBSDのfilemon機能で取得可能なファイルイベントを汎用的に取り扱えるようにしてみる

[NetBSD Advent Calendar 2021](https://qiita.com/advent-calendar/2021/netbsd) 9日目の記事です。今日は[8日目の記事](https://qiita.com/furandon_pig/items/66cf8f20e5b3a21ef234)で紹介した[filemon(4)](https://man.netbsd.org/NetBSD-9.2/i386/filemon.4)を利用したアプリに、他のファイルイベントを追加してみようと思います。

## ファイルイベントの追加

ファイルイベントの追加、と言っても必要な手順は[8日目の記事](https://qiita.com/furandon_pig/items/66cf8f20e5b3a21ef234)を応用するだけなので難しいことはありません。

`if (buf[0] == 'W') { ... }` の部分で `W` の部分でイベントタイプを判定しており、この部分に一連の[filemonのイベントタイプ](https://qiita.com/furandon_pig/items/5d324b06a0456e3080c8#filemon)を追加すれば良さそうです。

```c
               fp = fopen(temp_path, "r");
               if (fp) {
                   count = 0;
                   while (fgets(buf, BUFSIZ-1, fp)) {
                       if (count++ < read_line) {
                           continue;
                       }

                       if (buf[0] == 'W') {
                           // "W 521 cal.txt" => "cal.txt"
                           char *path = strchr(buf, ' ');
                           path = strchr(++path, ' ');
                           ++path;

                           switch(fork()) {
                                case -1:
                                    fprintf(stderr, "cannot fork");
                                    break;
                                case 0:
                                    if (w_cmd) {
                                        char *script[] = { w_cmd, path, NULL };
                                        execvp(script[0], script);
                                    }
                                default:
                                    wait(&status);
                           }
                       }
                       if (buf[0] == '#') {
                           is_done = 0;
                           break;
                       }
                   }
                   read_line += count - read_line;

                   fclose(fp);
                   usleep(700000);
               }
           }
```

サンプルコードはGistに置いてあります。

 * [nbsd_filemon_watch_any_event.c](https://gist.github.com/furandon-pig/b9223fafa5b1311baa8e6c3ba622e2d6)

複数のファイルイベントを一括して処理するため、ファイルイベントを処理する部分は関数として切り出しておきます。

```c
void exec_handler_command(char *path, char *cmd)
{
    int status;

    switch(fork()) {
        case -1:
            fprintf(stderr, "cannot fork");
            break;
        case 0:
            if (cmd) {
                char *script[] = { cmd, path, NULL };
                execvp(script[0], script);
            }
        default:
            wait(&status);
    }
}
```

あとは実行時引数として `-x x_event.sh` のようにファイルイベントと紐づけたいスクリプトを渡せるようにします。

```c
int main(int argc, char *argv[])
{
...
    while ((ch = getopt(argc, argv, "c:d:e:f:l:m:r:w:x:")) != -1) {
        switch (ch) {
            case 'c': c_cmd = optarg; break;
            case 'd': d_cmd = optarg; break;
            case 'e': e_cmd = optarg; break;
            case 'f': f_cmd = optarg; break;
            case 'l': l_cmd = optarg; break;
            case 'm': m_cmd = optarg; break;
            case 'r': r_cmd = optarg; break;
            case 'w': w_cmd = optarg; break;
            case 'x': x_cmd = optarg; break;
            case '?':
            default:
                break;
        }
    }
```

あとはファイルイベントにマッチする文字を見つけたら、指定したスクリプトを実行させます。

```c
           is_done = 1;
           while (is_done) {
...
                   while (fgets(buf, BUFSIZ-1, fp)) {
                       if (count++ < read_line) {
                           continue;
                       }

                       path = buf;
                       switch (buf[0]) {
                           case 'C': exec_handler_command(path, c_cmd); break;
                           case 'D': exec_handler_command(path, d_cmd); break;
                           case 'E': exec_handler_command(path, e_cmd); break;
                           case 'F': exec_handler_command(path, f_cmd); break;
                           case 'L': exec_handler_command(path, l_cmd); break;
                           case 'M': exec_handler_command(path, m_cmd); break;
                           case 'R': exec_handler_command(path, r_cmd); break;
                           case 'W': exec_handler_command(path, w_cmd); break;
                           case 'X': exec_handler_command(path, x_cmd); break;
                           default: break;
                       }

                       if (buf[0] == '#') {
                           is_done = 0;
                           break;
                       }
                   }
                   fclose(fp);
                   usleep(700000);
               }
           }
```

これで必要な機能が実装できました。ファイルイベントに紐づけて実行したいスクリプトを用意しておきます。

```sh
# cat x_event.sh
#!/bin/sh
notify-send -u normal 'exit'
```

## ファイルイベントを確認してみる

さっそくファイルイベントを確認してみます。以下のように一通りのファイルイベントにスクリプトを紐づけて実行します。

```sh
# ./nbsd_filemon_watch_any_event \
    -c c_event.sh \
    -d d_event.sh \
    -e e_event.sh \
    -f f_event.sh \
    -l l_event.sh \
    -m m_event.sh \
    -r r_event.sh \
    -w w_event.sh \
    -x x_event.sh \
    bash
```

コマンドを実行するたびに複数のファイルイベントが発生していることが確認できます。

![nbsd1209_01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61137/225f4f13-3ebf-b209-37f7-0f085a52ea13.gif)

## まとめ

[filemon(4)](https://man.netbsd.org/NetBSD-9.2/i386/filemon.4)で取得可能なファイルイベントに対し、任意のスクリプトを紐づけられるようにしてみました。現状ではサンプルファイル程度ですが、これを応用するとCI等のトリガーとして利用できそうです。