一个简单的 inotify 应用程序

这里的简单应用程序参见 下载 部分)遵循以上的通用逻辑。我们使用一个信号处理程序来监控 ctrl-cSIGINT)并且重置一个标志keep_running)使应用程序了解终止操作。真实的 inotify 调用在 utility 例程当中完成。注意,我们还创建了一个队列,这样能够将事件从 inotify 底层对象中清除,留着稍后处理。在真实的应用程序中,您可能想用一个不同于您处理事件所用的线程具有更高优先级)来完成这一操作。对于该应用程序,只是为了对一般原理进行举例说明。我们采用了一个简单的事件链表,在其中我们队列中的每个条目都包含原始事件以及指向队列中下一事件指针的空间。

主程序

清单 2 中展示了信号处理例程和主例程。在这个简单示例中,在命令行对每个传递进来的文件会目录建立监控,并利用事件掩码 IN_ALL_EVENTS 来监控每个对象的所有事件。在真实的应用程序中,您可能只希望跟踪文件与目录的创建或删除事件,因此您可以屏蔽打开、关闭以及属性改变事件。如果您对文件或目录的重命名和移动不感兴趣,您也可以屏蔽各种移动事件。关于更多细节,参见 inotify 帮助信息。

清单 2. inotify-test.c 的简单主程序
 


/* Signal handler that simply resets a flag to cause termination */
void signal_handler (int signum)
{
  keep_running = 0;
}

int main (int argc, char **argv)
{
  /* This is the file descriptor for the inotify watch */
  int inotify_fd;

  keep_running = 1;

  /* Set a ctrl-c signal handler */
  if (signal (SIGINT, signal_handler) == SIG_IGN)
    {
      /* Reset to SIG_IGN (ignore) if that was the prior state */
      signal (SIGINT, SIG_IGN);
    }

  /* First we open the inotify dev entry */
  inotify_fd = open_inotify_fd ();
  if (inotify_fd > 0)
    {

      /* We will need a place to enqueue inotify events,
         this is needed because if you do not read events
         fast enough, you will miss them. This queue is 
         probably too small if you are monitoring something
         like a directory with a lot of files and the directory 
         is deleted.
       */
      queue_t q;
      q = queue_create (128);

      /* This is the watch descriptor returned for each item we are 
         watching. A real application might keep these for some use 
         in the application. This sample only makes sure that none of
         the watch descriptors is less than 0.
       */
      int wd;


      /* Watch all events (IN_ALL_EVENTS) for the directories and 
         files passed in as arguments.
         Read the article for why you might want to alter this for 
         more efficient inotify use in your app.      
       */
      int index;
      wd = 0;
      printf("\n");
      for (index = 1; (index < argc) && (wd >= 0); index++) 
{
  wd = watch_dir (inotify_fd, argv[index], IN_ALL_EVENTS);
}

      if (wd > 0) 
{
  /* Wait for events and process them until a 
         termination condition is detected
   */
  process_inotify_events (q, inotify_fd);
}
      printf ("\nTerminating\n");

      /* Finish up by closing the fd, destroying the queue,
         and returning a proper code
       */
      close_inotify_fd (inotify_fd);
      queue_destroy (q);
    }
  return 0;
}

使用 inotify_init 打开文件描述符

清单 3 展示了用于创建 inotify 实例以及获取其文件描述符的简单实用工具函数。文件描述符返回给了调用者。如果出现错误,返回值将为负。

清单 3. 使用 inotify_init
 


/* Create an inotify instance and open a file descriptor
   to access it */
int open_inotify_fd ()
{
  int fd;

  watched_items = 0;
  fd = inotify_init ();

  if (fd < 0)
    {
      perror ("inotify_init () = ");
    }
  return fd;
  }

使用 inotify_add_watch 添加一个监控

一旦我们有了用于 inotify 实例的文件描述符之后,就需要增加一个或多个监控。您可以使用掩码来设置想要监控的事件。在本例中,采用掩码 IN_ALL_EVENTS,来监控所有可用事件。

清单 4. 使用 inotify_add_watch
 


int watch_dir (int fd, const char *dirname, unsigned long mask)
{
  int wd;
  wd = inotify_add_watch (fd, dirname, mask);
  if (wd < 0)
    {
      printf ("Cannot add watch for \"%s\" with event mask %lX", dirname,
      mask);
      fflush (stdout);
      perror (" ");
    }
  else
    {
      watched_items++;
      printf ("Watching %s WD=%d\n", dirname, wd);
      printf ("Watching = %d items\n", watched_items); 
    }
  return wd;
}
            

事件处理循环

现在我们已经设置了一些监控,接下来就要等待事件。如果还存在监控,并且 keep_running 标志没有被信号处理程序重置,则循环会一直进行。循环进程等待事件的发生,对有效事件进行排队,并在返回等待状态之前处理队列。在真实应用程序当中,您可能会将事件放入线程队列中,而在另一个线程中处理它们,清单 5 展示了该循环。

清单 5. 事件处理循环
 


int process_inotify_events (queue_t q, int fd)
{
  while (keep_running && (watched_items > 0))
    {
      if (event_check (fd) > 0)
{
  int r;
  r = read_events (q, fd);
  if (r < 0)
    {
      break;
    }
  else
    {
      handle_events (q);
    }
}
    }
  return 0;
  }

