fanotify 监控文件系统

inotify 的替代

Fanotify 是一个 notifier,即一种对文件系统变化产生通知的机制,是替代 inotify 的下一代文件系统通知机制。本文将探讨 fanotify 的特性和基本的使用,希望能为那些准备理解这个新的文件系统通知机制的读者提供一些参考。

刘 明, 软件工程师, 上海交通大学通信与电子工程系

刘明,老程序员一名,喜欢使用和研究 Linux,目前从事数据仓库开发和应用。



2011 年 12 月 29 日

引子

Fanotify (fscking all notifiction and file access system) 是一个 notifier,即一种对文件系统变化产生通知的机制。

我第一次看到 Fanotify 是在 2009 年,Eric Paris 在 lkml 上努力地向大家说明 fanotify 的特性。但在当时有影响力的内核开发人员都认为这只是一个拿了报酬的程序员为 Anti-Virus 公司所做的一个特别项目,而非一个有价值的内核特性。因此 fanotify 的前景并不光明。因此我也没有过多在意,那个时候,fanotify 的接口非常怪异,使用一种特别的 socket 接口,更让我失去了试一下的勇气。

经过漫长的等待,或许还加上耐心地推广,fanotify 居然被合并进入内核了。也不知从什么时候开始,反对意见都销声匿迹。终于,在 2.6.36 发布的那会儿,fanotify 堂而皇之地被列入了 KernelNewbie 的 changlog 中,并且作为 cool stuff 之一,号称为替代 inotify 的下一代 notifier。

两年间发生了什么?是什么让 fanotify 不再仅仅是 yet another notification interface 了呢? 本文将探讨 fanotify 的特性和基本的使用,希望能对理解这个新的文件系统通知机制提供一些参考。


fanotify 的特性

文件系统事件通知

作为一个 notifier,最基本的功能是当文件系统出现变化时通知相应的监控程序(本文将用 Listener 来指代监控程序)。比如文件 A 被打开时,监控程序就能得到通知,说文件 A 即将被打开,这样 Listener 就可以做一些相应的工作。类似文件夹同步等应用程序都依赖这个机制。在 Linux 的历史上,最早由 dnotify 提供这种服务,后来 inotify 起而代之。

Fanotify 也提供通知功能,表一列出了 fanotify 提供的文件系统变化事件。

表 1.fanotify 事件
Fanotify 定义 含义
FAN_ACCESS File was accessed
FAN_MODIFY File was modified
FAN_CLOSE_WRITE Writtable file closed
FAN_CLOSE_NOWRITE Unwrittable file closed
FAN_OPEN File was opened
FAN_OPEN_PERM File open in perm check
FAN_ACCESS_PERM File accessed in perm check

图 1 展示 fanotify 工作时的一个概况。

图 1. fanotify 事件
图 1. fanotify 事件

当应用程序 A 打开文件时,Listener A 就得到一个 FAN_OPEN 的通知;当 App A 写文件时,Listener A 就得到一个 FAN_MODIFY 的通知;close 也一样。这和 inotify 是一样的。

全文件系统监控

Inotify使用watchdescriptor这个数据结构来对应某个被监控的文件或者目录。每个需要被监控的文件系统对象(文件,目录)都需要一个wd对象来表示。对于下面图 2 中的文件树,您可以看到一个复杂文件目录需要维护多少wd

图 2. 测试
图 2. 测试

如果是需要对整个文件系统进行监控呢(比如杀毒软件)?需要创建多少的 wd 啊!在画上面这个图的时候,红色的箭头线让我非常疲惫。因此人们常说inotify缺乏可扩展性,因为如果再多一些箭头,我将拒绝再画了。

Fanotify 有三个个基本的模式:directed,per-mount 和 global。其中,directed 模式和 inotify 类似,直接工作在被监控的对象的 inode 上,一次只可以监控一个对象。因此需要监控大量目标时也很麻烦。Global 模式则监控整个文件系统,任何变化都会通知 Listener。杀毒软件便工作在这种模式下。Per-mount 模式工作在 mount 点上,比如磁盘 /dev/sda2 的 mount 点在 /home,则 /home 目录下的所有文件系统变化都可以被监控,这其实可以被看作另外一种 Global 模式。

