Linux C 使用 inotify 監控文件或目錄變化


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,將該文件加入監控隊列。

8 參考資料

1、linux 手冊中關於 inotify 的描述 - https://man7.org/linux/man-pages/man7/inotify.7.html

2、Stack Overflow 中關於 inotify inotify_event event->name is empty Shay 的提問 - Will Chappell 的回答- https://stackoverflow.com/questions/7957132/inotify-inotify-event-event-name-is-empty

3、inotify 檢測文件被修改 - https://www.169it.com/tech-qa-linux/article-13284431940448660571.html

4、如何在C中使用inotify - http://www.voidcn.com/article/p-ntlqecbe-bwk.html

5、c使用inotify監控linux路徑下文件變化 - meccaendless(一江明澈的水)- https://blog.csdn.net/meccaendless/article/details/80238997

6、如何用c語言實現對目錄或是文件進行文件的添加,修改,刪除監控(inotify) - jenie - https://blog.csdn.net/jenie/article/details/106195668?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-3&spm=1001.2101.3001.4242


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM