mounted_linux.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. package mountinfo
  2. import (
  3. "os"
  4. "path/filepath"
  5. "golang.org/x/sys/unix"
  6. )
  7. // MountedFast is a method of detecting a mount point without reading
  8. // mountinfo from procfs. A caller can only trust the result if no error
  9. // and sure == true are returned. Otherwise, other methods (e.g. parsing
  10. // /proc/mounts) have to be used. If unsure, use Mounted instead (which
  11. // uses MountedFast, but falls back to parsing mountinfo if needed).
  12. //
  13. // If a non-existent path is specified, an appropriate error is returned.
  14. // In case the caller is not interested in this particular error, it should
  15. // be handled separately using e.g. errors.Is(err, fs.ErrNotExist).
  16. //
  17. // This function is only available on Linux. When available (since kernel
  18. // v5.6), openat2(2) syscall is used to reliably detect all mounts. Otherwise,
  19. // the implementation falls back to using stat(2), which can reliably detect
  20. // normal (but not bind) mounts.
  21. func MountedFast(path string) (mounted, sure bool, err error) {
  22. // Root is always mounted.
  23. if path == string(os.PathSeparator) {
  24. return true, true, nil
  25. }
  26. path, err = normalizePath(path)
  27. if err != nil {
  28. return false, false, err
  29. }
  30. mounted, sure, err = mountedFast(path)
  31. return
  32. }
  33. // mountedByOpenat2 is a method of detecting a mount that works for all kinds
  34. // of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel.
  35. func mountedByOpenat2(path string) (bool, error) {
  36. dir, last := filepath.Split(path)
  37. dirfd, err := unix.Openat2(unix.AT_FDCWD, dir, &unix.OpenHow{
  38. Flags: unix.O_PATH | unix.O_CLOEXEC,
  39. })
  40. if err != nil {
  41. return false, &os.PathError{Op: "openat2", Path: dir, Err: err}
  42. }
  43. fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{
  44. Flags: unix.O_PATH | unix.O_CLOEXEC | unix.O_NOFOLLOW,
  45. Resolve: unix.RESOLVE_NO_XDEV,
  46. })
  47. _ = unix.Close(dirfd)
  48. switch err { //nolint:errorlint // unix errors are bare
  49. case nil: // definitely not a mount
  50. _ = unix.Close(fd)
  51. return false, nil
  52. case unix.EXDEV: // definitely a mount
  53. return true, nil
  54. }
  55. // not sure
  56. return false, &os.PathError{Op: "openat2", Path: path, Err: err}
  57. }
  58. // mountedFast is similar to MountedFast, except it expects a normalized path.
  59. func mountedFast(path string) (mounted, sure bool, err error) {
  60. // Root is always mounted.
  61. if path == string(os.PathSeparator) {
  62. return true, true, nil
  63. }
  64. // Try a fast path, using openat2() with RESOLVE_NO_XDEV.
  65. mounted, err = mountedByOpenat2(path)
  66. if err == nil {
  67. return mounted, true, nil
  68. }
  69. // Another fast path: compare st.st_dev fields.
  70. mounted, err = mountedByStat(path)
  71. // This does not work for bind mounts, so false negative
  72. // is possible, therefore only trust if return is true.
  73. if mounted && err == nil {
  74. return true, true, nil
  75. }
  76. return
  77. }
  78. func mounted(path string) (bool, error) {
  79. path, err := normalizePath(path)
  80. if err != nil {
  81. return false, err
  82. }
  83. mounted, sure, err := mountedFast(path)
  84. if sure && err == nil {
  85. return mounted, nil
  86. }
  87. // Fallback to parsing mountinfo.
  88. return mountedByMountinfo(path)
  89. }