FAM(File Alteration Monitor),提供了另一种监视linux系统变化的机制。fam由两部分构成:famd,用以监听客户请求并在文件系统变化后提供通知的daemon进程;libfam,客户端应用程序与famd交互的接口。
FAM支持的文件系统事件有
- FAMChanged,文件/文件夹被改变
- FAMDeleted,被监视的文件/文件夹被删除。如果开始监控的不存在的路径,也会产生该事件。
- FAMStartExecuting,被监控的可执行文件开始执行。如果多个进程执行该文件,只在第一个进程产生该事件。
- FAMStopExecuting,文件结束执行。如果多个进程执行该文件,只在最后一个结束的进程产生该事件。
- FAMCreated,被监视目录中有文件被创建。被监视目录的子目录改变不会被自动监视。
- FAMMoved,该事件不会发生,定义只是为了向后兼容。
- FAMAcknowledge,在FAMCancelMonitor()调用后famd会产生该事件;或者路径不合法(指采用了相对路径)也会产生该事件。
- FAMExists,如果监控的一个文件,famd为该文件产生FAMExists事件;如果监控的是目录,famd为该目录和直接包含于该目录中的文件或目录产生FAMExists事件。
- FAMEndExist,在最后一个FAMExists事件后,famd会产生FAMEndExist事件。
使用FAM的基本步骤:
1、通过调用FAMOpen创建与famd的连接,这个调用会返回一个FAMConnection结构,用于以后的函数调用。
2、调用FAMMonitorDirectory或者FAMMonitorFile来监视文件或目录
char *filename,
FAMRequest* fr,
void* userData);
int FAMMonitorFile(FAMConnection *fc,
char *filename,
FAMRequest* fr,
void* userData);
这两个函数返回的FAMRequest结构可以用于取消、暂停或继续对文件的监控。
3、有两种方式用于检测到文件/文件夹的变化:(1)对FAMOpen返回的FAMConnection结构的文件描述符(可以通过FAMCONNECTION_GETFD宏获得)执行select调用,在可用时调用FAMNextEvent来取得FAM事件(2)周期性的调用FAMPending,如果有事件会马上返回1,否则马上返回0,在有事件时调用FAMNextEvent来取得事件。
int FAMPending(FAMConnection* fc);
与FAMPending不同,FAMNextEvent在没有事件时会一直阻塞。
FAMEvent的结构为:
FAMConnection* fc;
FAMRequest fr;
char hostname[MAXHOSTNAMELEN];
char filename[NAME_MAX];
void *userdata;
FAMCodes code;
} FAMEvent;
4、调用FAMSuspendMonitor、FAMResumeMonitor、FAMCancelMonitor来暂停、继续和取消对文件/目录的监控。
int FAMResumeMonitor(FAMConnection *fc, FAMRequest *fr);
int FAMCancelMonitor(FAMConnection *fc, FAMRequest *fr);
5、最后在应用程序结束之前调用FAMClose,关闭与famd的连接,释放与被监控文件相关的资源。
有两点需要注意:
- FAM对文件的监控是基于文件名而不是inode的,因此如果重命名或者移动该文件,FAM会报告文件被删除。
- 调用FAMMonitorDirectory或FAMMonitorFile时的文件名应该是绝对路径,如果是相对路径,famd会忽略请求并发送FAMAcknowledge事件。
与inotify比较可见FAM的事件粒度不如inotify细,而且2.6.13以后的linux内核原生的支持inotify。然后FAM的优势在于:
- 使用FAM可以使应用程序脱离内核版本的限制,在更多的平台上工作。
- FAM支持NFS上的文件监控。
最后是一个简单的示例:
#include <fam.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
/* event_name() - return printable name of fam event code */
const char *event_name(int code)
{
static const char *famevent[] = {
"",
"FAMChanged",
"FAMDeleted",
"FAMStartExecuting",
"FAMStopExecuting",
"FAMCreated",
"FAMMoved",
"FAMAcknowledge",
"FAMExists",
"FAMEndExist"
};
static char unknown_event[10];
if (code < FAMChanged || code > FAMEndExist)
{
sprintf(unknown_event, "unknown (%d)", code);
return unknown_event;
}
return famevent[code];
}
void main(int argc, char *argv[])
{
int i, nmon, rc, fam_fd;
FAMConnection fc;
FAMRequest *frp;
struct stat status;
FAMEvent fe;
fd_set readfds;
/* Allocate storage for requests */
frp = malloc(argc * sizeof *frp);
if (!frp)
{
perror("malloc");
exit(1);
}
/* Open fam connection */
if ((FAMOpen(&fc)) < 0)
{
perror("fam");
exit(1);
}
/* Request monitoring for each program argument */
for (nmon = 0, i = 1; i < argc; i++)
{
if (stat(argv[i], &status) < 0)
{
perror(argv[i]);
status.st_mode = 0;
}
if ((status.st_mode & S_IFMT) == S_IFDIR)
rc = FAMMonitorDirectory(&fc, argv[i], frp + i,
NULL);
else
rc = FAMMonitorFile(&fc, argv[i], frp + i, NULL);
if (rc < 0)
{
perror("FAMMonitor failed");
continue;
}
nmon++;
}
if (!nmon)
{
fprintf(stderr, "Nothing monitored.\n");
exit(1);
}
/* Initialize select data structure */
fam_fd = FAMCONNECTION_GETFD(&fc);
FD_ZERO(&readfds);
FD_SET(fam_fd, &readfds);
/* Loop forever. */
while(1)
{
if (select(fam_fd + 1, &readfds,
NULL, NULL, NULL) < 0)
{
perror("select failed");
exit(1);
}
if (FD_ISSET(fam_fd, &readfds))
{
if (FAMNextEvent(&fc, &fe) < 0)
{
perror("FAMNextEvent");
exit(1);
}
printf("%-24s %s\n", fe.filename,
event_name(fe.code));
}
}
}