今天简单讨论下Linux下文件系统的监控方法,我测试过的有如下三种方法,分布针对不同的kernel 版本:
1 , 使用inotify API (如果你的Linux kernel >= 2.6.13 ,恭喜你了, 可以直接调用这个新的API )
2 , 使用inotify device ( 2.4.27<= kernel <= 2.6.13 , 这个方法本质上和调用inotify API 是一样的,但是麻烦些, 要给kernel打patch )
3 , 使用文件轮询方式 ( 这个不适用特别的API, 适用所有kernel版本 )
方法1 : inotify API 的使用
这个方法我实在不想在这里多说了, 网上一搜一堆得资料 。 这个方法应该是目前监控Linux 文件系统变化最佳的途径, 毕竟像文件监控这种任务能得到操作系统提供的API的支持是最好不过的了,coding 很容易, 也节省系统资源,不需要像方法3 轮询那样时刻或者定期的检查文件。
man page: http://linux.die.net/man/7/inotify
方法2 : inotify device 的使用
inotfiy API是在2.6.13 的版本之后加入kernel的, 一些国外编写linux kernel的牛人制作的一个backported的patch, 使得部分早于2.6.13 的linux系统也可以使用类似于调用inotify API方法进行文件监控。
以下两篇文章不错:
http://www.developertutorials.com/tutorials/linux/monitor-linux-inotify-050531-1133/
http://lwn.net/Articles/142751/
看完上面两篇文章之后就可以对大概如何使用inotify device 的方式监控文件有个大致的了解了,如果你觉得还想继续深究,下面这个链接提供了一些现成的源码
http://www.kernel.org/pub/linux/kernel/people/rml/inotify/utils/
其中 inotify-utils-0.21.tar.gz 值得一看, 拿来修改后可以直接使用。
方法3 :文件轮询的方法
这几天我在网上搜索了很多linux下文件监控的方法,绝大部分是推荐使用inotify API的文章,另外也有提及使用signal的方法,尚未找到使用文件轮询的方式进行文件监控的文章,这里我会较为详细的介绍下这个方法。(此方法的最大优点是没有使用任何特别的API, 适用于绝大多数Linux版本)
一 设计思路
通过文件对比的方法检测文件的Add,Delete, Modify
二 具体步骤
2.1 文件夹拷贝: 先把需要监控的文件夹完全复制一份,最为文件对比时的参考文件夹
2.2 文件"反向"检测:
使用readdir() 依次读取参考文件夹中的每个文件: 如果读取到的时文件夹,则递归调用此函数;
如果读取到的是文件,首先检测监控文件夹中对应的文件是否存在,如果不存在了,则认为此文件在监控文件夹中被删除,触发一个Delete 的事件,如果文件存在,继续检测 last write time 是否一致,如果last write time变化了, 说明此文件被修改;
所谓"反向" 的意思是说通过依次打开参考文件夹中的所有文件,来检测监控文件夹中对应的文件是否还存在或者被修改,显然,使用"反向”检测的方式只能检测文件的Delete 和 Modify,不能检测文件的Add。检测文件的Add 要通过"正向"检测的方法。
2.2 文件"反向"检测: 使用readdir() 依次读取监控文件夹中的每个文件:如果某个文件在对应的参考文件夹中不存在,则说明此文件是新添加的。
三 注意事项
3.1 开始监控前一定要先copy 监控文件夹作为对比用的参考文件夹
3.2 不能拿参考文件夹中文件的last write time 直接和监控文件的last write time 直接对比,因为参考文件夹中的last write time 是文件夹初始被拷贝创建的时间。所有要通过某种方式在监控开始前把监控文件夹中所有文件的last write time 提前记录下来,供监控对比时使用。我使用map来存储监控文件夹中所有文件的last write time。
3.3 注意递归的使用,要遍历文件夹中所有子文件夹的内容,一定要使用递归。
3.4 参考文件夹要和监控文件夹保持实时同步,比如在某轮监控中检测到一个新添加的文件,则同时要把此文件copy到参考文件夹,或者文件被修改,则要update map中对应的last write time。 避免下一轮检测中报告同样的事件。
四 code
代码为3个源文件, scm.h scm_main.cpp scm.cpp 。
编译执行后可以实现对某个文件夹内文件Add, Delete, Modify事件的监控 。
注意:程序执行前先copy要监控的文件夹。 把监控文件夹和参考文件夹的path作为可执行程序的参数传入。另外还需要建立一个backup的空文件夹,程序运行时会吧删除的或者被修改的original file 存放在backup的文件夹内
头文件 scm.h
#ifndef _SCM_
#define _SCM_
#include <string>
#include <map>
#define BACKUP_DIR "/home/joshua/backup"
using namespace std;
extern char dir_mon[255];
extern char dir_bak[255];
extern map<string, time_t> modify_time;
void mon_routine ();
int dir_lookup (char *, char *, int);
void file_lookup (char *, char *, char *, int);
int mtime_init (char *);
void sys_time ();
void sys_hostname ();
#endif
主文件 scm_main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <string>
#include <dirent.h>
#include <unistd.h>
#include <map>
#include "scm.h"
using namespace std;
char dir_mon[255]={0}; // directory to monitor
char dir_bak[255]={0}; // directory as reference
map<string, time_t> modify_time;
int main (int argc, char **argv)
{
// pass arguments as directory path
memcpy(dir_mon, argv[1], strlen(argv[1]));
memcpy(dir_bak, argv[2], strlen(argv[2]));
// initialize modify_time
if (mtime_init(dir_mon) == -1)
{
printf("mtime_init fail");
return -1;
}
printf(" Monitoring begin !\n");
while (1)
{
// monitor routine every 5 sec
mon_routine ();
sleep (5);
}
return 0;
}
函数 scm.cpp
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <string>
#include <errno.h>
#include <map>
#include "scm.h"
using namespace std;
void mon_routine ()
{
// lookup from backup_dir to monitor_dir, detect file delete or modify, flag=0
dir_lookup (dir_mon, dir_bak, 0);
// lookup from monitor_dir to backup_dir, detect file add, flag =1
dir_lookup (dir_bak, dir_mon, 1);
return ;
}
int dir_lookup (char *dir, char *dir_open, int flag)
{
DIR *odir;
struct dirent* dent;
int len;
// Open directory
if ((odir = opendir(dir_open)) == NULL)
{
printf ("Dir %s ", dir_open);
perror ("Couldn't be open:");
return -1;
}
// Lookup files in this directory one-by-one
while ((dent = readdir(odir)) != NULL)
{
// special file "." ".."
if ( strcmp(dent->d_name, ".") == 0 ||
strcmp(dent->d_name, "..") == 0 )
{
continue;
}
len = strlen(dent->d_name);
// regular file need to be checked
if ( dent->d_type == DT_REG &&
dent->d_name[0] != '.' && // get rid of hiden file
!(dent->d_name[len-1] == 'p' && // get rid of temporary file
dent->d_name[len-2] == 'm' &&
dent->d_name[len-3] == 't' &&
dent->d_name[len-4] == '.')&&
dent->d_name[len-1] != '~' // get rid of temporary file
)
{
char pathname_o[255]={0};
char pathname[255]={0};
sprintf(pathname_o, "%s/%s", dir_open, dent->d_name);
sprintf(pathname, "%s/%s", dir, dent->d_name);
// check out if this file is deleted , modified or added , controlled by flag bit
file_lookup (pathname, pathname_o, dent->d_name, flag);
}
// check out directory recursively
if (dent->d_type == DT_DIR )
{
char subdir_open[255]={0};
char subdir[255]={0};
sprintf(subdir_open, "%s/%s", dir_open, dent->d_name);
sprintf(subdir, "%s/%s", dir, dent->d_name);
// check out if this dir exist, if not , create it
struct stat statbuf_m;
if (stat(subdir, &statbuf_m) == -1)
{
// dir does not exist, which means a dir is created
char cmd[255]={0};
sprintf(cmd, "mkdir %s", subdir);
if(system(cmd) == -1)
{
perror("CMD Fail:");
}
}
// directory lookup recursively, controlled by flag bit
dir_lookup (subdir, subdir_open, flag);
}
}
closedir(odir);
return 0;
}
void file_lookup (char *file, char *file_o, char *file_name, int flag)
{
struct stat statbuf_m;
// file doesn not exist anymore
if (stat(file, &statbuf_m) == -1 && flag == 0)
{
sys_time ();
sys_hostname ();
printf("%s was deleted!\n", file);
// remove deleted file to backup dir
char newpath[255]={0};
sprintf(newpath, "%s/%s", BACKUP_DIR, file_name);
if(rename(file_o, newpath) == -1)
{
perror("file backup fail:");
}
// delete the filepath in map
modify_time.erase (file);
return ;
}
// file was MODIFIED
if (statbuf_m.st_mtime != modify_time[file] && flag == 0)
{
sys_time ();
sys_hostname ();
printf("%s was modified!\n", file);
// move original file to backup dir
char newpath[255]={0};
sprintf(newpath, "%s/%s", BACKUP_DIR, file_name);
if(rename(file_o, newpath) == -1)
{
perror("file backup fail:");
}
// update modify_time map
modify_time[file] = statbuf_m.st_mtime;
// copy modified file to sync dir
char cmd[255]={0};
sprintf(cmd, "cp \"%s\" \"%s\"", file, file_o);
if (system(cmd) == -1)
{
perror(" Cmd fail\n");
}
}
// a new file was added, this is implemented by directory check backwardly, controlled by flag bit
if (stat(file, &statbuf_m) == -1 && flag == 1)
{
sys_time ();
sys_hostname ();
printf("%s was added!\n", file_o);
// copy new file to sync_dir
char cmd[255]={0};
sprintf(cmd, "cp \"%s\" \"%s\"", file_o, file);
if (system(cmd) == -1)
{
perror("CMD fail:\n");
}
// update new file mtime to map
stat (file_o, &statbuf_m);
modify_time[file_o] = statbuf_m.st_mtime;
}
}
int mtime_init (char *dir_m)
{
DIR *odir;
struct dirent* dent;
// Open this backup directory
if ((odir = opendir(dir_m)) == NULL)
{
printf ("Dir %s ", dir_m);
perror ("Couldn't be open:");
return -1;
}
// Lookup files in this directory one-by-one
while ((dent = readdir(odir)) != NULL)
{
// special file "." ".."
if ( strcmp(dent->d_name, ".") == 0 ||
strcmp(dent->d_name, "..") == 0 )
{
continue;
}
// record regular file last_modify_time
if ( dent->d_type == DT_REG )
{
struct stat statbuf;
char pathname_m[255]={0};
sprintf(pathname_m, "%s/%s", dir_m, dent->d_name);
stat (pathname_m, &statbuf);
modify_time[pathname_m]=statbuf.st_mtime;
}
// check out directory recursively
if (dent->d_type == DT_DIR )
{
char subdir_m[255]={0};
sprintf(subdir_m, "%s/%s", dir_m, dent->d_name);
if (mtime_init (subdir_m) == -1)
{
return -1;
}
}
}
closedir(odir);
return 0;
}
void sys_time ()
{
time_t time_now;
struct tm *time_local;
char tm[30]={0};
time(&time_now);
time_local = localtime (&time_now);
sprintf(tm, "%d/%d/%d %d:%d:%d", time_local->tm_mon+1,
time_local->tm_mday,
time_local->tm_year+1900,
time_local->tm_hour,
time_local->tm_min,
time_local->tm_sec);
printf ("%s", tm);
}
void sys_hostname ()
{
char hostname[50]={0};
gethostname (hostname, 50);
printf (" %s ",hostname);
}