1package vfs 2 3import ( 4 "errors" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "syscall" 9) 10 11// A Stater implements Stat. It is assumed that the fs.FileInfos returned by 12// Stat are compatible with os.SameFile. 13type Stater interface { 14 Stat(string) (fs.FileInfo, error) 15} 16 17// Contains returns true if p is reachable by traversing through prefix. prefix 18// must exist, but p may not. It is an expensive but accurate alternative to the 19// deprecated filepath.HasPrefix. 20func Contains(fileSystem Stater, p, prefix string) (bool, error) { 21 prefixFI, err := fileSystem.Stat(prefix) 22 if err != nil { 23 return false, err 24 } 25 for { 26 fi, err := fileSystem.Stat(p) 27 switch { 28 case err == nil: 29 if os.SameFile(fi, prefixFI) { 30 return true, nil 31 } 32 goto TryParent 33 case errors.Is(err, fs.ErrNotExist): 34 goto TryParent 35 case errors.Is(err, fs.ErrPermission): 36 goto TryParent 37 default: 38 // Remove any fs.PathError or os.SyscallError wrapping, if present. 39 Unwrap: 40 for { 41 var pathError *fs.PathError 42 var syscallError *os.SyscallError 43 switch { 44 case errors.As(err, &pathError): 45 err = pathError.Err 46 case errors.As(err, &syscallError): 47 err = syscallError.Err 48 default: 49 break Unwrap 50 } 51 } 52 // Ignore some syscall.Errnos. 53 var syscallErrno syscall.Errno 54 if errors.As(err, &syscallErrno) { 55 if _, ignore := ignoreErrnoInContains[syscallErrno]; ignore { 56 goto TryParent 57 } 58 } 59 return false, err 60 } 61 TryParent: 62 parentDir := filepath.Dir(p) 63 if parentDir == p { 64 // Return when we stop making progress. 65 return false, nil 66 } 67 p = parentDir 68 } 69} 70