manager.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. package main
  2. // 挂载管理(docs/design/21-knowledge-sources.md §4.4、§4.5):
  3. // 平台用 sync 下发完整的登记列表;挂载进程挂上缺少的、卸载多余的、更新有变化的。
  4. // 挂载表变化时(设备插拔)立即重新计算状态,不需要定时检查。
  5. import (
  6. "encoding/json"
  7. "fmt"
  8. "log"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "regexp"
  13. "sort"
  14. "sync"
  15. "time"
  16. "github.com/hanwen/go-fuse/v2/fuse"
  17. "golang.org/x/sys/unix"
  18. )
  19. var idPattern = regexp.MustCompile(`^kb_[0-9a-z]{1,64}$`)
  20. type mountSpec struct {
  21. ID string `json:"id"`
  22. Kind string `json:"kind"`
  23. Local *localSource `json:"local,omitempty"`
  24. Remote *remoteSpec `json:"remote,omitempty"`
  25. }
  26. type mountStatus struct {
  27. ID string `json:"id"`
  28. State string `json:"state"`
  29. Message *string `json:"message"`
  30. Since string `json:"since"`
  31. // 当前的实际路径(本机目录)
  32. RealPath string `json:"realPath,omitempty"`
  33. }
  34. type mount struct {
  35. spec mountSpec
  36. point string
  37. src *source
  38. server *fuse.Server
  39. state string
  40. message string
  41. since time.Time
  42. realPath string
  43. remote *remoteProc // 远程存储的 rclone 挂载
  44. remoteErr string // 远程存储配置不正确
  45. }
  46. type manager struct {
  47. mu sync.Mutex
  48. sys system
  49. roots []string
  50. mntDir string
  51. stateFile string
  52. mounts map[string]*mount
  53. lastMiss time.Time
  54. // mountFn 便于测试替换
  55. mountFn func(point, name string, src *source) (*fuse.Server, error)
  56. // 远程存储:rclone 的内部挂载点与缓存目录
  57. remoteDir string
  58. cacheDir string
  59. runner *rcloneRunner
  60. }
  61. func newManager(sys system, roots []string, mntDir, stateFile string) *manager {
  62. return &manager{sys: sys, roots: roots, mntDir: mntDir, stateFile: stateFile, mounts: map[string]*mount{}, mountFn: mountFS}
  63. }
  64. // withRemote 启用远程存储(rclone 程序、内部挂载点目录、缓存目录)
  65. func (m *manager) withRemote(runner *rcloneRunner, remoteDir, cacheDir string) *manager {
  66. m.runner, m.remoteDir, m.cacheDir = runner, remoteDir, cacheDir
  67. return m
  68. }
  69. func (m *manager) setState(mt *mount, state, msg string) {
  70. if mt.state != state || mt.message != msg {
  71. if mt.state != "" {
  72. log.Printf("%s:%s → %s %s", mt.spec.ID, mt.state, state, msg)
  73. }
  74. mt.state, mt.message, mt.since = state, msg, time.Now()
  75. }
  76. }
  77. // resolve 按登记找到源目录并检查,返回真实路径与设备号、inode 号
  78. func (m *manager) resolve(spec mountSpec) (string, uint64, uint64, *checkError) {
  79. if spec.Kind != "local" || spec.Local == nil {
  80. return "", 0, 0, &checkError{"mount_failed", "暂不支持这种资料库类型"}
  81. }
  82. p, cerr := locate(m.sys, *spec.Local)
  83. if cerr != nil {
  84. return "", 0, 0, cerr
  85. }
  86. real, cerr := checkSource(p, m.roots)
  87. if cerr != nil {
  88. return "", 0, 0, cerr
  89. }
  90. how := unix.OpenHow{Flags: unix.O_PATH | unix.O_DIRECTORY | unix.O_CLOEXEC, Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_MAGICLINKS}
  91. fd, err := unix.Openat2(unix.AT_FDCWD, real, &how)
  92. if err != nil {
  93. return "", 0, 0, &checkError{"path_missing", "无法打开目录:" + real}
  94. }
  95. defer unix.Close(fd)
  96. var st unix.Stat_t
  97. if err := unix.Fstat(fd, &st); err != nil {
  98. return "", 0, 0, &checkError{"path_missing", "无法读取目录:" + real}
  99. }
  100. return real, st.Dev, st.Ino, nil
  101. }
  102. // resolveRemote 远程存储:rclone 已挂载时,源目录为内部挂载点;状态来自最近一次连接检查
  103. func (m *manager) resolveRemote(mt *mount) (string, uint64, uint64, string, string) {
  104. if mt.remoteErr != "" {
  105. return "", 0, 0, "mount_failed", mt.remoteErr
  106. }
  107. if mt.remote == nil {
  108. return "", 0, 0, "mount_failed", "没有配置 rclone"
  109. }
  110. mounted, state, msg := mt.remote.status()
  111. if !mounted || !mt.remote.isMounted() {
  112. if state == "ok" {
  113. state, msg = "pending", "正在连接"
  114. }
  115. return "", 0, 0, state, msg
  116. }
  117. var st unix.Stat_t
  118. if err := unix.Stat(mt.remote.point, &st); err != nil {
  119. return "", 0, 0, "unreachable", "无法访问 rclone 挂载:" + err.Error()
  120. }
  121. return mt.remote.point, st.Dev, st.Ino, state, msg
  122. }
  123. // refreshLocked 重新计算一个挂载的状态
  124. func (m *manager) refreshLocked(mt *mount) {
  125. if mt.spec.Kind == "remote" {
  126. real, dev, ino, state, msg := m.resolveRemote(mt)
  127. if real == "" {
  128. mt.src.close()
  129. } else {
  130. mt.src.set(real, dev, ino)
  131. }
  132. mt.realPath = ""
  133. if mt.server == nil && state == "ok" {
  134. state, msg = "mount_failed", "挂载失败"
  135. }
  136. m.setState(mt, state, msg)
  137. return
  138. }
  139. real, dev, ino, cerr := m.resolve(mt.spec)
  140. if cerr != nil {
  141. mt.src.close()
  142. mt.realPath = ""
  143. m.setState(mt, cerr.State, cerr.Message)
  144. return
  145. }
  146. mt.src.set(real, dev, ino)
  147. mt.realPath = real
  148. if mt.server == nil {
  149. m.setState(mt, "mount_failed", "挂载失败")
  150. return
  151. }
  152. m.setState(mt, "ok", "")
  153. }
  154. func (m *manager) refreshAll() {
  155. m.mu.Lock()
  156. defer m.mu.Unlock()
  157. for _, mt := range m.mounts {
  158. m.refreshLocked(mt)
  159. }
  160. }
  161. // onMiss 资料库不可用时有人访问:可能是设备刚接上(如自动挂载),限速重新检查
  162. func (m *manager) onMiss() {
  163. m.mu.Lock()
  164. if time.Since(m.lastMiss) < 2*time.Second {
  165. m.mu.Unlock()
  166. return
  167. }
  168. m.lastMiss = time.Now()
  169. m.mu.Unlock()
  170. go m.refreshAll()
  171. }
  172. func (m *manager) mountLocked(mt *mount) {
  173. if mt.server != nil {
  174. return
  175. }
  176. if err := os.MkdirAll(mt.point, 0o755); err != nil {
  177. log.Printf("%s:创建挂载点失败:%v", mt.spec.ID, err)
  178. return
  179. }
  180. srv, err := m.mountFn(mt.point, "kb:"+mt.spec.ID, mt.src)
  181. if err != nil {
  182. log.Printf("%s:挂载失败:%v", mt.spec.ID, err)
  183. return
  184. }
  185. mt.server = srv
  186. }
  187. func (m *manager) unmountLocked(mt *mount) {
  188. if mt.server != nil {
  189. if err := mt.server.Unmount(); err != nil {
  190. // 有文件正被打开:延迟卸载
  191. _ = exec.Command("fusermount3", "-uz", mt.point).Run()
  192. }
  193. mt.server = nil
  194. }
  195. mt.src.close()
  196. _ = os.Remove(mt.point)
  197. if mt.remote != nil {
  198. mt.remote.shutdown()
  199. mt.remote = nil
  200. }
  201. }
  202. // setupRemoteLocked 远程存储:配置变化时重启 rclone,返回新启动的进程
  203. func (m *manager) setupRemoteLocked(mt *mount) *remoteProc {
  204. mt.remoteErr = ""
  205. if m.runner == nil {
  206. mt.remoteErr = "挂载服务没有配置 rclone"
  207. return nil
  208. }
  209. env, err := buildRemote(*mt.spec.Remote, m.runner.bin)
  210. if err != nil {
  211. mt.remoteErr = err.Error()
  212. if mt.remote != nil {
  213. mt.remote.shutdown()
  214. mt.remote = nil
  215. }
  216. return nil
  217. }
  218. key := remoteKey(mt.spec.Remote)
  219. if mt.remote != nil && mt.remote.key == key {
  220. return nil
  221. }
  222. if mt.remote != nil {
  223. log.Printf("%s:远程存储配置有变化,重新连接", mt.spec.ID)
  224. mt.remote.shutdown()
  225. }
  226. mt.remote = newRemoteProc(mt.spec.ID, m.remoteDir, m.cacheDir, m.runner, m.sys, env, key, func() { go m.refreshAll() })
  227. mt.remote.start()
  228. return mt.remote
  229. }
  230. // sync 按完整列表挂载、卸载、更新
  231. func (m *manager) sync(specs []mountSpec, save bool) ([]mountStatus, error) {
  232. want := map[string]mountSpec{}
  233. for _, s := range specs {
  234. if !idPattern.MatchString(s.ID) {
  235. return nil, fmt.Errorf("资料库 ID 格式不正确:%q", s.ID)
  236. }
  237. if s.Kind == "local" && (s.Local == nil || !filepath.IsAbs(s.Local.Path)) {
  238. return nil, fmt.Errorf("%s:缺少本机目录", s.ID)
  239. }
  240. if s.Kind == "remote" && s.Remote == nil {
  241. return nil, fmt.Errorf("%s:缺少远程存储配置", s.ID)
  242. }
  243. if s.Kind != "local" && s.Kind != "remote" {
  244. return nil, fmt.Errorf("%s:不支持的类型 %s", s.ID, s.Kind)
  245. }
  246. want[s.ID] = s
  247. }
  248. m.mu.Lock()
  249. var started []*remoteProc
  250. for id, mt := range m.mounts {
  251. if _, ok := want[id]; !ok {
  252. m.unmountLocked(mt)
  253. delete(m.mounts, id)
  254. log.Printf("%s:已卸载", id)
  255. }
  256. }
  257. for id, s := range want {
  258. mt := m.mounts[id]
  259. if mt == nil {
  260. mt = &mount{spec: s, point: filepath.Join(m.mntDir, id), src: newSource()}
  261. mt.src.onMiss = m.onMiss
  262. m.mounts[id] = mt
  263. log.Printf("%s:登记 %s", id, describeSpec(s))
  264. } else {
  265. if mt.spec.Kind != s.Kind && mt.remote != nil {
  266. mt.remote.shutdown()
  267. mt.remote = nil
  268. }
  269. mt.spec = s
  270. }
  271. if s.Kind == "remote" {
  272. if p := m.setupRemoteLocked(mt); p != nil {
  273. started = append(started, p)
  274. }
  275. }
  276. m.mountLocked(mt)
  277. m.refreshLocked(mt)
  278. }
  279. m.mu.Unlock()
  280. if save {
  281. if err := m.saveState(specs); err != nil {
  282. log.Printf("保存 state.json 失败:%v", err)
  283. }
  284. }
  285. // 新连接的远程存储:等首次得出状态(最多 20 秒),让平台拿到准确的状态
  286. timeout := time.After(20 * time.Second)
  287. for _, p := range started {
  288. select {
  289. case <-p.ready:
  290. case <-timeout:
  291. }
  292. }
  293. if len(started) > 0 {
  294. m.refreshAll()
  295. }
  296. return m.status(), nil
  297. }
  298. // checkRemotes 状态查询时,超过 30 秒没检查过的远程存储在后台重新检查
  299. func (m *manager) checkRemotes() {
  300. m.mu.Lock()
  301. defer m.mu.Unlock()
  302. for _, mt := range m.mounts {
  303. if mt.remote != nil {
  304. mt.remote.checkIfStale()
  305. }
  306. }
  307. }
  308. func describeSpec(s mountSpec) string {
  309. if s.Remote != nil {
  310. return fmt.Sprintf("远程存储 %s %s", s.Remote.Type, s.Remote.Params["endpoint"]+s.Remote.Params["url"]+"/"+s.Remote.Params["bucket"])
  311. }
  312. if s.Local != nil {
  313. if s.Local.UUID != "" {
  314. return fmt.Sprintf("%s(UUID %s,设备内 %s)", s.Local.Path, s.Local.UUID, s.Local.RelPath)
  315. }
  316. return s.Local.Path
  317. }
  318. return s.Kind
  319. }
  320. func (m *manager) status() []mountStatus {
  321. m.mu.Lock()
  322. defer m.mu.Unlock()
  323. out := make([]mountStatus, 0, len(m.mounts))
  324. for id, mt := range m.mounts {
  325. st := mountStatus{ID: id, State: mt.state, Since: mt.since.UTC().Format(time.RFC3339), RealPath: mt.realPath}
  326. if mt.message != "" {
  327. msg := mt.message
  328. st.Message = &msg
  329. }
  330. out = append(out, st)
  331. }
  332. sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
  333. return out
  334. }
  335. func (m *manager) unmountAll() {
  336. m.mu.Lock()
  337. defer m.mu.Unlock()
  338. for id, mt := range m.mounts {
  339. m.unmountLocked(mt)
  340. delete(m.mounts, id)
  341. }
  342. }
  343. type stateFile struct {
  344. Mounts []mountSpec `json:"mounts"`
  345. }
  346. func (m *manager) saveState(specs []mountSpec) error {
  347. b, err := json.MarshalIndent(stateFile{Mounts: specs}, "", " ")
  348. if err != nil {
  349. return err
  350. }
  351. tmp := m.stateFile + ".tmp"
  352. if err := os.WriteFile(tmp, b, 0o600); err != nil {
  353. return err
  354. }
  355. return os.Rename(tmp, m.stateFile)
  356. }
  357. func (m *manager) loadState() []mountSpec {
  358. b, err := os.ReadFile(m.stateFile)
  359. if err != nil {
  360. return nil
  361. }
  362. var s stateFile
  363. if json.Unmarshal(b, &s) != nil {
  364. log.Printf("state.json 格式不正确,忽略")
  365. return nil
  366. }
  367. return s.Mounts
  368. }
  369. // cleanupStale 启动时清理 mnt/ 下残留的挂载(上次进程退出后失效)和多余的空目录
  370. func (m *manager) cleanupStale() {
  371. for _, e := range m.sys.mountinfo() {
  372. if filepath.Dir(e.Point) == m.mntDir || (m.remoteDir != "" && filepath.Dir(e.Point) == m.remoteDir) {
  373. if err := exec.Command("fusermount3", "-uz", e.Point).Run(); err != nil {
  374. log.Printf("清理残留挂载 %s 失败:%v", e.Point, err)
  375. } else {
  376. log.Printf("已清理残留挂载 %s", e.Point)
  377. }
  378. }
  379. }
  380. ents, _ := os.ReadDir(m.mntDir)
  381. for _, e := range ents {
  382. if idPattern.MatchString(e.Name()) {
  383. _ = os.Remove(filepath.Join(m.mntDir, e.Name()))
  384. }
  385. }
  386. }
  387. // watchMountinfo 挂载表变化时(POLLPRI)重新计算状态
  388. func (m *manager) watchMountinfo(stop <-chan struct{}) {
  389. f, err := os.Open("/proc/self/mountinfo")
  390. if err != nil {
  391. log.Printf("无法监听挂载表:%v", err)
  392. return
  393. }
  394. defer f.Close()
  395. buf := make([]byte, 64*1024)
  396. drain := func() {
  397. _, _ = f.Seek(0, 0)
  398. for {
  399. n, err := f.Read(buf)
  400. if n == 0 || err != nil {
  401. return
  402. }
  403. }
  404. }
  405. drain()
  406. fds := []unix.PollFd{{Fd: int32(f.Fd()), Events: unix.POLLPRI}}
  407. for {
  408. select {
  409. case <-stop:
  410. return
  411. default:
  412. }
  413. n, err := unix.Poll(fds, 1000)
  414. if err != nil && err != unix.EINTR {
  415. log.Printf("监听挂载表出错:%v", err)
  416. time.Sleep(time.Second)
  417. continue
  418. }
  419. if n <= 0 || fds[0].Revents&(unix.POLLPRI|unix.POLLERR) == 0 {
  420. continue
  421. }
  422. drain()
  423. // 设备插入时往往连续变化几次,稍等一下再统一计算
  424. time.Sleep(300 * time.Millisecond)
  425. drain()
  426. m.refreshAll()
  427. }
  428. }