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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions