してみるテストロゴ
Apache 2.4系でHTTP/2サーバを構築してみるテスト。

FreeBSDで、libinotifyによるディレクトリ監視を使ってみる

FreeBSD 11のpkgにlibinotifyが入ったようなので、これを試して見ます。

inotifyとは、Linux由来のディレクトリ監視APIのことです。

これを、BSD系のOSでも使えるようにするのが、libinotifyになります。

FreeBSDではFreeBSD11以降で、利用できます。

ディレクトリ監視APIとは、たとえば、ディレクトリ内でファイルが作られた、消された、移動したといったディレクトリに対するイベントを監視し、アプリケーションソフトに通知するAPIです。

これまで、UNIX系のOSでは、それぞれOS毎に異なるAPIでディレクトリ監視を実現していました。

LinuxはiNotify、FreeBSDなどBSD系はkqueue、Solarisはport_create/port_getといった感じです。

FreeBSDでinotifyが利用できるようになったため、今後、オープンソース系はinotifyで統一となりそうです。

kqueueのなにが使いにくかったのか?

愚痴になるんですけど、kqueueは、ディレクトリ内ファイル変更の監視が弱かった。

  Windows Linux Solaris FreeBSD
API関数 ReadDirectoryChanges inotify_init
inotify_add_watch
port_create
port_get
kqueue
kevent
ディレクトリ内
ファイル生成
ディレクトリ内
ファイル削除
ディレクトリ内
ファイル属性変更通知
ディレクトリ内
ファイル内容変更通知
×
ディレクトリ内
ファイル移動元通知
ディレクトリ内
ファイル移動先通知
ディレクトリツリーの監視 × × ×

WindowsとLinuxのディレクトリ監視は、ディレクトリ内で何のファイルが変更されたのか、把握できるのですが、FreeBSDにはその機能が無かったので、ディレクトリ監視に加えて、ディレクトリ内のファイルを監視しなければなりませんでした。

Solarisはディレクトリエントリのmtimeが変更されると、何のファイルが変更されたのかはわかりませんが、何等か変更があったことは通知してくれるので、それをトリガに、変更されたファイルを探しに行けばよいので、△としています。

素人考えでは、FreeBSDもSolarisのように、ディレクトリエントリのmtimeが変更されたら、変更を通知してもいいようなものですが、どうも、そうはしてくれません。これで困っていました。

libinotifyは、BSD系のkqueueでディレクトリを監視し、さらにその内部のファイルもkqueueで監視してくれるので、ディレクトリ内のファイル数に応じて負荷はかかりそうですが、同じことができるようになります。

libinotifyのインストール

ではインストールしてみます。

といっても、管理者権限で、

# pkg install libinotify

これだけです。

サンプルコード

ということで、実験に使ったコードです。無駄にC++で、エラーハンドリングも不十分ですがご容赦を。

inotify_sample.cpp
#include <unistd.h>
#include <poll.h>
#include <sys/inotify.h>
#include <iostream>

int main( int argc, char** argv )
{
    int fd = -1;
    int wd = -1;
    if(argc<2){
        perror("error: please set directory");
        return 1;
    }
    char* dirname = argv[1];

    if( (fd = inotify_init()) == -1 ) {
        perror( "error: inotify_init" );
        return 1;
    }

    wd=inotify_add_watch(fd,dirname,IN_ATTRIB|IN_MODIFY|IN_CREATE|IN_DELETE|IN_MOVE);

    struct pollfd  spfd;
    spfd.fd=fd;
    spfd.events=POLLRDNORM;
    spfd.revents=0;

    int length = 0;
    struct inotify_event event;
    char* pc=nullptr;
    while(poll(&spfd,1,1000*10)>0){
        if( (length = read( fd, &event,  sizeof(event) ) ) < 0 ) break;
        if(length!=sizeof(event))break;
        if(event.len>0){
            pc=(char*)malloc(event.len);
            if(pc){
                if((length=read( fd,pc,event.len))<0)break;
            }
        }else{
            pc=nullptr;
        }
        if(pc){
            std::cout  << pc << " ";
            free(pc);
            pc=nullptr;
        }
        if(event.mask&IN_ATTRIB) std::cout << "IN_ATTRIB ";
        if(event.mask&IN_MODIFY) std::cout << "IN_MODIFY " ;
        if(event.mask&IN_CREATE) std::cout << "IN_CREATE ";
        if(event.mask&IN_DELETE) std::cout << "IN_DELETE ";
        if(event.mask&IN_MOVED_FROM)std::cout << "IN_MOVED_FROM ";
        if(event.mask&IN_MOVED_TO) std::cout << "IN_MOVED_TO";
        std::cout << std::endl;
    }

    inotify_rm_watch( fd, wd );
    close(fd);

    return 0;
}

このサンプルは、10秒間なにもなければ終了し、10秒以内にファイルの変更通知を受け取れば、その内容を標準出力に出力し、さらに10秒間、ファイルの変更通知を待ちます。

コンパイルは

$ g++6 -g -linotify -o inotify_sample inotify_sample.cpp 

です。

ターミナルを2つ用意し、両方ともinotify_sampleがあるディレクトリに移動します。

TERMINAL 1
$ ./inotify_sample .

と打って(引数にピリオドを忘れないでください!)から、10秒以内に

TERMINAL 2
$ touch test.txt
$ vi test.txt
$ rm test.txt

とすると、TERMINAL 1側に、

TERMINAL 1
$ ./inotify_sample .
test.txt IN_CREATE
test.txt IN_MODIFY
test.txt IN_DELETE

と出力されます。

ちなみにパーミッションを変えるとIN_ATTRIBの通知が来ます。

ZFSでも同じように動きますが、ACLについては、パーミッションと異なり、変更しても通知はありませんでした。

NEXT >> トップページに戻る

©Copyrights 2015-2019, non-standard programmer

このサイトは、あくまでも私の個人的体験を、綴ったものです。 軽く参考程度にご利用ください。