From ae19c460d1101313eb7ac5992e01608ac673e57a Mon Sep 17 00:00:00 2001 From: Felicitas Pojtinger Date: Fri, 21 Jan 2022 04:03:56 +0100 Subject: [PATCH] fix: Resolve symlinks when creating files for `OsFs` compatibility --- pkg/fs/file_test.go | 147 +++++++++++++++++++------------------- pkg/fs/filesystem.go | 131 +++++++++++++++++++++------------ pkg/fs/filesystem_test.go | 25 ++++--- pkg/inventory/stat.go | 6 ++ 4 files changed, 176 insertions(+), 133 deletions(-) diff --git a/pkg/fs/file_test.go b/pkg/fs/file_test.go index fdb01c8..c33e4d0 100644 --- a/pkg/fs/file_test.go +++ b/pkg/fs/file_test.go @@ -1472,94 +1472,93 @@ var readTests = []struct { false, false, }, - // FIXME: Handle linkname - // { - // "Can not read /brokensymlink into non-empty buffer", - // "/brokensymlink", - // true, - // func(f afero.Fs) error { - // symFs, ok := f.(symFs) - // if !ok { - // return nil - // } + { + "Can not read /brokensymlink into non-empty buffer", + "/brokensymlink", + true, + func(f afero.Fs) error { + symFs, ok := f.(symFs) + if !ok { + return nil + } - // if err := symFs.SymlinkIfPossible("/mydir", "/brokensymlink"); err != nil { - // return err - // } + if err := symFs.SymlinkIfPossible("/mydir", "/brokensymlink"); err != nil { + return err + } - // return nil - // }, - // func(f afero.File) error { - // gotContent := make([]byte, 10) + return nil + }, + func(f afero.File) error { + gotContent := make([]byte, 10) - // if _, err := f.Read(gotContent); err != io.EOF { - // return err - // } + if _, err := f.Read(gotContent); err != io.EOF { + return err + } - // return nil - // }, - // true, - // true, - // false, - // false, - // }, - // { - // "Can read /existingsymlink into non-empty buffer without readlink", - // "/existingsymlink", - // false, - // func(f afero.Fs) error { - // symFs, ok := f.(symFs) - // if !ok { - // return nil - // } + return nil + }, + true, + true, + false, + false, + }, + { + "Can read /existingsymlink into non-empty buffer without readlink", + "/existingsymlink", + false, + func(f afero.Fs) error { + symFs, ok := f.(symFs) + if !ok { + return nil + } - // file, err := f.Create("/test.txt") - // if err != nil { - // return err - // } + file, err := f.Create("/test.txt") + if err != nil { + return err + } - // r := newDeterministicReader(1000) + r := newDeterministicReader(1000) - // if _, err := io.Copy(file, r); err != nil { - // return err - // } + if _, err := io.Copy(file, r); err != nil { + return err + } - // if err := file.Close(); err != nil { - // return err - // } + if err := file.Close(); err != nil { + return err + } - // if err := symFs.SymlinkIfPossible("/test.txt", "/existingsymlink"); err != nil { - // return err - // } + if err := symFs.SymlinkIfPossible("/test.txt", "/existingsymlink"); err != nil { + return err + } - // return nil - // }, - // func(f afero.File) error { - // wantHash := "HTUi7GuNreHASha4hhl1xwuYk03pyTJ0IJbFLv04UdccT9m_NA2oBFTrnMxJhEu3VMGxDYk_04Th9C0zOj5MyA==" - // wantLength := int64(32800768) + return nil + }, + func(f afero.File) error { + wantHash := "HTUi7GuNreHASha4hhl1xwuYk03pyTJ0IJbFLv04UdccT9m_NA2oBFTrnMxJhEu3VMGxDYk_04Th9C0zOj5MyA==" + wantLength := int64(32800768) - // hasher := sha512.New() - // gotLength, err := io.Copy(hasher, f) - // if err != nil { - // return err - // } - // gotHash := base64.URLEncoding.EncodeToString(hasher.Sum(nil)) + hasher := sha512.New() + gotLength, err := io.Copy(hasher, f) + if err != nil { + return err + } + gotHash := base64.URLEncoding.EncodeToString(hasher.Sum(nil)) - // if gotLength != wantLength { - // return fmt.Errorf("invalid read length, got %v, want %v", gotLength, wantLength) - // } + if gotLength != wantLength { + return fmt.Errorf("invalid read length, got %v, want %v", gotLength, wantLength) + } - // if gotHash != wantHash { - // return fmt.Errorf("invalid read hash, got %v, want %v", gotHash, wantHash) - // } + if gotHash != wantHash { + return fmt.Errorf("invalid read hash, got %v, want %v", gotHash, wantHash) + } - // return nil - // }, - // true, - // false, // FIXME: This should not be required and BasePathFs fails if it is used - // false, // FIXME: Allow resolving symlinks without using readlink`, which is what `BasePathFs` supports` - // true, - // }, + return nil + }, + true, + true, + false, + false, + }, } func TestFile_Read(t *testing.T) { diff --git a/pkg/fs/filesystem.go b/pkg/fs/filesystem.go index d325fd9..ce81b83 100644 --- a/pkg/fs/filesystem.go +++ b/pkg/fs/filesystem.go @@ -459,58 +459,97 @@ func (f *STFS) OpenFile(name string, flag int, perm os.FileMode) (afero.File, er f.onHeader, ) + + createFile := func() error { + if !f.readOnly && flag&os.O_CREATE != 0 && flag&os.O_EXCL == 0 { + if _, err := inventory.Stat( + f.metadata, + + filepath.Dir(name), + false, + + f.onHeader, + ); err != nil { + if err == sql.ErrNoRows { + return os.ErrNotExist + } + + return err + } + + if target, err := inventory.Stat( + f.metadata, + + name, + true, + + f.onHeader, + ); err == nil { + if target.Typeflag == tar.TypeDir { + return config.ErrIsDirectory + } + } + + if err := f.mknodeWithoutLocking(false, name, perm, false, "", false); err != nil { + return err + } + + hdr, err = inventory.Stat( + f.metadata, + + name, + false, + + f.onHeader, + ) + if err != nil { + return err + } + } else { + return os.ErrNotExist + } + + return nil + } + if err != nil { if err == sql.ErrNoRows { - if !f.readOnly && flag&os.O_CREATE != 0 && flag&os.O_EXCL == 0 { - if _, err := inventory.Stat( - f.metadata, - - filepath.Dir(name), - false, - - f.onHeader, - ); err != nil { - if err == sql.ErrNoRows { - return nil, os.ErrNotExist - } - - return nil, err - } - - if target, err := inventory.Stat( - f.metadata, - - name, - true, - - f.onHeader, - ); err == nil { - if target.Typeflag == tar.TypeDir { - return nil, config.ErrIsDirectory - } - } - - if err := f.mknodeWithoutLocking(false, name, perm, false, "", false); err != nil { - return nil, err - } - - hdr, err = inventory.Stat( - f.metadata, - - name, - false, - - f.onHeader, - ) - if err != nil { - return nil, err - } - } else { - return nil, os.ErrNotExist + if err := createFile(); err != nil { + return nil, err } } else { return nil, err } + } else { + hdr, err = inventory.Stat( + f.metadata, + + hdr.Linkname, + false, + + f.onHeader, + ) + + if err == nil && !f.readOnly && flag&os.O_CREATE != 0 && flag&os.O_EXCL == 0 { + hdr, err = inventory.Stat( + f.metadata, + + hdr.Linkname, + true, + + f.onHeader, + ) + } + + if err != nil { + if err == sql.ErrNoRows { + if err := createFile(); err != nil { + return nil, err + } + } else { + return nil, err + } + } } } else { return nil, err diff --git a/pkg/fs/filesystem_test.go b/pkg/fs/filesystem_test.go index 41d2ed5..698ab1d 100644 --- a/pkg/fs/filesystem_test.go +++ b/pkg/fs/filesystem_test.go @@ -1388,20 +1388,19 @@ var openTests = []struct { }, func(f afero.File) error { return nil }, }, - // FIXME: Since we can't differentiate between broken and non-broken symlinks, this does not work yet - // { - // "Can not broken symlink to /brokensymlink", - // openArgs{"/brokensymlink"}, - // true, - // func(sf symFs) error { - // if err := sf.SymlinkIfPossible("/test.txt", "/brokensymlink"); err != nil { - // return nil - // } + { + "Can not open broken symlink to /test.txt", + openArgs{"/brokensymlink"}, + true, + func(sf symFs) error { + if err := sf.SymlinkIfPossible("/test.txt", "/brokensymlink"); err != nil { + return nil + } - // return nil - // }, - // func(f afero.File) error { return nil }, - // }, + return nil + }, + func(f afero.File) error { return nil }, + }, { "Can open symlink /existingsymlink to directory", openArgs{"/existingsymlink"}, diff --git a/pkg/inventory/stat.go b/pkg/inventory/stat.go index 34bdb90..d1c8708 100644 --- a/pkg/inventory/stat.go +++ b/pkg/inventory/stat.go @@ -51,6 +51,12 @@ func Stat( return nil, err } } + + // Prevent returning broken symlinks as headers + if !symlink && dbhdr.Linkname != "" { + return nil, sql.ErrNoRows + } + if symlink { dbhdr.Name = linkname dbhdr.Linkname = name