Skip to content

Commit

Permalink
Merge branch 'google:main' into extractor_swift_packageresolved
Browse files Browse the repository at this point in the history
  • Loading branch information
Octaviusss authored Jan 17, 2025
2 parents a14cb3b + 33a0f08 commit ad8d4da
Show file tree
Hide file tree
Showing 90 changed files with 5,031 additions and 520 deletions.
1 change: 1 addition & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
args: --timeout=5m
tests:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
Expand Down
14 changes: 7 additions & 7 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ linters:
- bidichk
- bodyclose
- canonicalheader
# - containedctx
# - contextcheck
- containedctx
- contextcheck
- copyloopvar
- decorder
# - depguard
Expand All @@ -24,7 +24,7 @@ linters:
- durationcheck
# - errcheck
# - errchkjson
# - errname
- errname
# - errorlint
# - exhaustive
# - fatcontext
Expand Down Expand Up @@ -54,15 +54,14 @@ linters:
- makezero
- mirror
- misspell
# - musttag
- musttag
- nakedret
# - nilerr
- nilerr
# - nilnil
# - nlreturn
# - noctx
- nolintlint
# - nosprintfhostport
# - paralleltest
# - perfsprint
# - prealloc
- predeclared
Expand Down Expand Up @@ -90,7 +89,8 @@ linters:
- zerologlint
disable-all: true
# disable:
# - tparallel # Parallel tests mixes up log lines of multiple tests in the internal test runner
# - paralleltest # Parallel tests mixes up log lines of multiple tests in the internal test runner
# - tparallel # Parallel tests mixes up log lines of multiple tests in the internal test runner

linters-settings:
forbidigo:
Expand Down
3 changes: 2 additions & 1 deletion artifact/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/osv-scalibr/artifact/image/require"
"github.com/google/osv-scalibr/artifact/image/unpack"
"github.com/opencontainers/go-digest"

