fanotify 監控檔案系統

idaretobe發表於2015-01-14

引子

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 之外,這個創新特性還會有那些應用場景?回答這個問題恐怕只需要兩點:想象力和緩慢流失的時間。

相關文章