詳解Golang中文件系統(tǒng)事件監(jiān)聽
基本介紹
文件系統(tǒng)事件是指文件系統(tǒng)相關(guān)的各種操作和狀態(tài)變化,當(dāng)一個(gè)應(yīng)用層的進(jìn)程操作文件或目錄時(shí),會(huì)觸發(fā)system call,內(nèi)核的notification子系統(tǒng)可以守在那里,把該進(jìn)程對(duì)文件的操作上報(bào)給應(yīng)用層的監(jiān)聽進(jìn)程。這些事件可以包括文件和目錄的創(chuàng)建、修改、刪除和文件權(quán)限的更改等。
Linux中常用的有兩種機(jī)制能夠監(jiān)聽這些文件事件,分別為inotify和fanotify。
inotify和fanotify最大的區(qū)別就是fanotify能夠監(jiān)聽到是哪個(gè)進(jìn)程對(duì)文件或目錄進(jìn)行操作,并且能夠阻止該操作。
fanotify
fanotify:Linux 2.6.37版本引入,能夠通知用戶哪個(gè)進(jìn)程觸發(fā)了哪些事件,并且能夠?qū)ζ溥M(jìn)行干預(yù)。
Golang中fanotify有兩個(gè)函數(shù):
func FanotifyInit(flags uint, event_f_flags uint) (fd int, err error) func FanotifyMark(fd int, flags uint, mask uint64, dirFd int, pathname string) (err error)
函數(shù)介紹
func FanotifyInit(flags uint, event_f_flags uint) (fd int, err error)
該函數(shù)初始化了一個(gè)新的fanotify事件組,并返回與該組關(guān)聯(lián)的事件隊(duì)列的文件描述符,文件描述符用來(lái)調(diào)用FanotifyMark函數(shù),以指定應(yīng)該為其創(chuàng)建fanotify事件的文件、目錄、掛載或文件系統(tǒng),通過(guò)讀取文件描述符來(lái)接收這些事件。
flags參數(shù)包含一個(gè)多位字段,用于定義監(jiān)聽?wèi)?yīng)用程序的通知類型,可選的值有:
FAN_CLASS_CONTENT = 0x4 適用于需要訪問(wèn)已經(jīng)包含最終內(nèi)容的文件的事件監(jiān)聽器 FAN_CLASS_NOTIF = 0x0 默認(rèn)值,不需要指定,只用于監(jiān)聽,不訪問(wèn)文件內(nèi)容 FAN_CLASS_PRE_CONTENT = 0x8 適用于需要在文件包含最終數(shù)據(jù)之前訪問(wèn)文件的事件監(jiān)聽器*/ FAN_CLOEXEC = 0x1 如果在程序運(yùn)行時(shí)打開了一個(gè)文件描述符,并且在調(diào)用時(shí)沒有關(guān)閉,那么新程序中仍然能夠使用該文件描述符,設(shè)置這個(gè)字段,可以確保調(diào)用時(shí)關(guān)閉文件描述符 FAN_NONBLOCK = 0x2 為文件描述符啟用非阻塞標(biāo)志,讀取文件描述符時(shí)不會(huì)被阻塞 FAN_UNLIMITED_MARKS = 0x20 取消對(duì)每個(gè)用戶的通知標(biāo)記數(shù)量的限制 FAN_UNLIMITED_QUEUE = 0x10 刪除對(duì)事件隊(duì)列中事件數(shù)量的限制 FAN_REPORT_DFID_NAME = 0xc00 這是(FAN_REPORT_DIR_FID|FAN_REPORT_NAME)的同義詞 FAN_REPORT_DFID_NAME_TARGET = 0x1e00 這是(FAN_REPORT_DFID_NAME|FAN_REPORT_FID|FAN_REPORT_TARGET_FID)的同義詞 FAN_REPORT_DIR_FID = 0x400 Linux 5.9后的功能,使用此標(biāo)志初始化的通知組的事件將包含與事件相關(guān)的目錄對(duì)象的附加信息 FAN_REPORT_FID = 0x200 Linux 5.1后的功能,使用此標(biāo)志初始化的通知組的事件將包含相關(guān)的底層文件系統(tǒng)對(duì)象的附加信息 FAN_REPORT_NAME = 0x800 Linux 5.9后的功能,使用此標(biāo)志初始化的通知組的事件將包含與事件相關(guān)的目錄條目名稱的附加信息 FAN_REPORT_PIDFD = 0x80 Linux 5.15后的功能,使用此標(biāo)志初始化的事件將包含一個(gè)附加的信息記錄 FAN_REPORT_TARGET_FID = 0x1000 Linux 5.17后的功能,使用此標(biāo)志初始化的通知組的事件將包含與目錄條目修改事件相關(guān)的子節(jié)點(diǎn)的附加信息 FAN_REPORT_TID = 0x100 Linux 4.20后的功能,報(bào)告線程ID(TID)而不是進(jìn)程ID(PID) FAN_ENABLE_AUDIT = 0x40 Linux 4.15后的功能,啟用生成權(quán)限事件執(zhí)行的訪問(wèn)中介的審計(jì)日志記錄
event_f_flags參數(shù)定義了文件描述符狀態(tài),可選的值有:
O_RDONLY = 0x0 只讀 O_RDWR = 0x2 讀寫 O_WRONLY = 0x1 只寫 O_LARGEFILE = 0x0 啟用對(duì)超過(guò)2gb的文件的支持。在32位系統(tǒng)上, O_CLOEXEC = 0x80000 Linux 3.18后的功能為文件描述符啟用close-on-exec標(biāo)志 這些也是可以的O_APPEND,O_DSYNC,O_NOATIME,O_NONBLOCK,O_SYNC
func FanotifyMark(fd int, flags uint, mask uint64, dirFd int, pathname string) (err error)
該函數(shù)在文件系統(tǒng)對(duì)象上添加、刪除或修改fanotify標(biāo)記,調(diào)用者必須對(duì)要標(biāo)記的文件系統(tǒng)對(duì)象具有讀權(quán)限。
fd參數(shù)是由FanotifyInit函數(shù)返回的文件描述符。
flags參數(shù)是描述要執(zhí)行的操作,可選的值有:
FAN_MARK_ADD = 0x1 FAN_MARK_DONT_FOLLOW = 0x4 FAN_MARK_EVICTABLE = 0x200 Linux 5.19后的功能 FAN_MARK_FILESYSTEM = 0x100 Linux 4.20后的功能 FAN_MARK_FLUSH = 0x80 FAN_MARK_IGNORE = 0x400 Linux 6.0后的功能 FAN_MARK_IGNORED_MASK = 0x20 FAN_MARK_IGNORED_SURV_MODIFY = 0x40 FAN_MARK_IGNORE_SURV = 0x440 FAN_MARK_INODE = 0x0 FAN_MARK_MOUNT = 0x10 FAN_MARK_ONLYDIR = 0x8 FAN_MARK_REMOVE = 0x2
mask參數(shù)定義了應(yīng)該監(jiān)聽哪些事件或者忽略哪些事件,可選的值有:
FAN_ACCESS = 0x1 FAN_ACCESS_PERM = 0x20000 FAN_MODIFY = 0x2 FAN_CLOSE = 0x18 FAN_CLOSE_NOWRITE = 0x10 FAN_CLOSE_WRITE = 0x8 FAN_OPEN = 0x20 FAN_OPEN_EXEC = 0x1000 Linux 5.0后的功能 FAN_OPEN_EXEC_PERM = 0x40000 Linux 5.0后的功能 FAN_OPEN_PERM = 0x10000 FAN_ATTRIB = 0x4 Linux 5.1后的功能 FAN_CREATE = 0x100 Linux 5.1后的功能 FAN_DELETE = 0x200 Linux 5.1后的功能 FAN_DELETE_SELF = 0x400 Linux 5.1后的功能 FAN_FS_ERROR = 0x8000 Linux 5.16后的功能 FAN_MOVE = 0xc0 FAN_MOVED_FROM = 0x40 Linux 5.1后的功能 FAN_MOVED_TO = 0x80 Linux 5.1后的功能 FAN_MOVE_SELF = 0x800 Linux 5.1后的功能 FAN_RENAME = 0x10000000 Linux 5.17后的功能 FAN_ONDIR = 0x40000000 FAN_EVENT_ON_CHILD = 0x8000000
要標(biāo)記的文件系統(tǒng)對(duì)象由文件描述符dirFd和pathname中指定的路徑名決定
- 如果pathname為空,則由dirFd確定
- 如果pathname為空,并且dirFd的值為AT_FDCWD,監(jiān)聽當(dāng)前工作目錄
- 如果pathname是絕對(duì)路徑,dirFd被忽略
- 如果pathname是相對(duì)路徑,并且dirFd不是AT_FDCWD,監(jiān)聽pathname相對(duì)于dirFd目錄的路徑
- 如果pathname是相對(duì)路徑,并且dirFd為AT_FDCWD,監(jiān)聽pathname相對(duì)于當(dāng)前目錄的路徑
示例
package main
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"unsafe"
"golang.org/x/sys/unix"
)
func handle_perm(initFd int, fanfd int32) error {
fd := unix.FanotifyResponse{
Fd: fanfd,
Response: uint32(unix.FAN_DENY),
}
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, fd)
if err != nil {
log.Println(err)
}
ret, err := unix.Write(initFd, buf.Bytes())
if err != nil {
log.Println("handle_perm:err", err)
}
if ret < 0 {
return err
}
return nil
}
func main() {
path := "/root/testapp2/"
name := filepath.Clean(path)
initFd, err := unix.FanotifyInit(unix.FAN_CLOEXEC|unix.FAN_NONBLOCK|unix.FAN_CLASS_PRE_CONTENT, unix.O_RDONLY)
if err != nil {
log.Panicln("FanotifyInit err : ", err)
}
inotifyFile := os.NewFile(uintptr(initFd), "")
if initFd == -1 {
log.Println("fanFd err", err)
}
defer unix.Close(initFd)
mask := uint64(unix.FAN_EVENT_ON_CHILD | unix.FAN_OPEN_PERM)
err = unix.FanotifyMark(initFd, unix.FAN_MARK_ADD, mask, unix.AT_FDCWD, name)
if err != nil {
log.Panicln("FanotifyMark err : ", err)
}
fmt.Println("start:")
fmt.Println("監(jiān)控目錄:", name)
var (
buf [unix.FAN_EVENT_METADATA_LEN * 4096]byte
)
for {
n, err := inotifyFile.Read(buf[:])
if err != nil {
continue
}
if n < unix.FAN_EVENT_METADATA_LEN {
if n == 0 {
err = io.EOF
} else if n < 0 {
err = errors.New("notify: short ")
} else {
err = errors.New("notify: short read in readEvents()")
}
continue
}
var offset int
for offset <= int(n-unix.FAN_EVENT_METADATA_LEN) {
var (
raw = (*unix.FanotifyEventMetadata)(unsafe.Pointer(&buf[offset]))
pid = int32(raw.Pid)
event_len = uint32(raw.Event_len)
fd = int32(raw.Fd)
)
fdPath := fmt.Sprintf("/proc/self/fd/%d", fd)
f, err := os.Readlink(fdPath)
if err != nil {
log.Println(err)
} else {
fmt.Println("fdpath:", f)
}
proName := fmt.Sprintf("/proc/%d/comm", pid)
pN, err := os.ReadFile(proName)
if err != nil {
log.Println(err)
continue
}
if err := handle_perm(initFd, fd); err != nil {
continue
}
fmt.Printf("阻止程序: %v", string(pN))
offset += int(unix.FAN_EVENT_METADATA_LEN + event_len)
}
}
}示例代碼能夠拒絕程序打開該目錄下文件