长期以来,人们都希望 Linux 的 notifier 可以支持 sub-tree 通知,比如图 2 的众多监控对象都在 /home 目录下面,假如 notifier 可以指定监控整个 /home 目录,其下任意文件或者子目录的变化都可以引起通知,监控程序便无需为每一个 /home 下面的子目录和文件一一添加 watch descriptor 了。

在很久以前,Fanotify 就暗示说实现 sub-tree notification 不是不可能的,但直到今天 fanotify 依然无法支持 sub-tree 监控。但比 inotify 进了一步的是,fanotify 可以监控某个目录下的直接子节点。比如可以监控 /home 和他的直接子节点,文件 /home/foo1,/home/foo2 等都可以被监控,但 /home/pics/foo1 就不可以了,因为 /home/pics/foo1 不是 /home 的直接子节点。希望在后续的 fanotify 版本中可以弥补这个不足。

面对 sub-tree 监控的需要,目前 fanotify 的折中方案是采用 Global 模式,然后在监控程序内部进行判断,剔除那些不感兴趣的文件系统对象。这虽然不完美,但也算一个可行的方案吧。相比 inotify,有一点儿总比完全没有好一些吧。

访问控制 Access decision

在文件系统通知这个方面,fanotify 做的并不比 inotify 好,甚至还更差一些,因为 inotify 能监控更多类型的变化。我觉得非要说 fanotify 是 inotify 的替代,Access Decision 功能是一个比较有说服力的特性。Inotify 不能提供这个能力。

所谓 access descision 即当文件被访问的时候,监控程序不仅可以获得这个事件通知,还能够决定是否允许该操作。这对于杀毒软件是必要的:当您试图打开一个含有病毒的文件时,fanotify 将产生一个通知给作为 listener 的杀毒软件,这个时候杀毒软件不仅需要判断将被打开的文件是否含有病毒,还需要阻止您的这个不安全的操作。否则杀毒软件在检查到病毒的时候只能说:“哎呀,您中毒了!”因为病毒文件还是被打开了。

图 3. Access decision 流程
图 3. Access decision 流程

上图演示了 fanotify 进行访问控制的流程。当 app 需要打开文件的时候,加入该文件已经被 AV 程序监控,那么 open 这个操作将引起 fanotify 的通知,在 VFS 允许 open 返回之前,fanotify 先询问 AV program,假如允许,则 app 的 open 调用成功,否则 app 的 open 调用将失败。这样就可以阻止应用程序打开带病毒的文件了。

Listener groups

Fanotify 允许多个 Listener 同时监控同一个文件系统对象。比如杀毒软件 V 和桌面搜索软件 S 会同时监控目录 /myDocument。当文件 /mydocument/test 被打开的时候,fanotify 将通知 V 和 S。那么先通知谁呢?您可能觉得无关紧要,但实际上有时候这个很重要。

比如有一类软件叫做 hierarchical storage manager(HSM),在文件系统中实际存放的可能只是一个 stub 文件,文件真正的内容在下一级存储设备中,因此当 stub 文件被打开时,fanotify 应该先通知 HSM,让它先工作,将真正的文件内容导入到 stub 文件中;然后再通知杀毒软件,对真正的文件内容进行扫描;否则就有这样的一种可能:杀毒文件只扫描了 stub,而 HSM 随后将病毒导入。

Fanotify 将所有的 Listener 分成三个 Group,优先级从上到下递减 :

  • FAN_CLASS_PRE_CONTENT
  • FAN_CLASS_CONTENT
  • FAN_CLASS_NOTIF

初始化为 FAN_CLASS_PRE_CONTENT 的 Listener 优先级最高,将最先收到通知,其后是 FAN_CLASS_CONTENT;最后才是 FAN_CLASS_NOTIF 进程得到通知。

