Skip to content
Open
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
9 changes: 7 additions & 2 deletions fsmount/internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ func (f FuseFlags) IsEventOnly() bool {
return int(f)&O_EVTONLY != 0
}

// IsReadOnly checks if the flag is set to read-only.
// IsReadOnly reports whether the flags carry no write intent: the POSIX
// access mode (low two bits: O_RDONLY=0, O_WRONLY=1, O_RDWR=2) is O_RDONLY
// and no bit implying modification (O_CREAT, O_TRUNC, O_APPEND) is set.
// Kernels set unrelated bits on read opens (e.g. Linux forces
// O_LARGEFILE=0x8000 from 64-bit userspace), so the full flag word must not
// be compared against zero.
func (f FuseFlags) IsReadOnly() bool {
return int(f) == 0
return int(f)&0x3 == 0 && !f.IsCreate() && !f.IsTruncate() && !f.IsAppend()
}

// IsWriteOnly checks if the flag is set to write-only.
Expand Down
13 changes: 13 additions & 0 deletions fsmount/internal/flags/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ func TestFuseFlags(t *testing.T) {
expectedCreateExclusive: true,
expectedString: "FuseFlags{O_CREAT|O_EXCL}",
},
{
name: "ReadOnlyWithKernelSetBits",
flags: fuse.O_RDONLY | 0x8000, // Linux forces O_LARGEFILE (0x8000) on every 64-bit open
expectedReadOnly: true,
expectedWriteOnly: false,
expectedReadWrite: false,
expectedCreate: false,
expectedExclusive: false,
expectedTruncate: false,
expectedAppend: false,
expectedCreateExclusive: false,
expectedString: "FuseFlags{O_EVTONLY|O_RDONLY}",
},
{
name: "TruncateAppend",
flags: fuse.O_TRUNC | fuse.O_APPEND,
Expand Down
2 changes: 1 addition & 1 deletion fsmount/localfs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func (fs *LocalFs) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) {
fq := fs.fqPath(path)
stgo := syscall.Stat_t{}
if err := syscall.Lstat(path, &stgo); err != nil {
if err := syscall.Lstat(fq, &stgo); err != nil {
if !os.IsNotExist(err) {
fs.log.Trace("LocalFs: Getattr: failed to lstat file: path=%v, fh=%v, err=%v", fq, fh, err)
return -fuse.EIO
Expand Down
8 changes: 7 additions & 1 deletion fsmount/localfs_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ func (fs *LocalFs) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int)
return -fuse.ENOENT
}

stat.Mode = uint32(info.Mode())
// Go's FileMode encodes directories in its own high bits; FUSE consumers
// dispatch on the POSIX S_IFMT bits, so translate explicitly.
if info.IsDir() {
stat.Mode = fuse.S_IFDIR | 0755
} else {
stat.Mode = fuse.S_IFREG | uint32(info.Mode().Perm())
}
stat.Size = int64(info.Size())
node, ok := fs.vfs.fetch(path)
if ok && node.info.uid != 0 {
Expand Down
7 changes: 7 additions & 0 deletions fsmount/open_handles.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ func (h *OpenHandles) startUploadSweeper() {
continue
}
_, bytesWritten, lastActivity := node.uploadStats()
if lastActivity.IsZero() {
// A write session exists but its upload has not started
// (uploads begin at flush/release). Treating the zero
// timestamp as idle time would cancel the live session
// and delete its working copy on the first sweep.
continue
}
idle := time.Since(lastActivity)
h.log.Debug("Upload sweeper: checking upload for path %s, bytes written: %d, last activity: %v, idle time: %v", node.path, bytesWritten, lastActivity, idle)
// Case A: upload opened but never wrote any bytes — allow a long grace period.
Expand Down
17 changes: 13 additions & 4 deletions fsmount/remotefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,16 @@ func (fs *RemoteFs) Init() {
func (fs *RemoteFs) Destroy() {
fs.log.Debug("RemoteFs: Destroy: removing all file locks")

// Snapshot under the mutex, then release it: unlock re-acquires
// lockMapMutex, so unlocking while holding it self-deadlocks.
fs.lockMapMutex.Lock()
defer fs.lockMapMutex.Unlock()
for path, lockInfo := range fs.lockMap {
fs.unlock(path, lockInfo.Fh)
locks := make(map[string]*lockInfo, len(fs.lockMap))
for path, li := range fs.lockMap {
locks[path] = li
}
fs.lockMapMutex.Unlock()
for path, li := range locks {
fs.unlock(path, li.Fh)
}

fs.log.Debug("RemoteFs: Destroy: stopping cache maintenance")
Expand Down Expand Up @@ -2621,7 +2627,10 @@ func (fs *RemoteFs) listDir(path string) (childPaths map[string]struct{}, opErr
childPath := path_lib.Join(path, path_lib.Base(lock.Path))

// Ignore paths where the lock is held by this file system.
if _, ok := fs.lockMap[childPath]; ok {
fs.lockMapMutex.Lock()
_, heldByUs := fs.lockMap[childPath]
fs.lockMapMutex.Unlock()
if heldByUs {
continue
}

Expand Down