kakts-log

programming について調べたことを整理していきます

inotify_add_watch()によるファイル・ディレクトリ変更検知イベントを受信した際の挙動について

概要

linuxシステムコールでのinotify_add_watch(),によるファイル、ディレクトリの変更検知を行った際の挙動を整理する。

監視対象として特定のディレクトリを指定した場合、変更イベントの内容を保持するinotify_event構造体のname とlenにそれぞれ変更があったファイル名とファイル名の長さが入る。
しかし、監視対象としてディレクトリでなく特定のファイルを指定した場合、inotify_event->nameとlenには値が入らない

参考

Man page of INOTIFY

監視対象のディレクトリ内のオブジェクトに対してイベントが発生した場合、 inotify_event 構造体で返される name フィールドは、ディレクトリ内のファイル名を表す。

検証

inotify_add_watchの第2引数に、それぞれ特定のファイル・特定のディレクトリを指定した場合にinotify_event構造体の値がどうなるかを検証する

2つの例を挙げるが、主に異なるのは、inotify_add_watchの引数となる

wd = inotify_add_watch(inotifyFd, WATCH_TARGET_FILE, IN_ALL_EVENTS);

1: 特定のディレクトリを監視対象に指定した場合

`
|-- inotify_logger_directory
|-- log.txt
|-- target // 監視対象ディレクトリ
    `-- target.txt
    `-- target2.txt

コード:

tlpi/tlpi/inotify/inotify_logger_directory.c at master · kakts/tlpi · GitHub

/**
 * inotifyを使ったファイル変更検知ロガー
 * 
 * inotifyによる監視対象
 * 特定のファイルを指定
 */

#include <sys/inotify.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include "../lib/tlpi_hdr.h"

#define WATCH_TARGET_DIR "./target"
#define LOG_FILE "./log.txt"

/**
 * 指定したファイルディスクリプタが指すファイルにinotify_event構造体の内容を書き込む
 */
static void writeToLog(int fd, struct inotify_event *p)
{
    printf("writeToLog\n");
    printf("p->len:%d\n", p->len);
    printf("p->name:%s\n", p->name);
    ssize_t numWrite;

    // write()を使ってp->nameをファイルに書き込む
    numWrite = write(fd, p->name, p->len);
    if (numWrite == -1) {
        errExit("write");
    }

    // 改行文字を書き込む
    numWrite = write(fd, "\n", 1);
    if (numWrite == -1) {
        errExit("write");
    }

    printf("Write %ld bytes to log file. name:%s\n", (long) numWrite, p->name);
}

/**
 * read()に指定したバッファサイズ小さく、次のinotify_event構造体を読み込めない場合がある
 * これを回避するために、inotify_eventを最低でも1つは保持できるだけのサイズ(sizeof(struct inotify_event) + NAME_MAX + 1)を確保すれば良い
 */
#define BUF_LEN (10 * sizeof(struct inotify_event) + NAME_MAX + 1)


int main(int argc, char *argv[])
{
    int inotifyFd, logFd, wd, j;
    char buf[BUF_LEN];
    ssize_t numRead;
    char *p;
    struct inotify_event *event;

    // inotifyインスタンスを作成
    inotifyFd = inotify_init();
    if (inotifyFd == -1) {
        close(inotifyFd);
        errExit("inotify_init");
    
    }

    // 変更検知対象のディレクトリを指定
    // 実行ファイルと同一ディレクトリのファイルを監視する場合は、nameが入らない
    wd = inotify_add_watch(inotifyFd, WATCH_TARGET_DIR, IN_ALL_EVENTS);
    if (wd == -1) {
        errExit("inotify_add_watch");
    }
    printf("Watching %s using wd %d\n", "target.txt", wd);

    // ログ出力用のファイルを開く
    // 書き込む場合はファイル末尾へ追加する
    logFd = open(LOG_FILE, O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
    if (logFd == -1) {
        errExit("open");
    }



    // イベント処理用の無限ループ
    for (;;) {
        numRead = read(inotifyFd, buf, BUF_LEN);
        if (numRead == 0) {
            fatal("read() from inotify fd returned 0!");
        }

        if (numRead == -1) {
            errExit("read");
        }

        printf("Read %ld bytes from inotify fd\n", (long) numRead);

        // 読み込んだバッファの内容をinotify_event構造体にキャストして表示
        event = (struct inotify_event *) buf;
        writeToLog(logFd, event);
    }

    exit(EXIT_SUCCESS);
}

出力結果は下記となる。 ./target ディレクトリ配下のファイルを操作すると、操作したファイルの名前の情報がinotify_eventに渡っているのを確認できます。

Read 32 bytes from inotify fd
writeToLog
p->len:16
p->name:target.txt
Write 1 bytes to log file. name:target.txt
Read 32 bytes from inotify fd

....
writeToLog
p->len:16
p->name:target2.txt
Write 1 bytes to log file. name:target2.txt
Read 48 bytes from inotify fd
writeToLog
p->len:32

2: 特定のファイルを監視対象に指定した場合

root@a0cd1290306f:/tlpi/inotify# tree
.
|
|-- inotify_logger
|-- log.txt // log出力先
`-- target.txt // 監視対象ファイル