从上述宏的命名也大致可知:FAN_CLASS_PRE_CONTENT 用于 HSM 等需要在应用程序使用文件的 CONTENT 之前就得到文件操作权的应用程序;FAN_CLASS_CONTENT 适用于杀毒软件等需要检查文件 CONTENT 的软件;而 FAN_CLASS_NOTIF 则用于纯粹的 notification 软件,不需要访问文件内容的应用程序。

Listener PID

调用 Inotify 进行监控的进程如果对被监控文件进行操作,也将引起通知。有时候这会造成问题:

考察下面这个例子程序:

清单 1. inotify 的 loop 问题
 inotify_add_watch (fd, “/home/lm/loop”
           IN_MODIFY | IN_OPEN | IN_CREATE | IN_DELETE); 
  // 监控文件 /home/lm/loop 

 for (;;) 
 { 
   readInotifyEvent(); 
   if(event->mask & IN_OPEN) 
       check_what_changed(event); // 检查有些什么改动
 }

函数 check_what_changed() 为了检查文件内容是否有变化必须调用 open 打开文件。

清单 2. 检查文件内容是否有变化
 void check_what_changed(event) 
 { 
  fd = open(event->name, O_RDWR); // 又触发 inotify 通知
  read (fd, buf,128) 
  …
 }

这里的 open 操作也会触发 inotify 通知,从而使得代码清单 1 的 for 循环成为一个很难打破的无限循环。

Fanotify 在通知中包含了触发事件的进程的 Pid,因此上面的问题可以轻易解决:

在 check_what_changed 函数中判断引起通知的 pid,如果是监控程序自己,则忽略这个通知,不会再次打开该文件。从而打破无限循环。

Inotify 在事件中不包含 pid,因此监控程序无法知道是哪个进程触发了文件系统事件,这对于某些应用也是不方便的。Fanotify 在通知中包含了 Pid 从而满足了某些应用的需要。

实际上,Fanotify 的通知中包含了被监控文件系统对象的 open fd,应用程序可以直接使用这个 fd 对文件对象进行操作,而不会引起新的通知。这也是 Fanotify 相对于 Inotify 改进的一个地方。

Decision Cache

杀毒软件要扫描每一个即将被访问的文件,这对用户体验的影响很大。使用过 MacFee 等软件的用户一定感同身受。

假如一个文件被频繁使用,且没有修改,那么最好只在第一次访问的时候扫描它,之后便不再需要扫描了。类似一个 cache,扫描过的文件进入这个 cache,下次再访问同一个文件时,假如在 cache 中存在,那就不需要再次扫描文件内容了。

Fanotify 支持这种 cache,也叫做 ignore marks。它的工作原理很简单,假如对一个文件系统对象设置了 ignore marks,那么下次该文件被访问时,相应的事件便不会触发访问控制的代码,从而始终允许该文件的访问。

杀毒软件可以这样使用此特性,当应用程序第一次打开文件 file A 时,Fanotify 将通知杀毒软件 AV 进行文件内容扫描,如果 AV 软件发现该文件没有病毒,在允许本次访问的同时,对该文件设置一个 ignore mark。如下图所示:

图 4. 访问文件
图 4. 访问文件

此后 File A 再次被访问的时候,Fanotify 将发现在 cache 中已经有相应的 Ignore Mark,因此不再通知 AV 软件进行访问控制而直接允许该文件的访问请求。

图 5. 直接允许该文件的访问请求
图 5. 直接允许该文件的访问请求

当文件内容被修改时,Fanotify 将自动清除 Ignore mark。Ignore Mark 的数量缺省情况下有一定限制,但用户可以通过修改 init flag 设置无限的 mark 数目。将在后续 API 讲解中详细说明。


Fanotify 的缺点

Fanotify 目前支持的文件系统事件类型比 inotify 少很多。

表 2. inotify 和 fanotify 所支持的文件系统事件对比
文件系统事件 Inotify Fanotify
ACCESS Y Y
MODIFY Y Y
ATTRIB Y
CLOSE_WRITE Y Y
CLOSE_NOWRITE Y Y
OPEN Y Y
MOVED_FROM Y
MOVED_TO Y
CREATE Y
DELETE Y
DELETE_SELF Y
MOVE_SELF Y
UNMOUNT Y
OPEN_PERM Y
CCESS_PERM Y

