ファイルの変更を監視

Linux だと inotify(2) を利用してできる.poll(2) 等と組み合わせるといい.*BSD とか Mac OS X とかだと kqueue(2) を使うらしいけどあんまり調べてない.


監視対象のファイルが削除されるか C-c されると止まる.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <errno.h>
#include <poll.h>
#include <sys/inotify.h>

int main(int argc, char *argv[])
{
  if (argc != 2) {
    printf("usage: %s filename\n", argv[0]);
    exit(EXIT_SUCCESS);
  }

  const int fd = inotify_init();
  if (fd < 0) {
    perror("inotify_init");
    exit(EXIT_FAILURE);
  }

  const int wd = inotify_add_watch(fd, argv[1], IN_MODIFY | IN_DELETE_SELF);
  if (wd < 0) {
    perror("inotify_add_watch");
    exit(EXIT_FAILURE);
  }

  while (1) {
    struct pollfd fds[1];
    fds[0].fd = fd;
    fds[0].events = POLLIN | POLLERR;

    int nfds = poll(fds, 1, -1);
    if (nfds < 0) {
      if (errno == EINTR) {
        goto FINISH;
      } else {
        perror("poll");
        exit(EXIT_FAILURE);
      }
    } else if (nfds > 0) {
      char buf[1024];
      const ssize_t s = read(fd, buf, sizeof buf);
      if (s < 0) {
        perror("read");
        exit(EXIT_FAILURE);
      }
      int i = 0;
      while (i < s) {
        const struct inotify_event *ev = (const struct inotify_event *)buf;
        i += offsetof(struct inotify_event, name) + ev->len;
        if (ev->mask & IN_MODIFY) {
          puts("modified");
        } else if (ev->mask & IN_DELETE_SELF) {
          puts("deleted");
          goto FINISH;
        }
      }
    }
  }
FINISH:
  close(fd);
  return 0;
}


しかし C で書くのも面倒.Ruby + EventMachine でもっと手軽に扱える.

#!/usr/bin/ruby
require 'eventmachine'

module Watcher
  def file_modified
    puts 'modified'
  end

  def file_deleted
    puts 'deleted'
    EM.stop_event_loop
  end
end

EM.kqueue = true if EM.kqueue?
EM.run do
  EM.watch_file ARGV.first, Watcher
end