1package mountinfo 2 3import ( 4 "os" 5 "path/filepath" 6 7 "golang.org/x/sys/unix" 8) 9 10// mountedByOpenat2 is a method of detecting a mount that works for all kinds 11// of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel. 12func mountedByOpenat2(path string) (bool, error) { 13 dir, last := filepath.Split(path) 14 15 dirfd, err := unix.Openat2(unix.AT_FDCWD, dir, &unix.OpenHow{ 16 Flags: unix.O_PATH | unix.O_CLOEXEC, 17 }) 18 if err != nil { 19 if err == unix.ENOENT { // not a mount 20 return false, nil 21 } 22 return false, &os.PathError{Op: "openat2", Path: dir, Err: err} 23 } 24 fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{ 25 Flags: unix.O_PATH | unix.O_CLOEXEC | unix.O_NOFOLLOW, 26 Resolve: unix.RESOLVE_NO_XDEV, 27 }) 28 _ = unix.Close(dirfd) 29 switch err { 30 case nil: // definitely not a mount 31 _ = unix.Close(fd) 32 return false, nil 33 case unix.EXDEV: // definitely a mount 34 return true, nil 35 case unix.ENOENT: // not a mount 36 return false, nil 37 } 38 // not sure 39 return false, &os.PathError{Op: "openat2", Path: path, Err: err} 40} 41 42func mounted(path string) (bool, error) { 43 // Try a fast path, using openat2() with RESOLVE_NO_XDEV. 44 mounted, err := mountedByOpenat2(path) 45 if err == nil { 46 return mounted, nil 47 } 48 // Another fast path: compare st.st_dev fields. 49 mounted, err = mountedByStat(path) 50 // This does not work for bind mounts, so false negative 51 // is possible, therefore only trust if return is true. 52 if mounted && err == nil { 53 return mounted, nil 54 } 55 56 // Fallback to parsing mountinfo 57 return mountedByMountinfo(path) 58} 59