从上表可以看出,相比 inotify,fanotify 所支持的文件系统事件少很多,尤其是 fanotify 不支持 move,这使得 fanotify 无法应用于类似桌面搜索或者实时远程文件系统同步等应用。当文件从一个目录移动到另一个目录,或者被改名时,fanotify 不产生任何通知。这使得一些使用 inotify 的应用因此无法迁移到 fanotify 上面来。

此外和 inotify 一样,目前 fanotify 无法做到 sub-tree 监控。虽然 Eric 很久之前就声称支持 sub-tree 监控没有技术障碍,但直到目前我们依然没有看到 fanotify 可以支持 sub-tree 监控。

但 fanotify 毕竟还很年轻,有些缺点也是可以理解的吧。。。假如前面说的那些特性令您有了一些兴趣,那么下面我们就来看看如何使用 fanotify 进行编程吧。


Fanotify 基本编程

接口函数

Fanotify 向应用程序提供了两个系统调用:

fanotify_init 和 fanotify_mark,这比之前的 socket 要容易理解很多。

为了在应用程序中使用这两个系统调用,必须自己定义它们作为新的系统调用,因为 fanotify 刚出现不久,目前 glibc 还不支持它。

代码如下:

清单 3,声明系统调用的代码
 #if defined(__x86_64__) 
 # define __NR_fanotify_init 	 300 
 # define __NR_fanotify_mark 	 301 
 #elif defined(__i386__) 
 # define __NR_fanotify_init 	 338 
 # define __NR_fanotify_mark 	 339 
 #else 
 # error "System call numbers not defined for this architecture"
 #endif 

 static inline int fanotify_init(unsigned int flags, 
           unsigned int event_f_flags) 
 { 
	 return syscall(__NR_fanotify_init, flags, event_f_flags); 
 } 

 static inline int fanotify_mark(int fanotify_fd, 
                 unsigned int flags, __u64 mask, 
				 int dfd, const char *pathname) 
 { 
	 return syscall(__NR_fanotify_mark, fanotify_fd, 
                      flags, mask, 
		       dfd, pathname); 
 } 
 #endif

演示 fanotify 的例子程序

Fanotify 的作者 Eric Paris 写了一个 例子程序来演示 fanotify 的使,Eric 写的很好,我写不出更好的了,所以就直接拿来主义吧。在此讲解讲解 Eric 例子程序的细节。

要运行 Eric Paris 的例子程序,您需要 2.6.36 以上的内核。在写这篇文章的时候,我所使用的内核版本为 2.6.39。

为了支持 fanotify,需要选中以下两个内核编译选项:

 FANOTIFY  -- “Filesystem wide access notification”
 FANOTIFY_ACCESS_PERMISSIONS -- "fanotify permissions checking"

准备好了内核,就着手看例子代码吧。该例子程序演示 fanotify 的基本功能,我将它编译成可执行文件 av,运行时效果如下:

 [lm@localhost inotify]$ ./av 
 USAGE: ./av [-cdfhmnp] [-o 
 {open,close,access,modify,open_perm,access_perm}] file ... 
 -c: learn about events on children of a directory (not decendants) 
 -d: send events which happen to directories 
 -f: set premptive ignores (go faster) 
 -h: this help screen 
 -m: place mark on the whole mount point, not just the inode 
 -n: do not ignore repeated permission checks 
 -p: check permissions, not just notification 
 -s N: sleep N seconds before replying to perm events

首先 av 提供 notify 功能,用户可以通过 -o 选项选择需要被通知的事件,比如 open,close 等等,可以不指定而采用默认的通知事件。比如在窗口 1 中运行:

 ./av /home/lm/f1

程序将监控所有对文件 /home/lm/f1 的操作。此时打开新的窗口 2

 cd /hom/lm 
 echo “test” >f1

对文件进行写操作,此时可以在窗口 1 看到如下输出:

 /home/lm/f1: pid=2079 open modify close(writable)