posted @ 2024-01-18 13:49 科目三什么時(shí)候過(guò) 閱讀(17) 評(píng)論(0) 編輯 收藏 舉報(bào)
相關(guān)文章
Golang中List的實(shí)現(xiàn)方法示例詳解
最近決定復(fù)習(xí)下Go,所以下面這篇文章主要給大家介紹了關(guān)于Golang中List的實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
Go語(yǔ)言bufio庫(kù)的全面指南與實(shí)戰(zhàn)技巧詳解
這篇文章主要為大家全面介紹一下?bufio?庫(kù)的核心組件與功能,包括?Reader、Writer?和?Scanner?等并深入探討它們?cè)趯?shí)際編程中的運(yùn)用場(chǎng)景和技巧,感興趣的可以了解下2024-01-01
Golang信號(hào)量設(shè)計(jì)實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了Golang信號(hào)量設(shè)計(jì)實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
golang?基于?mysql?簡(jiǎn)單實(shí)現(xiàn)分布式讀寫鎖
這篇文章主要介紹了golang?基于mysql簡(jiǎn)單實(shí)現(xiàn)分布式讀寫鎖,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
goland 設(shè)置注釋模板的過(guò)程圖文詳解
這篇文章主要介紹了goland 設(shè)置注釋模板的過(guò)程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-12-12

