github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/filesystem/open_posix.go (about) 1 //go:build !windows 2 3 package filesystem 4 5 import ( 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "time" 11 12 "golang.org/x/sys/unix" 13 ) 14 15 // Open opens a filesystem path for traversal and/or other operations. It will 16 // return either a Directory or an io.ReadSeekCloser object (as an io.Closer for 17 // convenient closing access without casting), along with Metadata that can be 18 // used to determine the type of object being returned. Unless requested, this 19 // function does not allow the leaf component of path to be a symbolic link 20 // (though intermediate components of the path can be symbolic links and will be 21 // resolved in the resolution of the path), and an error will be returned if 22 // this is the case (though on POSIX systems it will not be 23 // ErrUnsupportedOpenType). However, if allowSymbolicLinkLeaf is true, then this 24 // function will allow resolution of a path leaf component that's a symbolic 25 // link. In this case, the referenced object must still be a directory or 26 // regular file, and the returned object will still be either a Directory or an 27 // io.ReadSeekCloser. 28 func Open(path string, allowSymbolicLinkLeaf bool) (io.Closer, *Metadata, error) { 29 // Open the file. Unless explicitly allowed, we disable resolution of 30 // symbolic links at the leaf position of the path by specifying O_NOFOLLOW. 31 // Note that this flag only affects the leaf component of the path - 32 // intermediate symbolic links are still allowed and resolved. 33 // 34 // Ideally, we'd want the open function to open the symbolic link itself 35 // in the event that O_NOFOLLOW is specified and the path leaf references a 36 // symbolic link, and then we could get stat information on the opened 37 // symbolic link object and return ErrUnsupportedType. Unfortunately, that's 38 // not what happens when you pass O_NOFOLLOW to open and the path leaf 39 // references a symbolic link. Instead, it returns ELOOP. This is 40 // problematic because ELOOP is also used to indicate the condition where 41 // too many symbolic links have been encountered, and thus there's no way to 42 // differentiate the two cases and figure out whether or not we should 43 // return ErrUnsupportedType. Even openat doesn't provide a solution to this 44 // problem since it doens't support AT_SYMLINK_NOFOLLOW. Essentially, 45 // there's no way to "open" a symbolic link - it can only be read with 46 // readlink and its ilk. Since ELOOP still sort of makes sense (we've 47 // encountered too many symbolic links at the path leaf), we return it 48 // unmodified. 49 flags := unix.O_RDONLY | unix.O_NOFOLLOW | unix.O_CLOEXEC | extraOpenFlags 50 if allowSymbolicLinkLeaf { 51 flags &^= unix.O_NOFOLLOW 52 } 53 descriptor, err := openatRetryingOnEINTR(unix.AT_FDCWD, path, flags, 0) 54 if err != nil { 55 return nil, nil, err 56 } 57 58 // Grab metadata for the file. 59 var rawMetadata unix.Stat_t 60 if err := fstatRetryingOnEINTR(descriptor, &rawMetadata); err != nil { 61 closeConsideringEINTR(descriptor) 62 return nil, nil, fmt.Errorf("unable to query file metadata: %w", err) 63 } 64 65 // Convert the raw system-level metadata. 66 metadata := &Metadata{ 67 Name: filepath.Base(path), 68 Mode: Mode(rawMetadata.Mode), 69 Size: uint64(rawMetadata.Size), 70 ModificationTime: time.Unix(rawMetadata.Mtim.Unix()), 71 DeviceID: uint64(rawMetadata.Dev), 72 FileID: uint64(rawMetadata.Ino), 73 } 74 75 // Dispatch further construction according to type. 76 switch metadata.Mode & ModeTypeMask { 77 case ModeTypeDirectory: 78 return &Directory{ 79 descriptor: descriptor, 80 file: os.NewFile(uintptr(descriptor), path), 81 }, metadata, nil 82 case ModeTypeFile: 83 return file(descriptor), metadata, nil 84 default: 85 closeConsideringEINTR(descriptor) 86 return nil, nil, ErrUnsupportedOpenType 87 } 88 }