-c 选项表示监控当前目录以及子目录的变化,不过需要注意这里只能监控直接子目录,比如

 ./av -c /home/lm

可以监控所有 /home/lm 目录的变化事件,也可以监控 /home/lm/d1 的变化,但不能监控 /home/lm/d1/yetanother 的变化。

-p 表示需要进行访问控制 (Access Decission),目前 Eric 的例子程序默认允许所有的访问,因此假如您运行这个例子程序,表面上看不出和 notify 模式有任何的区别。后面我将在讲解代码之后对访问控制进行小小修改,您便可以看到如何进行访问控制了。

其他的参数不是很重要,限于篇幅,不再赘述。让我们开始解读一些重要的代码片段从而帮助大家理解 fanotify 吧。

初始化 fanotify

首先需要初始化 fanotify:

 fan_fd = fanotify_init(init_flags, O_RDONLY | O_LARGEFILE);

其中 init_flags 根据用户参数的不同而有不同的设置。

清单 4. Init_flags 设置
 if (fan_mask & FAN_ALL_PERM_EVENTS) 
  init_flags |= FAN_CLASS_CONTENT; 
 else 
  init_flags |= FAN_CLASS_NOTIF;

假如用户指定 -p 选项,即需要 Access Decision, 则将 init_flag 设置为 FAN_CLASS_CONTENT,否则设置为 FAN_CLASS_NOTIF。这个 flag 的含义在 Listener Group 一节中已经讲解过。

除了分组标志之外,可以选择的 init_flag 还有如下一些:

FAN_CLOEXEC

- 设置 close-on-exec 标志,即执行 exec 后,fanotify 的 fd 将被关闭而不能被子进程使用

FAN_NONBLOCK

- 设置 fanotify 的 fd 为非阻塞模式,在其上 read 不会 block,即使没有数据也会立即返回。

FAN_UNLIMITED_QUEUE

- 将 queue depth 设置为无限。即设置无限多的监控对象,使用这个选项必须小心,因为会导致内存用光引发 OOM

FAN_UNLIMITED_MARKS

- 允许应用设置无限多的 Marks,主要是 ignore marks。比如 AV 软件,使用 ignore mark 作为已经扫描过的文件的缓存。因此可能需要很多 marks。同样,使用这个选项需要注意内存问题。

设置监控事件 mask

初始化好 Fanotify 之后,就可以告诉 Fanotify 我们想监控哪些文件对象,以及监控哪些事件。这是通过 fanotify_mark 系统调用来实现的。查看例子程序,发现有如下代码:

清单 5. 监控文件系统对象
 for (; optind < argc; optind++) 
	 if (mark_object(fan_fd, argv[optind], AT_FDCWD, fan_mask, mark_flags) != 0) 
		 goto fail;

av 可以监控多个文件系统对象,只要将这些对象作为命令行参数输入即可,以上循环就是对参数列表所有最后的所有文件系统对象调用 mark_object 函数。其实现如下:

清单 6. 调用 mark_object 函数
 int mark_object(int fan_fd, const char *path, int fd, uint64_t mask, unsigned int flags) 
 { 
	 return fanotify_mark(fan_fd, flags, mask, fd, path); 
 }

调用 fanotify_mark,对指定文件对象设置 mask。这里有两个重要参数:mask 和 flags。

Mask 表示事件,比如 FAN_ACCESS,详情见表 3.

Flags 有如下这些可能的取值,代表需要进行的操作。

表 3. Flags 可嫩的取值
Flag 标志 flag 含义
FAN_MARK_ADD 添加一个 MASK
FAN_MARK_REMOVE 删除一个 Mask
FAN_MARK_DONT_FOLLOW same meaning as O_NOFOLLOW as described in open(2)
FAN_MARK_ONLYDIR same meaning as O_DIRECTORY as described in open(2)
FAN_MARK_MOUNT 工作在 per-mount 模式下,Fanotify 将监控整个 mount 点。
FAN_MARK_IGNORED_MASK 设置一个 ignore mask
FAN_MARK_IGNORED_SURV_MODIFY 当 ignore mask 所对应的 inode 修改时,不清空该 ignore mask。
FAN_MARK_FLUSH 清除所有 Mark

