Skip to content

[stable34] fix(files_external): S3 folder mtime updated on every read-only access#61977

Merged
AndyScherzinger merged 4 commits into
stable34from
backport/59539/stable34
Jul 14, 2026
Merged

[stable34] fix(files_external): S3 folder mtime updated on every read-only access#61977
AndyScherzinger merged 4 commits into
stable34from
backport/59539/stable34

Conversation

@backportbot

@backportbot backportbot Bot commented Jul 10, 2026

Copy link
Copy Markdown

Backport of PR #59539

Antreesy added 4 commits July 10, 2026 15:10
normalizePath('') returns '.' for S3 object keys, but the filecache stores the
storage root under the key ''. getDirectoryMetaData() was calling getCache()->get('.')
which looks up by md5('.'), never matching the root entry stored at md5('').

The cache miss caused getDirectoryMetaData to return synthetic data with time() as
mtime/storage_mtime and uniqid() as etag on every call. The scanner then saw a
storage_mtime mismatch and wrote the fabricated timestamps back to the cache on every
occ files:scan run, regardless of whether any S3 content had changed.

Introduce getCachePath() to translate the normalized root path '.' back to '' before
any filecache lookup.

Assisted-by: ClaudeCode:claude-sonnet-4-6
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
…rs it

Common::getMetaData (Common.php:667) always sets storage_mtime = mtime before
returning. For S3 virtual directories this is wrong: mtime can be bumped by
child propagation while storage_mtime should remain the actual last S3 change.

When the scanner later calls getMetaData() it compares data['storage_mtime']
against cacheData['storage_mtime']. If they differ it writes the value back,
triggering View::getCacheEntry to fire propagateChange on every read even when
nothing on S3 changed.

Override getMetaData() to restore storage_mtime from the live cache entry (or,
for non-root directories not yet in the cache, from the S3 directory marker
LastModified header) after the parent call.

Assisted-by: ClaudeCode:claude-sonnet-4-6
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
…changed

Storage backends like AmazonS3 whose hasUpdated() always returns true (S3 has
no cheap global change-detection mechanism) caused propagateChange() to be called
on every watcher->update() after a folder was browsed, even when the underlying
storage_mtime and etag were identical to the cached values.

Before the fix, getCacheEntry() would call watcher->update() and then
unconditionally call propagateChange() whenever the watcher reported a change.
For S3 directories this meant every read bumped the parent mtime chain.

After the fix, getCacheEntry() snapshots the cache entry before watcher->update()
and compares it with the entry after. propagateChange() is only invoked when at
least one metadata field (mtime, storage_mtime, size, or etag) actually changed.

Assisted-by: ClaudeCode:claude-sonnet-4-6
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
Add null-safety checks to handle S3 responses that don't include
LastModified and ETag fields. This prevents 'Undefined array key'
warnings and deprecation notices when processing directory metadata
or incomplete S3 responses.

- objectToMetaData(): Check if LastModified/ETag exist before accessing
- getMetaData(): Check if LastModified exists before using in strtotime()

Fixes test failures in testStat where hasUpdated('/', time) would fail
when encountering S3 objects without complete metadata.

Assisted-by: ClaudeCode:claude-sonnet-4-6
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
@backportbot backportbot Bot requested a review from a team as a code owner July 10, 2026 15:10
@backportbot backportbot Bot added bug 3. to review Waiting for reviews feature: external storage hotspot: file time handling ctime, mtime, etc. handling during various operations AI assisted labels Jul 10, 2026
@backportbot backportbot Bot added this to the Nextcloud 34.0.2 milestone Jul 10, 2026

@Antreesy Antreesy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested (docker-dev, rust-fs as external S3)

@AndyScherzinger AndyScherzinger merged commit 86c2c73 into stable34 Jul 14, 2026
206 of 238 checks passed
@AndyScherzinger AndyScherzinger deleted the backport/59539/stable34 branch July 14, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews AI assisted bug feature: external storage hotspot: file time handling ctime, mtime, etc. handling during various operations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants