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  }