主循环,等待事件

基本的等待事件循环如下所示:

清单 7. 等待事件循环
  FD_ZERO(&rfds); 
  FD_SET(fan_fd, &rfds); 
  select(fan_fd+1, &rfds, NULL, NULL, NULL);
  while ((len = read(fan_fd, buf, sizeof(buf))) > 0) { 
    。。。
    while(FAN_EVENT_OK(metadata, len)) { 
    // 处理 metadata 
    . . . 
    metadata = FAN_EVENT_NEXT(metadata, len); // 读取下一个 metadata 
    } 
  select(fan_fd+1, &rfds, NULL, NULL, NULL) 
  }

Fanotify_init 返回的 file descriptor 可以适用于所有文件系统调用。因此可以方便地使用 read 来等待时间;同样,也可以调用 select 同时等待多个 fd 上的事件。因此基本的事件等待循环如代码清单 7 所示。

处理 metadata

每一个事件都由一个 metadata 数据结构所表示。

应用程序通过调用 fanotify_init 和 fanotify_mark 设置好需要监控的对象和事件之后,便可以不断地调用 read 轮询是否有文件系统事件发生了。每一个文件系统事件,都由下面这个数据结构表示。

清单 8. 数据结构
 struct fanotify_event_metadata { 
	 __u32 event_len; 
	 __u8 vers; 
	 __u8 reserved; 
	 __u16 metadata_len; 
	 __aligned_u64 mask; 
	 __s32 fd; 
	 __s32 pid; 
 };

这个数据结构类似于 inotify 中的 inotify_event,其中我们最常使用的数据成员是:

  • fd -- 一个 open fd,代表触发事件的文件系统对象
  • pid -- 触发文件系统对象的进程 id
  • mask -- 事件 mask,通过判断 mask,我们可以获知发生了什么事件,比如 OPEN,ACCESS 等。

不同的事件处理

通过分析 metadata,我们可以确切地获知当前发生的事件类型。因此一般地,我们可以用一个分支语句结构对不同的事件进行不同的处理,代码如下:

清单 9. 分支语句结构
 if (metadata->mask & FAN_ACCESS) 
	 printf(" access"); 
 if (metadata->mask & FAN_OPEN) 
	 printf(" open"); 
 if (metadata->mask & FAN_MODIFY) 
	 printf(" modify"); 
 if (metadata->mask & FAN_CLOSE) { 
	 if (metadata->mask & FAN_CLOSE_WRITE) 
		 printf(" close(writable)"); 
	 if (metadata->mask & FAN_CLOSE_NOWRITE) 
		 printf(" close"); 
 } 
 if (metadata->mask & FAN_OPEN_PERM) 
	 printf(" open_perm"); 
 if (metadata->mask & FAN_ACCESS_PERM) 
	 printf(" access_perm"); 
 if (metadata->mask & FAN_ALL_PERM_EVENTS) { 
	 if (opt_sleep) 
		 sleep(opt_sleep); 
		 if (handle_perm(fan_fd, metadata)) 
		 goto fail; 
	 if (metadata->fd >= 0 && 
	    opt_ignore_perm && 
	    set_ignored_mask(fan_fd, metadata->fd, 
			     metadata->mask)) 
		 goto fail; 
 }

判断 mask 域,如果是 OPEN,则打印 open。看懂了些 if 语句之后,您便可以执行不同于打印的其他您想要的操作了。

Access Descision

相比于 inotify,fanotify 还可以进行 Access Decision,当发生 OPEN_PERM/ACCESS_PERM 事件时,监控进程可以通过向内核 fanotify file descriptor 回写一个数据来允许或者拒绝文件操作。该数据为:

 struct fanotify_response { 
	 __s32 fd; 
	 __u32 response; 
 };

其中,fd 代表将被打开的文件系统对象的 fd;

response 代表是否允许操作的决定,可以选择的值为:

  • FAN_ALLOW,即允许
  • FAN_DENY,即拒绝

