詳解Golang中文件系統(tǒng)事件監(jiān)聽
基本介紹
文件系統(tǒng)事件是指文件系統(tǒng)相關(guān)的各種操作和狀態(tài)變化,當(dāng)一個應(yīng)用層的進(jìn)程操作文件或目錄時,會觸發(fā)system call,內(nèi)核的notification子系統(tǒng)可以守在那里,把該進(jìn)程對文件的操作上報給應(yīng)用層的監(jiān)聽進(jìn)程。這些事件可以包括文件和目錄的創(chuàng)建、修改、刪除和文件權(quán)限的更改等。
Linux中常用的有兩種機制能夠監(jiān)聽這些文件事件,分別為inotify和fanotify。
inotify和fanotify最大的區(qū)別就是fanotify能夠監(jiān)聽到是哪個進(jìn)程對文件或目錄進(jìn)行操作,并且能夠阻止該操作。
fanotify
fanotify:Linux 2.6.37版本引入,能夠通知用戶哪個進(jìn)程觸發(fā)了哪些事件,并且能夠?qū)ζ溥M(jìn)行干預(yù)。
Golang中fanotify有兩個函數(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ù)初始化了一個新的fanotify事件組,并返回與該組關(guān)聯(lián)的事件隊列的文件描述符,文件描述符用來調(diào)用FanotifyMark函數(shù),以指定應(yīng)該為其創(chuàng)建fanotify事件的文件、目錄、掛載或文件系統(tǒng),通過讀取文件描述符來接收這些事件。
flags參數(shù)包含一個多位字段,用于定義監(jiān)聽?wèi)?yīng)用程序的通知類型,可選的值有:
FAN_CLASS_CONTENT = 0x4 適用于需要訪問已經(jīng)包含最終內(nèi)容的文件的事件監(jiān)聽器 FAN_CLASS_NOTIF = 0x0 默認(rèn)值,不需要指定,只用于監(jiān)聽,不訪問文件內(nèi)容 FAN_CLASS_PRE_CONTENT = 0x8 適用于需要在文件包含最終數(shù)據(jù)之前訪問文件的事件監(jiān)聽器*/ FAN_CLOEXEC = 0x1 如果在程序運行時打開了一個文件描述符,并且在調(diào)用時沒有關(guān)閉,那么新程序中仍然能夠使用該文件描述符,設(shè)置這個字段,可以確保調(diào)用時關(guān)閉文件描述符 FAN_NONBLOCK = 0x2 為文件描述符啟用非阻塞標(biāo)志,讀取文件描述符時不會被阻塞 FAN_UNLIMITED_MARKS = 0x20 取消對每個用戶的通知標(biāo)記數(shù)量的限制 FAN_UNLIMITED_QUEUE = 0x10 刪除對事件隊列中事件數(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)的目錄對象的附加信息 FAN_REPORT_FID = 0x200 Linux 5.1后的功能,使用此標(biāo)志初始化的通知組的事件將包含相關(guān)的底層文件系統(tǒng)對象的附加信息 FAN_REPORT_NAME = 0x800 Linux 5.9后的功能,使用此標(biāo)志初始化的通知組的事件將包含與事件相關(guān)的目錄條目名稱的附加信息 FAN_REPORT_PIDFD = 0x80 Linux 5.15后的功能,使用此標(biāo)志初始化的事件將包含一個附加的信息記錄 FAN_REPORT_TARGET_FID = 0x1000 Linux 5.17后的功能,使用此標(biāo)志初始化的通知組的事件將包含與目錄條目修改事件相關(guān)的子節(jié)點的附加信息 FAN_REPORT_TID = 0x100 Linux 4.20后的功能,報告線程ID(TID)而不是進(jìn)程ID(PID) FAN_ENABLE_AUDIT = 0x40 Linux 4.15后的功能,啟用生成權(quán)限事件執(zhí)行的訪問中介的審計日志記錄
event_f_flags參數(shù)定義了文件描述符狀態(tài),可選的值有:
O_RDONLY = 0x0 只讀 O_RDWR = 0x2 讀寫 O_WRONLY = 0x1 只寫 O_LARGEFILE = 0x0 啟用對超過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)對象上添加、刪除或修改fanotify標(biāo)記,調(diào)用者必須對要標(biāo)記的文件系統(tǒng)對象具有讀權(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)對象由文件描述符dirFd和pathname中指定的路徑名決定
- 如果pathname為空,則由dirFd確定
- 如果pathname為空,并且dirFd的值為AT_FDCWD,監(jiān)聽當(dāng)前工作目錄
- 如果pathname是絕對路徑,dirFd被忽略
- 如果pathname是相對路徑,并且dirFd不是AT_FDCWD,監(jiān)聽pathname相對于dirFd目錄的路徑
- 如果pathname是相對路徑,并且dirFd為AT_FDCWD,監(jiān)聽pathname相對于當(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) } } }
示例代碼能夠拒絕程序打開該目錄下文件
相關(guān)文章
golang?基于?mysql?簡單實現(xiàn)分布式讀寫鎖
這篇文章主要介紹了golang?基于mysql簡單實現(xiàn)分布式讀寫鎖,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09