Skip to content

Commit

Permalink
[Layer Scanning] Preserve the file permissions of files written to di…
Browse files Browse the repository at this point in the history
…sk when extracting from a tar file. Currently, all files had `0600` permissions, which breaks go binary extraction.

PiperOrigin-RevId: 715374364
  • Loading branch information
Mario Leyva authored and copybara-github committed Jan 15, 2025
1 parent 40d067f commit eb1530f
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 14 deletions.
53 changes: 42 additions & 11 deletions artifact/image/layerscanning/image/file_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import (
"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 @@ -31,28 +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
targetPath 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 @@ -103,13 +114,14 @@ func (f *fileNode) RealFilePath() string {
// 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 @@ -123,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
}
90 changes: 89 additions & 1 deletion artifact/image/layerscanning/image/file_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"path"
"path/filepath"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
Expand Down Expand Up @@ -59,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
13 changes: 11 additions & 2 deletions artifact/image/layerscanning/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,12 +385,17 @@ func (img *Image) handleDir(realFilePath, virtualPath, originLayerID string, tar
return nil, fmt.Errorf("failed to create directory with realFilePath %s: %w", realFilePath, err)
}
}

fileInfo := header.FileInfo()

return &fileNode{
extractDir: img.ExtractDir,
originLayerID: originLayerID,
virtualPath: virtualPath,
isWhiteout: isWhiteout,
mode: fs.FileMode(header.Mode) | fs.ModeDir,
mode: fileInfo.Mode() | fs.ModeDir,
size: fileInfo.Size(),
modTime: fileInfo.ModTime(),
}, nil
}

Expand All @@ -415,12 +420,16 @@ func (img *Image) handleFile(realFilePath, virtualPath, originLayerID string, ta
return nil, fmt.Errorf("unable to copy file: %w", err)
}

fileInfo := header.FileInfo()

return &fileNode{
extractDir: img.ExtractDir,
originLayerID: originLayerID,
virtualPath: virtualPath,
isWhiteout: isWhiteout,
mode: fs.FileMode(header.Mode),
mode: fileInfo.Mode(),
size: fileInfo.Size(),
modTime: fileInfo.ModTime(),
}, nil
}

Expand Down

0 comments on commit eb1530f

Please sign in to comment.