进行访问控制决策的函数例子代码如下:

清单 10. 访问控制决策的函数
 int handle_perm(int fan_fd, struct fanotify_event_metadata *metadata) 
 { 
	 struct fanotify_response response_struct; 
	 int ret; 

	 response_struct.fd = metadata->fd; 
	 response_struct.response = FAN_ALLOW; 

	 ret = write(fan_fd, &response_struct, sizeof(response_struct)); 
	 if (ret < 0) 
		 return ret; 

	 return 0; 
 }

在 Eric 的例子中,总是允许该文件操作。因此 handle_perm 函数中没有做任何特殊处理,只是回复一个 FAN_ALLOW。下面我将修改这里的逻辑,让我们更清晰地理解访问控制是如何具体工作的吧。


修改例子程序

修改 handle_perm,让它 总是不允许文件操作。

清单 11. 不允许文件操作
 int handle_perm(int fan_fd, struct fanotify_event_metadata *metadata) 
 { 
	 struct fanotify_response response_struct; 
	 int ret; 

	 response_struct.fd = metadata->fd; 
	 response_struct.response = FAN_DENY;// 唯一的修改

	 ret = write(fan_fd, &response_struct, sizeof(response_struct)); 
	 if (ret < 0) 
		 return ret; 

	 return 0; 
 }

编译运行:

窗口 1

 ./av -p /home/lm/f1

窗口 2

 [lm@localhost ~]$ cat f1 
 cat: f1: Operation not permitted

您也可以在 handle_perm 中加入其他的逻辑,比如打开文件 metadata->fd,读取其中的内容,根据文件内容决定是否允许该操作。这正是 AV 软件所做的。


一个模拟 HSM 的 fanotify 应用程序

再将 Eric 的例子程序稍加修改,便可以写一个模拟 HSM 的例子程序。

HSM(Hierarchical Storage Management) 是一种经济而且地利用存储设备的存储管理方式。HSM 对用户是透明的,也就是说用户并不知道这种管理方式的存在。

不同的存储设备有不同的容量和价格,容量大的价格便宜的往往速度慢。HSM 将访问速度快的设备作为缓存,将大量文件保存在速度低,价格便宜的存储介质上。当系统需要在快速设备上访问某文件时,将文件真正的内容从其他存储设备上调出,此后再访问该文件就直接在快速设备上读取。但快速设备容量小,因此长期不读的文件将被再次放回到慢速设备上。类似 cache。但此过程对用户透明,用户只感觉到自己在访问本地文件。

在清单 9 的第四行之后加入如下几行代码:

清单 12. 代码
 read(metadata->fd,buf1,sizeof(buf1)); 
 if(strncmp(buf1,"stub",4)==0) 
 { 
    write(metadata->fd,"abcd",4); 
 }

这段代码没有特别需要解释的,如果文件内容是 stub,就把它替换为真正的文件内容。真的 HSM 文件会从其他的存储介质上将真实文件内容读出,再替换现有文件。作为简单例子,这里就简单写入几个单词吧。执行效果如下:

 [lm@localhost ~]$ echo “stub” >hsmt

运行 HSM

 [lm@localhost ~]$ ./hsm ~/hsmt 

在另外的窗口执行 cat 
 [lm@localhost ~]$ cat ~/hsmt 
 abcd

结束语

Fanotify 已经进入内核,而且已经历了 2,3 年的开发,但似乎还是很初级。

在 /usr/include/linux 下面您依然可以看到 inotify.h。

几乎所有的现有相关应用仍然继续使用 inotify,一些项目甚至宣称他们无法迁移到 fanotify。

这都是现实。

不过我却依然看好 fanotify,因为对新事物要充满耐心。缺点总可以慢慢弥补,而创新是最重要的。相比之前的 notifier,fanotify 提供了访问控制,除了 AV,HSM 之外,这个创新特性还会有那些应用场景?回答这个问题恐怕只需要两点:想象力和缓慢流失的时间。

参考资料

学习

讨论

  • 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=783398
ArticleTitle=fanotify 监控文件系统
publish-date=12292011