The recent change to disallow following symlinks by default conflicts with the expected behavior of .stignore. This adds a new flag which may be passed to OpenFile to skip default symlink-forbidding open flag. --------- Signed-off-by: Jakob Borg <jakob@kastelo.net>
This commit is contained in:
+11
-1
@@ -240,16 +240,24 @@ func (f *BasicFilesystem) DirNames(name string) ([]string, error) {
|
|||||||
return names, nil
|
return names, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open opens the file for reading, while not following symlinks
|
||||||
func (f *BasicFilesystem) Open(name string) (File, error) {
|
func (f *BasicFilesystem) Open(name string) (File, error) {
|
||||||
return f.OpenFile(name, os.O_RDONLY, 0)
|
return f.OpenFile(name, os.O_RDONLY, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenFile is the generalised file open call, using the flags to determine
|
||||||
|
// the open semantics. We always add O_NOFOLLOW, disallowing opening files
|
||||||
|
// by following symlinks, unless OptFollow is set.
|
||||||
func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File, error) {
|
func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File, error) {
|
||||||
rootedName, err := f.rooted(name)
|
rootedName, err := f.rooted(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
flags |= alwaysOpenFlags // enforce extra bits in flags
|
if flags&OptFollow != 0 {
|
||||||
|
flags &^= OptFollow // unset the synthetic OptFollow bit
|
||||||
|
} else {
|
||||||
|
flags |= optNoFollow
|
||||||
|
}
|
||||||
fd, err := os.OpenFile(rootedName, flags, os.FileMode(mode))
|
fd, err := os.OpenFile(rootedName, flags, os.FileMode(mode))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -257,6 +265,8 @@ func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File,
|
|||||||
return basicFile{fd, name}, err
|
return basicFile{fd, name}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create opens a file for writing, creating it as required. The Create will
|
||||||
|
// fail if the destination is a symlink.
|
||||||
func (f *BasicFilesystem) Create(name string) (File, error) {
|
func (f *BasicFilesystem) Create(name string) (File, error) {
|
||||||
return f.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
|
return f.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const alwaysOpenFlags = syscall.O_NOFOLLOW // never open symlinks as the final path component
|
const optNoFollow = syscall.O_NOFOLLOW
|
||||||
|
|
||||||
func (f *BasicFilesystem) CreateSymlink(target, name string) error {
|
func (f *BasicFilesystem) CreateSymlink(target, name string) error {
|
||||||
name, err := f.rooted(name)
|
name, err := f.rooted(name)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
const alwaysOpenFlags = 0 // no extra flags
|
const optNoFollow = 0 // not defined for Windows
|
||||||
|
|
||||||
var errNotSupported = errors.New("symlinks not supported")
|
var errNotSupported = errors.New("symlinks not supported")
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"math/bits"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -173,8 +174,18 @@ const (
|
|||||||
OptSync = os.O_SYNC
|
OptSync = os.O_SYNC
|
||||||
OptTruncate = os.O_TRUNC
|
OptTruncate = os.O_TRUNC
|
||||||
OptWriteOnly = os.O_WRONLY
|
OptWriteOnly = os.O_WRONLY
|
||||||
|
OptFollow = 1 << (bits.UintSize - 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Ensure OptFollow doesn't clash with any other flag.
|
||||||
|
flags := OptAppend | OptCreate | OptExclusive | OptReadOnly | OptReadWrite | OptSync | OptTruncate | OptWriteOnly
|
||||||
|
flags &= OptFollow
|
||||||
|
if flags != 0 {
|
||||||
|
panic("bug: flag clash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SkipDir is used as a return value from WalkFuncs to indicate that
|
// SkipDir is used as a return value from WalkFuncs to indicate that
|
||||||
// the directory named in the call is to be skipped. It is not returned
|
// the directory named in the call is to be skipped. It is not returned
|
||||||
// as an error by any function.
|
// as an error by any function.
|
||||||
|
|||||||
@@ -357,8 +357,8 @@ func hashPatterns(patterns []Pattern) string {
|
|||||||
return hex.EncodeToString(h.Sum(nil))
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadIgnoreFile(fs fs.Filesystem, file string) (fs.File, fs.FileInfo, error) {
|
func loadIgnoreFile(ffs fs.Filesystem, file string) (fs.File, fs.FileInfo, error) {
|
||||||
fd, err := fs.Open(file)
|
fd, err := ffs.OpenFile(file, fs.OptReadOnly|fs.OptFollow, 0o666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fd, nil, err
|
return fd, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1731,3 +1731,37 @@ func testEscape(t *testing.T, tests []escapeTest, noErrors bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestIgnoreThroughSymlink verifies that an ignore file can be loaded when
|
||||||
|
// it is itself a symlink pointing at the real file. This exercises the
|
||||||
|
// SkipDefaultOpenFlags path, as our normal Open enforces O_NOFOLLOW. We use
|
||||||
|
// a real filesystem here because the fake one does not implement the
|
||||||
|
// O_NOFOLLOW semantics that make this distinction meaningful.
|
||||||
|
func TestIgnoreThroughSymlink(t *testing.T) {
|
||||||
|
if build.IsWindows {
|
||||||
|
t.Skip("symlinks not supported on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
testFS := fs.NewFilesystem(fs.FilesystemTypeBasic, t.TempDir())
|
||||||
|
|
||||||
|
// The real ignore file lives under a different name, and .stignore is a
|
||||||
|
// symlink pointing at it.
|
||||||
|
if err := fs.WriteFile(testFS, "real-ignores", []byte("bfile\n"), 0o666); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := testFS.CreateSymlink("real-ignores", ".stignore"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pats := New(testFS, WithCache(true))
|
||||||
|
if err := pats.Load(".stignore"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pats.Match("bfile").IsIgnored() {
|
||||||
|
t.Error("bfile should be ignored")
|
||||||
|
}
|
||||||
|
if pats.Match("afile").IsIgnored() {
|
||||||
|
t.Error("afile should not be ignored")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user