API
1. inotify_init()
#include <sys/inotify.h>
int inotify_init(void);
//Returns file desctiptor on success, or -1 on error.
inotify_init()
会返回一个文件描述符,用于在后续操作中指代inotify实例。
2. inotify_add_watch()
#include <sys/inotify.h>
int inotify_add_watch(int fd, const char* pathname, uint32_t mask);
// Returns watch descriptor on success, or -1 on error.
针对描述符fd所指代inotify实例的监控列表,inotify_add_watch()
既可以追加新的监控项,也可以修改现有监控项。
- pathname
- 标识要创建或修改的监控项所对应的文件。
- 调用程序必须对该文件具有读权限。
- 调用
inotify_add_watch()
时,会对文件权限做一次性检测。只要监控项继续存在,即便文件权限被更改,调用程序依然可以收到通知。
- mask
- 掩码,针对pathname定义了要监控的事件。
如果先前未将pathname加入fd的监控列表,则inotify_add_watch()
会在列表中创建一个新的监控项,并返回一个新的、非负的监控描述符,用来在后续操作中指代此监控项。对inotify实例来说,该监控描述符是唯一的。
如果先前已将pathname加入fd的监控列表,则会修改现有pathname监控项的掩码,并返回其监控描述符。(此描述符就是最初将pathname加入该监控列表的系统调用inotify_add_watch()
所返回的监控描述符。)
3. inotify_rm_watch()
inotify_rm_watch()
会从文件描述符fd所指代的 inotify实例中,删除由wd所定义的监控项。
#include <sys/inotify.h>
int inotify_rm_watch(int fd, uint32_t wd);
// Returns 0 on success, or -1 on error.
- wd
- 监控描述符,由之前的
inotify_add_watch()
调用返回。
- 监控描述符,由之前的
删除监控项会为该描述符生成IN_IGNORED
事件。稍后将讨论该事件。
inotify事件
使用inotify_add_watch()
删除或修改监控项时,位掩码mask标示了pathname要监控的事件。
mask对应的事件位如下:
- [ 待办 ]
读取inotify事件
将监控项在监控列表中登记后,应用程序可用read()
从inotify文件描述符中读取事件,以判断发生了哪些事件。
如果没有事件发生,read()
会一直阻塞,直到有事件产生(除非对该描述符设置O_NOBLOCK
)。
事件的结构体如下:
struct inotify_event{
int wd; // Watch descriptor on which event occured
uint32_t mask; // Bits describing event that occured
uint32_t cookie; // Cookie for related events (for rename())
uint32_t len; // Size of 'name' field
char name[]; // Optional null-terminated filename
};
-
wd
- 指明发生事件1是哪个监控描述符。
- 该字段由之前对
inotify_add_watch()
的调用返回。 - 当应用程序要监控同一inotify文件描述符下的多个文件和目录时,字段wd就派上用场。应用利用其所提供的线索来判定发生事件的特定文件或目录。(要做到这一点,应用程序必须维护专有数据结构,记录监控描述符与路径名之间的关系。)
-
mask
- 该字段会返回描述该事件的位掩码
注意以下几点:- 移除监控项时,会产生
IN_IGNORED
事件。起因可能由两个:其一,应用程序使用了inotify_rm_watch()
系统调用显式移除监控项。其二,因监控对象被删除或其所驻留的文件系统遭卸载,致使内核隐式删除监控项。以IN_ONESHOT
而建立的监控项因事件触发而自动移除时,不会产生IN_IGNORED
事件。 - 如果事件的主体为路径,那么除去其他位以外,在mask中还会设置
IN_ISDIR
位。 IN_UNMOUNT
事件会通知应用程序包含受监控对象的文件系统已遭卸载。该事件发生后,还会产生包含IN_IGNORED
置位的附加事件。- 后续章节将介绍
IN_Q_OVERFLOW
,并讨论对排队inotify事件的限制。
- 移除监控项时,会产生
- 该字段会返回描述该事件的位掩码
-
cookie
- 该字段可以将相关事件联系在一起。目前,只有在对文件重命名时才会用到该字段。当这种情况发生时,系统会针对待重命名文件所在目录产生
IN_MOVED_FROM
事件,然后,还会针对重命名后文件的所在目录生成IN_MOVED_TO
事件。(若前后目录相同,则会针对同一目录产生上述两个事件。) - 两个事件的cookie字段值相等,故而应用程序得以将它们关联起来。
- 该字段可以将相关事件联系在一起。目前,只有在对文件重命名时才会用到该字段。当这种情况发生时,系统会针对待重命名文件所在目录产生
-
name
- 当受监控目录中有文件发生事件时,name字段返回一个字符串,标识该文件,以空字符结尾。
- 若受监控对象自身有事件发生,则不使用name字段,将len字段置为0。
-
len
- 标示实际分配给name字段的字节数。
- 存储于name内的字符串结尾与下一个inotify_event结构的开始之间,可能会有额外的填充字节,孤儿len字段不可或缺。
- 单个inotify_event事件的长度是
sizeof(struct inotify_event) + len
。
如果传递给read()
的缓冲区过小,无法容纳一个inotify_event结构,那么read()
调用将以失败告终,错误号为EINVAL。
只要保证缓冲区足够容纳至少一个事件,这个问题就可以完全规避:传给read()
的缓冲区应至少为sizeof(struct inotify_event) + NAME_MAX + 1
,其中NAME_MAX
是文件名的最大长度,此外再加上终止空字符用的字节。
针对文件描述符fd调用
ioctl(fd, FIONREAD, &numbytes)
,会返回其所指代的inotify实例中的当前可读字节数。
从inotify文件描述符中读取的事件形成了一个有序队列。打个比方,对文件重命名时,便可保证在IN_MOVED_TO
事件之前能读取到IN_MOVED_FROM
事件。
在事件队列的末尾追加一个新事件时,如果此新事件与队列当前尾部事件拥有相同的wd、mask、cookie和mask值,那么内核会将两者合并(以避免对新事件排队)。之所以这么做,是因为很多应用程序都并不关注同一时间的反复出现,而其丢弃多余的事件能降低内核维护事件队列所需的内存总量。然而,这也意味着使用inotify将无法可靠判定出周期性事件的发生次数或频率。
例子
检测文件创建和删除的。注意,inotify无法检测/proc
、/sys
等伪文件。
#include <sys/inotify.h>
#include <err.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
typedef struct inotify_event inotify_event;
int main (void)
{
const char dir[] = "/home/sixqaq";
int fd_notify = inotify_init();
if (inotify_add_watch (fd_notify, dir, IN_CREATE | IN_DELETE) == -1)
err (-1, "inotify_add_watch() %d", __LINE__);
char buf[sizeof (inotify_event) + NAME_MAX + 1];
for (; ;) {
ssize_t nread = read (fd_notify, buf, sizeof (buf));
inotify_event *event = (inotify_event *) buf;
if (event->mask & IN_CREATE)
printf ("new file <%s> is created in <%s>\n", event->name, dir);
else if (event->mask & IN_DELETE)
printf ("file <%s> is removed\n", event->name);
}
}