上記のような構成で、./target.txtに対して変更があった場合、変更情報を./log.txtに出力させます。

コード tlpi/tlpi/inotify/inotify_logger.c at master · kakts/tlpi · GitHub

/**
 * inotifyを使ったファイル変更検知ロガー
 * 
 * inotifyによる監視対象
 * 特定のファイルを指定
 */

#include <sys/inotify.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include "../lib/tlpi_hdr.h"

#define WATCH_TARGET_FILE "./target.txt"
#define LOG_FILE "./log.txt"

/**
 * 指定したファイルディスクリプタが指すファイルにinotify_event構造体の内容を書き込む
 */
static void writeToLog(int fd, struct inotify_event *p)
{
    printf("writeToLog\n");
    printf("p->len:%d\n", p->len);
    printf("p->name:%s\n", p->name);
    ssize_t numWrite;

    // write()を使ってp->nameをファイルに書き込む
    numWrite = write(fd, LOG_FILE, strlen(LOG_FILE));
    if (numWrite == -1) {
        errExit("write");
    }

    // 改行文字を書き込む
    numWrite = write(fd, "\n", 1);
    if (numWrite == -1) {
        errExit("write");
    }

    printf("Write %ld bytes to log file. name:%s\n", (long) numWrite, p->name);
}

/**
 * read()に指定したバッファサイズ小さく、次のinotify_event構造体を読み込めない場合がある
 * これを回避するために、inotify_eventを最低でも1つは保持できるだけのサイズ(sizeof(struct inotify_event) + NAME_MAX + 1)を確保すれば良い
 */
#define BUF_LEN (10 * sizeof(struct inotify_event) + NAME_MAX + 1)


int main(int argc, char *argv[])
{
    int inotifyFd, logFd, wd, j;
    char buf[BUF_LEN];
    ssize_t numRead;
    char *p;
    struct inotify_event *event;

    // inotifyインスタンスを作成
    inotifyFd = inotify_init();
    if (inotifyFd == -1) {
        close(inotifyFd);
        errExit("inotify_init");
    
    }

    // 変更検知対象のファイルを指定
    // 実行ファイルと同一ディレクトリのファイルを監視する場合は、nameが入らない
    wd = inotify_add_watch(inotifyFd, WATCH_TARGET_FILE, IN_ALL_EVENTS);
    if (wd == -1) {
        errExit("inotify_add_watch");
    }
    printf("Watching %s using wd %d\n", "target.txt", wd);

    // ログ出力用のファイルを開く
    // 書き込む場合はファイル末尾へ追加する
    logFd = open(LOG_FILE, O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
    if (logFd == -1) {
        errExit("open");
    }



    // イベント処理用の無限ループ
    for (;;) {
        numRead = read(inotifyFd, buf, BUF_LEN);
        if (numRead == 0) {
            fatal("read() from inotify fd returned 0!");
        }

        if (numRead == -1) {
            errExit("read");
        }

        printf("Read %ld bytes from inotify fd\n", (long) numRead);

        // 読み込んだバッファの内容をinotify_event構造体にキャストして表示
        event = (struct inotify_event *) buf;
        writeToLog(logFd, event);
    }

    exit(EXIT_SUCCESS);
}

出力結果は下記となる。

writeToLog
p->len:0
p->name:
Write 1 bytes to log file. 

特定のファイルを指定した場合はlen, nameに値が入ってこないのを確認できました。