Skip to content

Re-shared external storage (SMB) not always rescanned for recipients #63253

Description

@pdostal

Steps to reproduce

  1. User A adds an SMB external storage mount.
  2. User A shares a folder from that mount directly with User B.
  3. Something changes on the SMB server outside of Nextcloud.
  4. User A browses the folder → gets rescanned correctly.
  5. User B browses the re-share → does not get rescanned (stale listing).

Root cause

SharedStorage::getWatcher() decides between a real Watcher and a
NullWatcher purely from string metadata on the share's cache entry:

https://github.com/nextcloud/server/blob/master/apps/files_sharing/lib/SharedStorage.php#L453-L477

$node = $this->getShare()->getNodeCacheEntry();
if ($node instanceof CacheEntry) {
    $storageId = $node->getData()['storage_string_id'] ?? null;
    if ($storageId !== null && !(str_starts_with($storageId, 'home::') || str_starts_with($storageId, 'object::user'))) {
        // real watcher, rescans happen
    }
}
// falls through here -> NullWatcher, rescans never happen
$this->watcher = new NullWatcher();

getNodeCacheEntry() returns null, or a CacheEntry missing the
storage_string_id key, for several legitimate code paths:

  • DefaultShareProvider::getSharesBy() / getShareById() / getSharesByPath()
    never populate a node cache entry at all (no filecache/storages join).
  • Even DefaultShareProvider::_getSharedWith() (used for the normal
    getMountsForUser/getMountsForPath mount setup) can come back without
    storage_string_id if the source node wasn't in filecache yet at
    share-fetch time.

Whenever that happens, the code silently assumes "home storage" and installs
a NullWatcher, meaning the share never gets checked against the real
backend for the lifetime of that storage object/request.

This is the same fragile spot as #50235, which was patched defensively in
#50769 (?? null to avoid the crash) — that fixed the exception but left the
underlying "silently never rescan" behavior in place.

Suggested fix

Fall back to a live, deterministic check of the real underlying storage
($this->nonMaskedStorage->instanceOfStorage(IHomeStorage::class), after
$this->init()) when the cache-entry metadata is missing or incomplete,
instead of assuming home storage. This mirrors the existing fallback pattern
in SharedStorage::getSourceRootInfo(). A FailedStorage guard is needed so
a deleted owner / offline storage still safely yields NullWatcher.

I'm happy to submit a PR for this if the approach sounds right.

This issue has been created in cooperation with Claude Sonnet 5 but it has been reviewed and submitted by me - human.

Metadata

Metadata

Assignees

No one assigned

    Labels

    0. Needs triagePending check for reproducibility or if it fits our roadmapenhancement

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions