Skip to content

Directory cache not invalidated on write operations causes phantom folders on Windows #346

@gregorkrebs

Description

@gregorkrebs

Environment

Component Version
sshfs sshfs-3.7.5
sshfs-win v3.5.20357
WinFsp v2.1
Windows Windows 10/11 (x64)
OpenSSH system default

Problem Description

When using sshfs-win on Windows, the directory cache is not invalidated
after write operations (mkdir, rename, create, unlink).

This causes two distinct failure modes visible in Windows Explorer.

Failure Mode 1: Phantom Folders
Windows Explorer uses a create-then-rename pattern when a user creates
a new folder and types a name:

  1. mkdir("New folder")
  2. rename("New folder" → "MyProject")

Because the parent directory cache is not invalidated after step 2,
a subsequent readdir() returns stale cached data containing "New folder"
instead of "MyProject".

Failure Mode 2: Out-of-Band Writes Not Visible
Files written directly to the remote host (separate SSH session, agent,
CI system) are not reflected in the mounted directory until the cache
expires (~20 seconds) or the filesystem is remounted.

Steps to Reproduce

Phantom Folder:

1. Mount remote host via sshfs-win
2. Open mounted drive in Windows Explorer
3. Right-click → New → Folder
4. Immediately type a name ("MyProject") and press Enter
5. Observe: phantom "New folder" entries appear alongside "MyProject"

Out-of-Band Write:

1. Mount remote host via sshfs-win
2. In a separate SSH session on the remote:
   echo "test" > /remote/path/new_file.txt
3. In Windows Explorer: observe mounted directory (no manual refresh)
4. Observe: new_file.txt not visible until cache expires

Root Cause Analysis

The issue is in sshfs.c. These functions do not invalidate the
parent directory cache entry after successful completion:

Function Line Missing
sshfs_mkdir() 2428 parent cache_invalidate
sshfs_rmdir() 2512 parent cache_invalidate
sshfs_rename() 2559 from+to parent invalidate
sshfs_create() 3397 parent cache_invalidate
sshfs_unlink() 2500 parent cache_invalidate

cache_invalidate() at line 2867 works correctly — it is
simply not called on the parent path after these mutations.

Proposed Fix

static void cache_invalidate_parent(const char *path) {
    char *parent = g_strdup(path);
    char *slash = strrchr(parent, '/');
    if (slash && slash != parent) {
        *slash = '\0';
        cache_invalidate(parent);
    } else {
        cache_invalidate("/");
    }
    g_free(parent);
}

Add cache_invalidate_parent(path) at the end of:

  • sshfs_mkdir() — line 2428
  • sshfs_rmdir() — line 2512
  • sshfs_rename() — both from and to parent paths, line 2559
  • sshfs_create() — line 3397
  • sshfs_unlink() — line 2500
  • sshfs_symlink() — line 2481

I am ready to submit a pull request if this approach is approved.

Workaround

Disable caching entirely via mount options:

sshfs user@host:/path Z: \
  -o attr_timeout=0 \
  -o entry_timeout=0 \
  -o negative_timeout=0 \
  -o dcache_timeout=0

⚠️ This increases SFTP traffic but eliminates stale entries immediately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions