fs.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. package main
  2. // 只读文件系统(docs/design/21-knowledge-sources.md §4.2):
  3. // - 以 . 开头的文件和目录不出现在列表中,按路径访问也返回"不存在";
  4. // - 文件一律显示为所有人只读,目录为所有人可进入;
  5. // - 每次访问都先打开源目录并核对设备号、inode 号,再用 openat2(RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS|RESOLVE_NO_XDEV) 打开,
  6. // 访问过程中目录被替换成符号链接也不会读到源目录以外的内容,也不会跨到源目录下挂载的其他文件系统;
  7. // - 资料库不可用时根目录照常存在,读取返回"输入/输出错误"。
  8. import (
  9. "context"
  10. "strings"
  11. "sync"
  12. "syscall"
  13. "time"
  14. "github.com/hanwen/go-fuse/v2/fs"
  15. "github.com/hanwen/go-fuse/v2/fuse"
  16. "golang.org/x/sys/unix"
  17. )
  18. // source 一个挂载对应的源目录:真实路径及其设备号、inode 号;设备拔出、恢复时由 manager 更新。
  19. // 不长期持有源目录的句柄,否则设备会一直"忙",无法卸载(安全弹出、自动挂载的空闲卸载都会失败)。
  20. type source struct {
  21. mu sync.RWMutex
  22. path string // 为空表示不可用
  23. dev uint64
  24. ino uint64
  25. gen uint64
  26. // onMiss 不可用时有人访问,通知 manager 重新检查(异步、限速)
  27. onMiss func()
  28. }
  29. func newSource() *source { return &source{} }
  30. // set 更新源目录(path 为空表示不可用),返回是否有变化
  31. func (s *source) set(path string, dev, ino uint64) bool {
  32. s.mu.Lock()
  33. defer s.mu.Unlock()
  34. if s.path == path && s.dev == dev && s.ino == ino {
  35. return false
  36. }
  37. s.path, s.dev, s.ino = path, dev, ino
  38. s.gen++
  39. return true
  40. }
  41. func (s *source) close() { s.set("", 0, 0) }
  42. func (s *source) miss() syscall.Errno {
  43. if s.onMiss != nil {
  44. go s.onMiss()
  45. }
  46. return syscall.EIO
  47. }
  48. // openRoot 打开源目录并确认还是登记时的那个目录(设备拔出后,原路径可能露出挂载点下面的其他文件)
  49. func openRoot(path string, dev, ino uint64) (int, bool) {
  50. how := unix.OpenHow{Flags: unix.O_PATH | unix.O_DIRECTORY | unix.O_CLOEXEC, Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_MAGICLINKS}
  51. fd, err := unix.Openat2(unix.AT_FDCWD, path, &how)
  52. if err != nil {
  53. return -1, false
  54. }
  55. var st unix.Stat_t
  56. if unix.Fstat(fd, &st) != nil || st.Dev != dev || st.Ino != ino {
  57. unix.Close(fd)
  58. return -1, false
  59. }
  60. return fd, true
  61. }
  62. // openat 相对源目录打开 rel("." 表示源目录本身)
  63. func (s *source) openat(rel string, flags int) (int, syscall.Errno) {
  64. s.mu.RLock()
  65. path, dev, ino := s.path, s.dev, s.ino
  66. s.mu.RUnlock()
  67. if path == "" {
  68. return -1, s.miss()
  69. }
  70. root, ok := openRoot(path, dev, ino)
  71. if !ok {
  72. return -1, s.miss()
  73. }
  74. defer unix.Close(root)
  75. how := unix.OpenHow{Flags: uint64(flags | unix.O_CLOEXEC | unix.O_NOFOLLOW), Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_MAGICLINKS | unix.RESOLVE_NO_XDEV}
  76. for {
  77. fd, err := unix.Openat2(root, rel, &how)
  78. if err == unix.EINTR || err == unix.EAGAIN {
  79. continue
  80. }
  81. if err != nil {
  82. errno := err.(syscall.Errno)
  83. // 路径中出现符号链接、越界或跨到其他文件系统:对调用方表现为不存在
  84. if errno == unix.ELOOP || errno == unix.EXDEV {
  85. return -1, syscall.ENOENT
  86. }
  87. return -1, errno
  88. }
  89. return fd, 0
  90. }
  91. }
  92. func (s *source) generation() uint64 {
  93. s.mu.RLock()
  94. defer s.mu.RUnlock()
  95. return s.gen
  96. }
  97. type kbNode struct {
  98. fs.Inode
  99. src *source
  100. }
  101. var (
  102. _ fs.NodeLookuper = (*kbNode)(nil)
  103. _ fs.NodeGetattrer = (*kbNode)(nil)
  104. _ fs.NodeOpener = (*kbNode)(nil)
  105. _ fs.NodeReaddirer = (*kbNode)(nil)
  106. _ fs.NodeReadlinker = (*kbNode)(nil)
  107. _ fs.NodeStatfser = (*kbNode)(nil)
  108. )
  109. func (n *kbNode) rel() string {
  110. p := n.Path(n.Root())
  111. if p == "" {
  112. return "."
  113. }
  114. return p
  115. }
  116. func (n *kbNode) child(name string) string {
  117. if r := n.rel(); r != "." {
  118. return r + "/" + name
  119. }
  120. return name
  121. }
  122. func hidden(name string) bool { return strings.HasPrefix(name, ".") }
  123. // statRel 不跟随符号链接地获取属性
  124. func (n *kbNode) statRel(rel string) (unix.Stat_t, syscall.Errno) {
  125. var st unix.Stat_t
  126. fd, errno := n.src.openat(rel, unix.O_PATH)
  127. if errno != 0 {
  128. return st, errno
  129. }
  130. defer unix.Close(fd)
  131. if err := unix.Fstat(fd, &st); err != nil {
  132. return st, err.(syscall.Errno)
  133. }
  134. return st, 0
  135. }
  136. func permFor(mode uint32) uint32 {
  137. switch mode & unix.S_IFMT {
  138. case unix.S_IFDIR:
  139. return 0o555
  140. case unix.S_IFLNK:
  141. return 0o777
  142. default:
  143. return 0o444
  144. }
  145. }
  146. // inoFor 设备号参与计算,避免源目录下挂了其他文件系统时 inode 号冲突
  147. func inoFor(st *unix.Stat_t) uint64 {
  148. return st.Ino ^ (uint64(unix.Major(st.Dev))<<56 | uint64(unix.Minor(st.Dev)&0xffff)<<40)
  149. }
  150. func fillAttr(st *unix.Stat_t, out *fuse.Attr) {
  151. out.Ino = inoFor(st)
  152. out.Size = uint64(st.Size)
  153. out.Blocks = uint64(st.Blocks)
  154. out.Blksize = uint32(st.Blksize)
  155. out.Atime, out.Atimensec = uint64(st.Atim.Sec), uint32(st.Atim.Nsec)
  156. out.Mtime, out.Mtimensec = uint64(st.Mtim.Sec), uint32(st.Mtim.Nsec)
  157. out.Ctime, out.Ctimensec = uint64(st.Ctim.Sec), uint32(st.Ctim.Nsec)
  158. out.Nlink = uint32(st.Nlink)
  159. out.Mode = st.Mode&unix.S_IFMT | permFor(st.Mode)
  160. out.Uid, out.Gid = 0, 0
  161. }
  162. func (n *kbNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
  163. if hidden(name) {
  164. return nil, syscall.ENOENT
  165. }
  166. st, errno := n.statRel(n.child(name))
  167. if errno != 0 {
  168. return nil, errno
  169. }
  170. fillAttr(&st, &out.Attr)
  171. ch := n.NewInode(ctx, &kbNode{src: n.src}, fs.StableAttr{Mode: st.Mode & unix.S_IFMT, Ino: inoFor(&st), Gen: n.src.generation()})
  172. return ch, 0
  173. }
  174. func (n *kbNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
  175. if f, ok := fh.(*kbFile); ok {
  176. return f.Getattr(ctx, out)
  177. }
  178. st, errno := n.statRel(n.rel())
  179. if errno != 0 {
  180. if n.IsRoot() {
  181. // 不可用时根目录照常存在
  182. now := uint64(time.Now().Unix())
  183. out.Mode = unix.S_IFDIR | 0o555
  184. out.Nlink = 2
  185. out.Atime, out.Mtime, out.Ctime = now, now, now
  186. return 0
  187. }
  188. return errno
  189. }
  190. fillAttr(&st, &out.Attr)
  191. return 0
  192. }
  193. func (n *kbNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
  194. if flags&(unix.O_WRONLY|unix.O_RDWR|unix.O_TRUNC|unix.O_APPEND|unix.O_CREAT) != 0 {
  195. return nil, 0, syscall.EROFS
  196. }
  197. fd, errno := n.src.openat(n.rel(), unix.O_RDONLY)
  198. if errno != 0 {
  199. return nil, 0, errno
  200. }
  201. return &kbFile{fd: fd}, 0, 0
  202. }
  203. func (n *kbNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
  204. fd, errno := n.src.openat(n.rel(), unix.O_RDONLY|unix.O_DIRECTORY)
  205. if errno != 0 {
  206. return nil, errno
  207. }
  208. defer unix.Close(fd)
  209. var dirSt unix.Stat_t
  210. _ = unix.Fstat(fd, &dirSt)
  211. var list []fuse.DirEntry
  212. buf := make([]byte, 32*1024)
  213. for {
  214. nread, err := unix.Getdents(fd, buf)
  215. if err == unix.EINTR {
  216. continue
  217. }
  218. if err != nil {
  219. return nil, err.(syscall.Errno)
  220. }
  221. if nread <= 0 {
  222. break
  223. }
  224. for off := 0; off < nread; {
  225. reclen, ino, typ, name := parseDirent(buf[off:nread])
  226. if reclen == 0 {
  227. break
  228. }
  229. off += reclen
  230. if name == "" || hidden(name) {
  231. continue
  232. }
  233. st := unix.Stat_t{Ino: ino, Dev: dirSt.Dev}
  234. list = append(list, fuse.DirEntry{Name: name, Mode: direntMode(typ), Ino: inoFor(&st)})
  235. }
  236. }
  237. return fs.NewListDirStream(list), 0
  238. }
  239. func (n *kbNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
  240. fd, errno := n.src.openat(n.rel(), unix.O_PATH)
  241. if errno != 0 {
  242. return nil, errno
  243. }
  244. defer unix.Close(fd)
  245. buf := make([]byte, 4096)
  246. k, err := unix.Readlinkat(fd, "", buf)
  247. if err != nil {
  248. return nil, err.(syscall.Errno)
  249. }
  250. return buf[:k], 0
  251. }
  252. func (n *kbNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
  253. fd, errno := n.src.openat(".", unix.O_PATH)
  254. if errno != 0 {
  255. return 0
  256. }
  257. defer unix.Close(fd)
  258. var s unix.Statfs_t
  259. if unix.Fstatfs(fd, &s) == nil {
  260. out.Blocks, out.Bfree, out.Bavail = s.Blocks, 0, 0
  261. out.Files, out.Ffree = s.Files, 0
  262. out.Bsize, out.Frsize = uint32(s.Bsize), uint32(s.Frsize)
  263. out.NameLen = uint32(s.Namelen)
  264. }
  265. return 0
  266. }
  267. // kbFile 只读文件句柄
  268. type kbFile struct {
  269. mu sync.Mutex
  270. fd int
  271. }
  272. var (
  273. _ fs.FileReader = (*kbFile)(nil)
  274. _ fs.FileReleaser = (*kbFile)(nil)
  275. _ fs.FileGetattrer = (*kbFile)(nil)
  276. _ fs.FileLseeker = (*kbFile)(nil)
  277. )
  278. func (f *kbFile) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
  279. f.mu.Lock()
  280. defer f.mu.Unlock()
  281. return fuse.ReadResultFd(uintptr(f.fd), off, len(dest)), 0
  282. }
  283. func (f *kbFile) Release(ctx context.Context) syscall.Errno {
  284. f.mu.Lock()
  285. defer f.mu.Unlock()
  286. if f.fd >= 0 {
  287. unix.Close(f.fd)
  288. f.fd = -1
  289. }
  290. return 0
  291. }
  292. func (f *kbFile) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno {
  293. f.mu.Lock()
  294. defer f.mu.Unlock()
  295. var st unix.Stat_t
  296. if err := unix.Fstat(f.fd, &st); err != nil {
  297. return err.(syscall.Errno)
  298. }
  299. fillAttr(&st, &out.Attr)
  300. return 0
  301. }
  302. func (f *kbFile) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) {
  303. f.mu.Lock()
  304. defer f.mu.Unlock()
  305. n, err := unix.Seek(f.fd, int64(off), int(whence))
  306. if err != nil {
  307. return 0, err.(syscall.Errno)
  308. }
  309. return uint64(n), 0
  310. }
  311. func direntMode(typ uint8) uint32 {
  312. switch typ {
  313. case unix.DT_DIR:
  314. return unix.S_IFDIR
  315. case unix.DT_LNK:
  316. return unix.S_IFLNK
  317. case unix.DT_FIFO:
  318. return unix.S_IFIFO
  319. case unix.DT_SOCK:
  320. return unix.S_IFSOCK
  321. case unix.DT_CHR:
  322. return unix.S_IFCHR
  323. case unix.DT_BLK:
  324. return unix.S_IFBLK
  325. default:
  326. return unix.S_IFREG
  327. }
  328. }
  329. // parseDirent 解析一条 linux_dirent64:d_ino(8) d_off(8) d_reclen(2) d_type(1) d_name
  330. func parseDirent(b []byte) (reclen int, ino uint64, typ uint8, name string) {
  331. if len(b) < 19 {
  332. return 0, 0, 0, ""
  333. }
  334. reclen = int(uint16(b[16]) | uint16(b[17])<<8)
  335. if reclen < 19 || reclen > len(b) {
  336. return 0, 0, 0, ""
  337. }
  338. for i := 0; i < 8; i++ {
  339. ino |= uint64(b[i]) << (8 * i)
  340. }
  341. typ = b[18]
  342. nb := b[19:reclen]
  343. if i := indexZero(nb); i >= 0 {
  344. nb = nb[:i]
  345. }
  346. name = string(nb)
  347. if name == "." || name == ".." {
  348. name = ""
  349. }
  350. return
  351. }
  352. func indexZero(b []byte) int {
  353. for i, c := range b {
  354. if c == 0 {
  355. return i
  356. }
  357. }
  358. return -1
  359. }
  360. // mountFS 把源目录挂到 mountpoint
  361. func mountFS(mountpoint, name string, src *source) (*fuse.Server, error) {
  362. sec := time.Second
  363. root := &kbNode{src: src}
  364. return fs.Mount(mountpoint, root, &fs.Options{
  365. EntryTimeout: &sec,
  366. AttrTimeout: &sec,
  367. NegativeTimeout: &sec,
  368. MountOptions: fuse.MountOptions{
  369. AllowOther: true,
  370. FsName: name,
  371. Name: "agentkb",
  372. Options: []string{"ro", "nosuid", "nodev", "noexec"},
  373. MaxReadAhead: 128 * 1024,
  374. },
  375. })
  376. }