轉載:https://www.cnblogs.com/PikapBai/p/14480881.html
作者:PikapBai
1 執行環境#
- 作業系統:Ubuntu 18
2 inotify 簡介#
-
inotify 是一個 Linux 核心特性(監視檔案系統事件),它用於監控檔案系統,比如刪除、讀、寫操作等,當發生對應事件時,則會觸發 inotify。當監控目錄時,與該目錄自身以及該目錄下面的檔案都會被監控,其上有事件發生時都會通知給應用程式
-
inotify 監控機制為非遞迴,若想監控整個目錄子樹內的事件,則需對該樹中的每個目錄發起 inotify_add_watch() 呼叫
-
使用 inotify:建立一個檔案描述符,附加一個或多個監視器(一個監視器 是一個路徑和一組事件),然後使用 read() 方法從描述符獲取事件資訊。read() 並不會用光整個週期,它在事件發生之前是被阻塞的。
-
因為 inotify 透過傳統的檔案描述符工作,可使用 select(),poll(),epoll() 以及由訊號驅動的 I/O 來監控 inotify 檔案描述符
-
要使用 inotify,必須具備一臺帶有 2.6.13 或更新核心的 Linux 機器(以前的 Linux 核心版本使用更低階的檔案監控器 dnotify)。如果您不知道核心的版本,請轉到 shell,輸入 uname -a
3 inotify API#
3.1 inotify_init#
建立一個 inotify 例項並返回一個引用 inotify 例項的檔案描述符
函式原型:
#include<sys/inotify.h>
int inotify_init(void);
返回值:
-
成功:該函式的返回值為一個檔案描述符,該檔案描述符所指代的檔案中將會儲存所監控的 檔案/目錄 所發生的 事件集。
-
失敗:返回 -1,並且將 errno 設定為對應錯誤。
使用及解釋:
int fd = inotify_init();
fd 為所指的 inotify 例項的 監控列表,系統呼叫 inotify_add_watch() 可以向該 fd 追加 新的監控項。
3.2 inotify_add_watch#
針對 fd 所指的 inotify 例項的 監控列表 追加 新的監控項。
函式原型:
#include<sys/inotify.h>
int inotify_add_watch(int fd,const char *pathname,uint32_t mask);
返回值:
-
成功:返回值為一個用於 唯一指代此 監控項 的描述符
-
失敗:返回值 < 0 ,則代表新增該監控項失敗,需要檢測 pathname 是否有可讀許可權,是否存在,系統的監控佇列是否已滿等
引數:
-
pathname 為想要建立的監控項所對應的檔案,特別注意呼叫該介面必須要對該檔案有讀許可權,該函式只對檔案做一次檢查,如果在監控時修改了所監控的檔案讀許可權,則不會影響繼續監控此檔案
-
mask 為一位掩碼,針對 pathname 定義了想要監控的事件,此函式的返回值為一個用於唯一指代此監控項的描述符(將在 4 inotify 事件 中介紹)
4 inotify 常用監控事件#
-
IN_ACCESS:檔案 被訪問時 觸發事件,例如 read,execve
-
IN_ATTRIB:檔案屬性 發生變化 觸發事件。例如 許可權 chmod,時間戳 setxattr,連結數 link 等
-
IN_CLOSE_WRITE:一個檔案被開啟 寫入操作結束,檔案被關閉時 觸發事件
-
IN_CLOSE_NOWRITE:一個檔案被開啟 沒有任何寫操作,檔案被關閉時 觸發事件
-
IN_CREATE:在監控列表下 建立一個檔案或目錄 時 觸發事件,例如 open O_CREAT,mkdir 等
-
IN_DELETE:在監控列表下 檔案或目錄 被刪除時 觸發事件
-
IN_DELETE_SELF:監控檔案或目錄 本身被刪除時 觸發事件,而且,如果一個檔案或目錄被移到其它地方,比如使用 mv 命令,也會觸發該事件,因為 mv 命令本質上是複製一份當前檔案,然後刪除當前檔案的操作。此時監控終止,並且將收到一個 IN_IGNORED 事件。
-
IN_MODIFY:檔案 被修改時 觸發事件,例如:有寫操作(write)或者檔案內容被清空(truncate)操作。不過需要注意的是,IN_MODIFY 可能會連續觸發多次。
-
IN_MODIFY_SELF:所監控的檔案或目錄本身 發生移動時 觸發事件
-
IN_MOVED_FROM:將檔案或目錄 移除 監控列表 觸發事件
-
IN_MOVED_TO:將檔案或目錄 移入 監控列表 觸發事件
-
IN_OPEN:檔案被開啟 觸發事件
-
IN_ALL_EVENTS:監控所有事件
-
IN_MOVE:IN_MOVED_FROM | IN_MOVED_TO 事件的統稱
5 儲存 inotify 事件 結構體 struct inotify_event#
將 監控項 在 監控列表 中登記後,應用程式可以用 read() 從 inotify 的檔案描述符 中讀取事件以判定發生了那些事件。若讀取之時還沒有發生任何事件,則 read() 會阻塞,直至有事件產生。事件發生後,每次呼叫 read() 會返回一個快取區,內含一個或多個如下型別的結構體:
struct inotify_event
{
int wd; // 指向發生事件的監控項的檔案描述符,該欄位值由之前對 inotify_add_watch() 的呼叫返回。用於區分是哪個監控項觸發了該事件
uint32_t mask; // inotify 事件的一位掩碼
uint32_t cookie; // 唯一的關聯 inotify 事件的值
uint32_t len; // 分配給 name 的位元組數
char name[]; // 標識觸發該事件的檔名
};
注意:
如果是監控目錄,此時目錄下的檔案觸發事件,會輸出對應的檔名。但是如果只監控檔案,則無法根據 event->name 輸出對應更改的檔名,原因參考 7.1 監控檔案時,無法根據 event->name 輸出對應更改的檔名
6 inotify 示例#
6.1 程式碼#
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/inotify.h>
#define EVENT_NUM 12
const char *event_str[EVENT_NUM] =
{
"IN_ACCESS",
"IN_MODIFY",
"IN_ATTRIB",
"IN_CLOSE_WRITE",
"IN_CLOSE_NOWRITE",
"IN_OPEN",
"IN_MOVED_FROM",
"IN_MOVED_TO",
"IN_CREATE",
"IN_DELETE",
"IN_DELETE_SELF",
"IN_MOVE_SELF"
};
int inotifyTask(char *argv[])
{
int errTimes = 0;
int fd = -1;
INIT_INOTIFY:
fd = inotify_init();
if(fd < 0)
{
fprintf(stderr, "inotify_init failed\n");
printf("Error no.%d: %s\n", errno, strerror(errno));
goto INOTIFY_FAIL;
}
int wd1 = -1;
int wd2 = -1;
struct inotify_event *event;
int length;
int nread;
char buf[BUFSIZ];
int i = 0;
buf[sizeof(buf) - 1] = 0;
INOTIFY_AGAIN:
wd1 = inotify_add_watch(fd, argv[1], IN_ALL_EVENTS);
if(wd1 < 0)
{
fprintf(stderr, "inotify_add_watch %s failed\n", argv[1]);
printf("Error no.%d: %s\n", errno, strerror(errno));
if(errTimes < 3)
{
goto INOTIFY_AGAIN;
}
else
{
goto INOTIFY_FAIL;
}
}
wd2 = inotify_add_watch(fd, argv[2], IN_ALL_EVENTS);
if(wd2 < 0)
{
fprintf(stderr, "inotify_add_watch %s failed\n", argv[2]);
printf("Error no.%d: %s\n", errno, strerror(errno));
if(errTimes < 3)
{
goto INOTIFY_AGAIN;
}
else
{
goto INOTIFY_FAIL;
}
}
length = read(fd, buf, sizeof(buf) - 1);
nread = 0;
// inotify 事件發生時
while(length > 0)
{
printf("\n");
event = (struct inotify_event *)&buf[nread];
// 遍歷所有事件
for(i = 0; i< EVENT_NUM; i++)
{
// 判斷事件是否發生
if( (event->mask >> i) & 1 )
{
// 該監控項為目錄或目錄下的檔案時
if(event->len > 0)
{
fprintf(stdout, "%s --- %s\n", event->name, event_str[i]);
}
// 該監控項為檔案時
else if(event->len == 0)
{
if(event->wd == wd1)
{
fprintf(stdout, "%s --- %s\n", argv[1], event_str[i]);
}
if(event->wd == wd2)
{
fprintf(stdout, "%s --- %s\n", argv[2], event_str[i]);
}
}
}
}
nread = nread + sizeof(struct inotify_event) + event->len;
length = length - sizeof(struct inotify_event) - event->len;
}
goto INOTIFY_AGAIN;
close(fd);
return 0;
INOTIFY_FAIL:
return -1;
}
int main(int argc, char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s path path\n", argv[0]);
return -1;
}
if(inotifyTask(argv) == -1)
{
return -1;
}
return 0;
}
6.2 編譯#
編譯命令:
gcc inotify.c -o out
如下圖所示:
6.3 執行截圖#
6.3.1 不加任何引數#
此時會提示資訊,需要輸入兩個路徑用於監控,如下圖所示:
6.3.2 監控兩個檔案#
監控 /etc/a,/etc/b
如下圖所示:
6.3.3 監控兩個目錄#
監控 /etc,/tmp
如下圖所示:
7 inotify 監控檔案時常見問題#
7.1 監控檔案時,無法根據 event->name 輸出對應更改的檔名#
原因:
在 linux 手冊中關於 inotify 的描述有對應解釋。 如果是監控目錄,此時目錄下的檔案觸發事件,會輸出對應的檔名。但是如果只監控檔案,則無法輸出對應更改的檔名。如下圖所示:
7.2 監控檔案時,無法持續監控,第二次更改檔案時,它沒有響應#
原因:
這是由於 vim 的工作機制引起的,vim 會先將原始檔複製為另一個檔案,然後在另一檔案基礎上編輯(字尾名為 swp),儲存的時候再將這個檔案覆蓋原始檔。此時原來的檔案已經被後來的新檔案代替,因此監視物件所監視的檔案已經不存在了,所以自然不會產生任何事件。
解決方法:
重新使用 inotify_add_watch,將該檔案加入監控佇列。