Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions internal/fusefs/fuse_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func (fs *ArtifactFuse) LookUpInode(_ context.Context, op *fuseops.LookUpInodeOp
fs.mu.Unlock()

op.Entry.Child = ref.ID
op.Entry.Attributes = inodeAttrs(mode, uint64(size), typ, mtime)
op.Entry.Attributes = inodeAttrs(mode, size, typ, mtime)
setChildEntryExpiry(&op.Entry, time.Second)
return nil
}
Expand All @@ -216,7 +216,7 @@ func (fs *ArtifactFuse) GetInodeAttributes(_ context.Context, op *fuseops.GetIno
if err != nil {
return syscall.ENOENT
}
op.Attributes = inodeAttrs(mode, uint64(size), typ, mtime)
op.Attributes = inodeAttrs(mode, size, typ, mtime)
op.AttributesExpiration = attrExpiry(time.Second)
return nil
}
Expand All @@ -243,7 +243,7 @@ func (fs *ArtifactFuse) SetInodeAttributes(ctx context.Context, op *fuseops.SetI
if op.Mtime != nil {
mtime = *op.Mtime
}
op.Attributes = inodeAttrs(mode, uint64(size), typ, mtime)
op.Attributes = inodeAttrs(mode, size, typ, mtime)
op.AttributesExpiration = attrExpiry(time.Second)
return nil
}
Expand Down Expand Up @@ -567,7 +567,15 @@ func TryUnmount(mountPoint string) error {
return err
}

func inodeAttrs(mode uint32, size uint64, typ string, mtime time.Time) fuseops.InodeAttributes {
// inodeAttrs builds the kernel-visible attributes for an inode. The size is
// taken as a signed int64 because that's how snapshot and overlay state store
// it; a negative value there means the on-disk row is corrupt. Publishing it
// verbatim would wrap into a huge uint64 (e.g. -1 becomes ~18 exabytes), so
// clamp to zero instead.
func inodeAttrs(mode uint32, size int64, typ string, mtime time.Time) fuseops.InodeAttributes {
if size < 0 {
size = 0
}
m := os.FileMode(mode & 0o777)
if m == 0 {
if typ == "dir" {
Expand All @@ -586,7 +594,7 @@ func inodeAttrs(mode uint32, size uint64, typ string, mtime time.Time) fuseops.I
m |= os.ModeSymlink
}
return fuseops.InodeAttributes{
Size: size,
Size: uint64(size),
Nlink: 1,
Mode: m,
Uid: uint32(os.Getuid()),
Expand Down
85 changes: 85 additions & 0 deletions internal/fusefs/inode_attrs_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//go:build !windows

package fusefs

import (
"math"
"os"
"testing"
"time"
)

func TestInodeAttrs_ClampsNegativeSizeToZero(t *testing.T) {
cases := []struct {
name string
size int64
}{
{"minus one", -1},
{"min int64", math.MinInt64},
{"arbitrary negative", -4096},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := inodeAttrs(0o644, tc.size, "file", time.Unix(0, 0))
if got.Size != 0 {
t.Fatalf("file size = %d, want 0 for input %d", got.Size, tc.size)
}
})
}
}

func TestInodeAttrs_PreservesPositiveFileSize(t *testing.T) {
got := inodeAttrs(0o644, 42, "file", time.Unix(0, 0))
if got.Size != 42 {
t.Fatalf("file size = %d, want 42", got.Size)
}
}

func TestInodeAttrs_PreservesMaxInt64(t *testing.T) {
got := inodeAttrs(0o644, math.MaxInt64, "file", time.Unix(0, 0))
if got.Size != math.MaxInt64 {
t.Fatalf("file size = %d, want MaxInt64", got.Size)
}
}

func TestInodeAttrs_DirZeroSizeBecomes4096(t *testing.T) {
got := inodeAttrs(0o755, 0, "dir", time.Unix(0, 0))
if got.Size != 4096 {
t.Fatalf("dir size = %d, want 4096", got.Size)
}
}

func TestInodeAttrs_DirNegativeSizeAlsoBecomes4096(t *testing.T) {
// Negative clamps to 0, which the dir branch then upgrades to 4096.
got := inodeAttrs(0o755, -1, "dir", time.Unix(0, 0))
if got.Size != 4096 {
t.Fatalf("dir size = %d, want 4096", got.Size)
}
}

func TestInodeAttrs_SymlinkModeBitSet(t *testing.T) {
got := inodeAttrs(0o777, 16, "symlink", time.Unix(0, 0))
if got.Mode&os.ModeSymlink == 0 {
t.Fatalf("symlink mode bit not set in %v", got.Mode)
}
if got.Size != 16 {
t.Fatalf("symlink size = %d, want 16", got.Size)
}
}

func TestInodeAttrs_DefaultFileModeWhenZero(t *testing.T) {
got := inodeAttrs(0, 1, "file", time.Unix(0, 0))
if got.Mode.Perm() != 0o644 {
t.Fatalf("file default perm = %v, want 0644", got.Mode.Perm())
}
}

func TestInodeAttrs_DefaultDirModeWhenZero(t *testing.T) {
got := inodeAttrs(0, 1, "dir", time.Unix(0, 0))
if got.Mode.Perm() != 0o755 {
t.Fatalf("dir default perm = %v, want 0755", got.Mode.Perm())
}
if got.Mode&os.ModeDir == 0 {
t.Fatalf("dir mode bit not set in %v", got.Mode)
}
}