等待事件

在我们的示样例应用程序中,循环会不停地进行下去,直至监控事件出现或者收到了中断信号。清单 6 展示了相关代码。

清单 6. 等待事件或者中断
 


int event_check (int fd)
{
  fd_set rfds;
  FD_ZERO (&rfds);
  FD_SET (fd, &rfds);
  /* Wait until an event happens or we get interrupted 
     by a signal that we catch */
  return select (FD_SETSIZE, &rfds, NULL, NULL, NULL);
  }

读取事件

当事件发生时,程序会依照缓存区的大小来读取尽量多的事件,然后把这些事件放入队列等待事件处理程序来处理。样例代码不能处理这种情况 — 可用事件超出 16.384 byte 缓存中可存储的事件。要处理这类情况,需要在缓存末端处理部分事件。目前,对名字长度进行限制不成问题,但是优秀的防御式编程会检查名字,来确保不会溢出缓存。

清单 7. 读取事件并排队
 


int read_events (queue_t q, int fd)
{
  char buffer[16384];
  size_t buffer_i;
  struct inotify_event *pevent;
  queue_entry_t event;
  ssize_t r;
  size_t event_size, q_event_size;
  int count = 0;

  r = read (fd, buffer, 16384);
  if (r <= 0)
    return r;
  buffer_i = 0;
  while (buffer_i < r)
    {
      /* Parse events and queue them. */
      pevent = (struct inotify_event *) &buffer[buffer_i];
      event_size =  offsetof (struct inotify_event, name) + pevent->len;
      q_event_size = offsetof (struct queue_entry, inot_ev.name) + 
                                  pevent->len;
      event = malloc (q_event_size);
      memmove (&(event->inot_ev), pevent, event_size);
      queue_enqueue (event, q);
      buffer_i += event_size;
      count++;
    }
  printf ("\n%d events queued\n", count);
  return count;
}

处理事件

最后,我们需要对事件做处理了。对于该应用程序,我们只简单地报告所发生的事件。如果一个名字出现在事件结构中,我们就报告它是一个文件或目录。发生移动时,还会报告与移动或重命名事件相关的 cookie 信息。清单 8 展示了部分代码,包括对一些事件的处理。参见 下载 部分可获取完整代码。

清单 8. 处理事件
 


void handle_event (queue_entry_t event)
{
  /* If the event was associated with a filename, we will store it here */
  char *cur_event_filename = NULL;
  char *cur_event_file_or_dir = NULL;
  /* This is the watch descriptor the event occurred on */
  int cur_event_wd = event->inot_ev.wd;
  int cur_event_cookie = event->inot_ev.cookie;

  unsigned long flags;

  if (event->inot_ev.len)
    {
      cur_event_filename = event->inot_ev.name;
    }
  if ( event->inot_ev.mask & IN_ISDIR )
    {
      cur_event_file_or_dir = "Dir";
    }
  else 
    {
      cur_event_file_or_dir = "File";
    }
  flags = event->inot_ev.mask & 
    ~(IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED );

  /* Perform event dependent handler routines */
  /* The mask is the magic that tells us what file operation occurred */
  switch (event->inot_ev.mask & 
  (IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED))
    {
      /* File was accessed */
    case IN_ACCESS:
      printf ("ACCESS: %s \"%s\" on WD #%i\n",
      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File was modified */
    case IN_MODIFY:
      printf ("MODIFY: %s \"%s\" on WD #%i\n",
      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File changed attributes */
    case IN_ATTRIB:
      printf ("ATTRIB: %s \"%s\" on WD #%i\n",
      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File open for writing was closed */
    case IN_CLOSE_WRITE:
      printf ("CLOSE_WRITE: %s \"%s\" on WD #%i\n",
      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File open read-only was closed */
    case IN_CLOSE_NOWRITE:
      printf ("CLOSE_NOWRITE: %s \"%s\" on WD #%i\n",
      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File was opened */
    case IN_OPEN:
      printf ("OPEN: %s \"%s\" on WD #%i\n",
      cur_event_file_or_dir, cur_event_filename, cur_event_wd);
      break;

      /* File was moved from X */
    case IN_MOVED_FROM:
      printf ("MOVED_FROM: %s \"%s\" on WD #%i. Cookie=%d\n",
      cur_event_file_or_dir, cur_event_filename, cur_event_wd, 
              cur_event_cookie);
      break;
.
. (other cases)
.
      /* Watch was removed explicitly by inotify_rm_watch or automatically
         because file was deleted, or file system was unmounted.  */
    case IN_IGNORED:
      watched_items--;
      printf ("IGNORED: WD #%d\n", cur_event_wd);
      printf("Watching = %d items\n",watched_items); 
      break;

      /* Some unknown message received */
    default:
      printf ("UNKNOWN EVENT \"%X\" OCCURRED for file \"%s\" on WD #%i\n",
      event->inot_ev.mask, cur_event_filename, cur_event_wd);
      break;
    }
  /* If any flags were set other than IN_ISDIR, report the flags */
  if (flags & (~IN_ISDIR))
    {
      flags = event->inot_ev.mask;
      printf ("Flags=%lX\n", flags);
    }
    }

这个简单示例用于说明 inotify 如何工作,以及您可以监控什么事件。您的实际需求将决定对哪些事件进行监控以及如何处理这些事件。


相关内容