Skip to content

Commit cb84d83

Browse files
yasithdevclaude
andcommitted
fix: convert Go FileMode to Unix mode_t for FUSE attributes
Go's os.FileMode uses a different bit layout than Unix mode_t: ModeDir is 1<<31 in Go but S_IFDIR is 0040000 in Unix. When populating fuse.Attr.Mode, the raw uint32 cast preserved Go's layout, causing FUSE to see directories as regular files. Added goModeToUnix() that properly maps all Go file type bits (dir, symlink, pipe, socket, device) and special bits (setuid, setgid, sticky) to their Unix/syscall equivalents. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c64332d commit cb84d83

File tree

1 file changed

+44
-10
lines changed

1 file changed

+44
-10
lines changed

subsystems/mount/overlayfs.go

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,42 @@ func (n *overlayNode) locateChild(name string) source {
342342
}
343343
}
344344

345+
// goModeToUnix converts Go's os.FileMode to a Unix mode_t (uint32).
346+
// Go uses its own bit layout (e.g. ModeDir = 1<<31) which differs from
347+
// Unix/FUSE mode bits (S_IFDIR = 0040000). This must be used whenever
348+
// populating fuse.Attr.Mode, fuse.DirEntry.Mode, or fs.StableAttr.Mode.
349+
func goModeToUnix(m os.FileMode) uint32 {
350+
perm := uint32(m.Perm())
351+
switch {
352+
case m.IsDir():
353+
perm |= syscall.S_IFDIR
354+
case m&os.ModeSymlink != 0:
355+
perm |= syscall.S_IFLNK
356+
case m&os.ModeNamedPipe != 0:
357+
perm |= syscall.S_IFIFO
358+
case m&os.ModeSocket != 0:
359+
perm |= syscall.S_IFSOCK
360+
case m&os.ModeCharDevice != 0:
361+
perm |= syscall.S_IFCHR
362+
case m&os.ModeDevice != 0:
363+
perm |= syscall.S_IFBLK
364+
default:
365+
perm |= syscall.S_IFREG
366+
}
367+
if m&os.ModeSetuid != 0 {
368+
perm |= syscall.S_ISUID
369+
}
370+
if m&os.ModeSetgid != 0 {
371+
perm |= syscall.S_ISGID
372+
}
373+
if m&os.ModeSticky != 0 {
374+
perm |= syscall.S_ISVTX
375+
}
376+
return perm
377+
}
378+
345379
func localAttrToFuse(info os.FileInfo, out *fuse.Attr) {
346-
out.Mode = uint32(info.Mode())
380+
out.Mode = goModeToUnix(info.Mode())
347381
out.Size = uint64(info.Size())
348382
out.Mtime = uint64(info.ModTime().Unix())
349383
fillStatFields(info, out)
@@ -352,14 +386,14 @@ func localAttrToFuse(info os.FileInfo, out *fuse.Attr) {
352386
func sftpAttrToFuse(info os.FileInfo, out *fuse.Attr) {
353387
s := info.Sys()
354388
if stat, ok := s.(*sftp.FileStat); ok {
355-
out.Mode = uint32(info.Mode())
389+
out.Mode = goModeToUnix(info.Mode())
356390
out.Size = uint64(info.Size())
357391
out.Mtime = uint64(stat.Mtime)
358392
out.Atime = uint64(stat.Atime)
359393
out.Uid = stat.UID
360394
out.Gid = stat.GID
361395
} else {
362-
out.Mode = uint32(info.Mode())
396+
out.Mode = goModeToUnix(info.Mode())
363397
out.Size = uint64(info.Size())
364398
out.Mtime = uint64(info.ModTime().Unix())
365399
out.Atime = out.Mtime
@@ -387,13 +421,13 @@ func (n *overlayNode) Lookup(ctx context.Context, name string, out *fuse.EntryOu
387421
// Check upper first
388422
if info, err := os.Lstat(up); err == nil {
389423
localAttrToFuse(info, &out.Attr)
390-
stable := fs.StableAttr{Mode: uint32(info.Mode())}
424+
stable := fs.StableAttr{Mode: goModeToUnix(info.Mode())}
391425
return n.NewInode(ctx, child, stable), fs.OK
392426
}
393427
// Fall back to lower
394428
if info, err := n.sftpLstat(rp); err == nil {
395429
sftpAttrToFuse(info, &out.Attr)
396-
stable := fs.StableAttr{Mode: uint32(info.Mode())}
430+
stable := fs.StableAttr{Mode: goModeToUnix(info.Mode())}
397431
return n.NewInode(ctx, child, stable), fs.OK
398432
}
399433
return nil, syscall.ENOENT
@@ -413,7 +447,7 @@ func (n *overlayNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno)
413447
seen[e.Name()] = struct{}{}
414448
result = append(result, fuse.DirEntry{
415449
Name: e.Name(),
416-
Mode: uint32(info.Mode()),
450+
Mode: goModeToUnix(info.Mode()),
417451
})
418452
}
419453
}
@@ -431,7 +465,7 @@ func (n *overlayNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno)
431465
}
432466
result = append(result, fuse.DirEntry{
433467
Name: e.Name(),
434-
Mode: uint32(e.Mode()),
468+
Mode: goModeToUnix(e.Mode()),
435469
})
436470
}
437471
return nil
@@ -563,7 +597,7 @@ func (n *overlayNode) Create(ctx context.Context, name string, flags uint32, mod
563597
localAttrToFuse(info, &out.Attr)
564598

565599
child := n.child(name)
566-
stable := fs.StableAttr{Mode: uint32(info.Mode())}
600+
stable := fs.StableAttr{Mode: goModeToUnix(info.Mode())}
567601
inode := n.NewInode(ctx, child, stable)
568602
return inode, &localFileHandle{f: f}, fuse.FOPEN_DIRECT_IO, fs.OK
569603
}
@@ -581,7 +615,7 @@ func (n *overlayNode) Mkdir(ctx context.Context, name string, mode uint32, out *
581615
localAttrToFuse(info, &out.Attr)
582616

583617
child := n.child(name)
584-
stable := fs.StableAttr{Mode: uint32(info.Mode())}
618+
stable := fs.StableAttr{Mode: goModeToUnix(info.Mode())}
585619
return n.NewInode(ctx, child, stable), fs.OK
586620
}
587621

@@ -684,7 +718,7 @@ func (n *overlayNode) Symlink(ctx context.Context, target, name string, out *fus
684718
localAttrToFuse(info, &out.Attr)
685719

686720
child := n.child(name)
687-
stable := fs.StableAttr{Mode: uint32(info.Mode())}
721+
stable := fs.StableAttr{Mode: goModeToUnix(info.Mode())}
688722
return n.NewInode(ctx, child, stable), fs.OK
689723
}
690724

0 commit comments

Comments
 (0)