scalibrfs "github.com/google/osv-scalibr/fs"
)
Expand All @@ -43,7 +44,7 @@ type Layer interface {
// produced by the FS method.
IsEmpty() bool
// DiffID is the hash of the uncompressed layer. Will be an empty string if the layer is empty.
DiffID() string
DiffID() digest.Digest
// Command is the specific command that produced the layer.
Command() string
// Uncompressed gives the uncompressed tar as a file reader.
Expand Down
55 changes: 44 additions & 11 deletions artifact/image/layerscanning/image/file_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import (
"io/fs"
"os"
"path"
"path/filepath"
"time"
)

const (

// filePermission represents the permission bits for a file, which are minimal since files in the
// layer scanning use case are read-only.
filePermission = 0600
Expand All @@ -30,27 +31,39 @@ const (
dirPermission = 0700
)

// FileNode represents a file in a virtual filesystem.
// fileNode represents a file in a virtual filesystem.
type fileNode struct {
// extractDir and originLayerID are used to construct the real file path of the fileNode.
extractDir string
originLayerID string
isWhiteout bool
virtualPath string
mode fs.FileMode
file *os.File

// isWhiteout is true if the fileNode represents a whiteout file
isWhiteout bool

// virtualPath is the path of the fileNode in the virtual filesystem.
virtualPath string
// targetPath is reserved for symlinks. It is the path that the symlink points to.
targetPath string

// size, mode, and modTime are used to implement the fs.FileInfo interface.
size int64
mode fs.FileMode
modTime time.Time

// file is the file object for the real file referred to by the fileNode.
file *os.File
}

// ========================================================
// fs.File METHODS
// ========================================================

// Stat returns the file info of real file referred by the fileNode.
// TODO: b/378130598 - Need to replace the os stat permission with the permissions on the filenode.
func (f *fileNode) Stat() (fs.FileInfo, error) {
if f.isWhiteout {
return nil, fs.ErrNotExist
}
return os.Stat(f.RealFilePath())
return f, nil
}

// Read reads the real file referred to by the fileNode.
Expand Down Expand Up @@ -94,20 +107,21 @@ func (f *fileNode) Close() error {
// RealFilePath returns the real file path of the fileNode. This is the concatenation of the
// root image extract directory, origin layer ID, and the virtual path.
func (f *fileNode) RealFilePath() string {
return path.Join(f.extractDir, f.originLayerID, f.virtualPath)
return filepath.Join(f.extractDir, f.originLayerID, filepath.FromSlash(f.virtualPath))
}

// ========================================================
// fs.DirEntry METHODS
// ========================================================

// Name returns the name of the fileNode.
// Name returns the name of the fileNode. Name is also used to implement the fs.FileInfo interface.
func (f *fileNode) Name() string {
_, filename := path.Split(f.virtualPath)
return filename
}

// IsDir returns whether the fileNode represents a directory.
// IsDir returns whether the fileNode represents a directory. IsDir is also used to implement the
// fs.FileInfo interface.
func (f *fileNode) IsDir() bool {
return f.Type().IsDir()
}
Expand All @@ -121,3 +135,22 @@ func (f *fileNode) Type() fs.FileMode {
func (f *fileNode) Info() (fs.FileInfo, error) {
return f.Stat()
}

// ========================================================
// fs.FileInfo METHODS
// ========================================================
func (f *fileNode) Size() int64 {
return f.size
}

func (f *fileNode) Mode() fs.FileMode {
return f.mode
}

func (f *fileNode) ModTime() time.Time {
return f.modTime
}

func (f *fileNode) Sys() any {
return nil
}
99 changes: 94 additions & 5 deletions artifact/image/layerscanning/image/file_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import (
"io/fs"
"os"
"path"
"path/filepath"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
Expand Down Expand Up @@ -58,7 +60,94 @@ var (

// TODO: b/377551664 - Add tests for the Stat method for the fileNode type.
func TestStat(t *testing.T) {
return
baseTime := time.Now()
regularFileNode := &fileNode{
extractDir: "tempDir",
originLayerID: "",
virtualPath: "/bar",
isWhiteout: false,
mode: filePermission,
size: 1,
modTime: baseTime,
}
symlinkFileNode := &fileNode{
extractDir: "tempDir",
originLayerID: "",
virtualPath: "/symlink-to-bar",
targetPath: "/bar",
isWhiteout: false,
mode: fs.ModeSymlink | filePermission,
size: 1,
modTime: baseTime,
}
whiteoutFileNode := &fileNode{
extractDir: "tempDir",
originLayerID: "",
virtualPath: "/bar",
isWhiteout: true,
mode: filePermission,
}

type info struct {
name string
size int64
mode fs.FileMode
modTime time.Time
}

tests := []struct {
name string
node *fileNode
wantInfo info
wantErr error
}{
{
name: "regular file",
node: regularFileNode,
wantInfo: info{
name: "bar",
size: 1,
mode: filePermission,
modTime: baseTime,
},
},
{
name: "symlink",
node: symlinkFileNode,
wantInfo: info{
name: "symlink-to-bar",
size: 1,
mode: fs.ModeSymlink | filePermission,
modTime: baseTime,
},
},
{
name: "whiteout file",
node: whiteoutFileNode,
wantErr: fs.ErrNotExist,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotFileNode, gotErr := tc.node.Stat()
if tc.wantErr != nil {
if diff := cmp.Diff(tc.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
t.Errorf("Stat(%v) returned unexpected error (-want +got): %v", tc.node, diff)
}
return
}

gotInfo := info{
name: gotFileNode.Name(),
size: gotFileNode.Size(),
mode: gotFileNode.Mode(),
modTime: gotFileNode.ModTime(),
}
if diff := cmp.Diff(tc.wantInfo, gotInfo, cmp.AllowUnexported(info{})); diff != "" {
t.Errorf("Stat(%v) returned unexpected fileNode (-want +got): %v", tc.node, diff)
}
})
}
}

func TestRead(t *testing.T) {
Expand Down Expand Up @@ -435,22 +524,22 @@ func TestRealFilePath(t *testing.T) {
{
name: "root directory",
node: rootDirectory,
want: "/tmp/extract/layer1",
want: filepath.FromSlash("/tmp/extract/layer1"),
},
{
name: "root file",
node: rootFile,
want: "/tmp/extract/layer1/bar",
want: filepath.FromSlash("/tmp/extract/layer1/bar"),
},
{
name: "non-root file",
node: nonRootFile,
want: "/tmp/extract/layer1/dir1/foo",
want: filepath.FromSlash("/tmp/extract/layer1/dir1/foo"),
},
{
name: "non-root directory",
node: nonRootDirectory,
want: "/tmp/extract/layer1/dir1/dir2",
want: filepath.FromSlash("/tmp/extract/layer1/dir1/dir2"),
},
}

Expand Down
Loading

0 comments on commit ad8d4da

Please sign in to comment.