Skip to content

watchfs: possible metadata coruption after upload #1763

@rhafer

Description

@rhafer

Describe the bug

With STORAGE_USERS_POSIX_WATCH_FS=true, when a user changes a file on-disk while a file with the same name is upload via WebDAV and still being stuck in post-processing the attributes of the file can get corrupted.

The easiest way to reproduce this is to run the features/collaborativePosix/collaborativePosixFS.feature:55 with a prolonged POSTPROCESSING_DELAY.

Steps to reproduce

  1. Start the server with ocwrapper and an increase postprocessing delay: POSTPROCESSING_DELAY=10s tests/ocwrapper/bin/ocwrapper serve --bin=/work/opencloud/opencloud/bin/opencloud
  2. run the above mentioned test: TEST_SERVER_URL="https://localhost:9200" BEHAT_FEATURE=tests/acceptance/features/collaborativePosix/collaborativePosixFS.feature:55 make test-acceptance-api
  3. Test fails

The test upload a test file with the content contentnew via webdav, then tries to append the string new via normal via system filesystem operations. And finally tries to verify the updated content again via webdav. The content returned via webdav is now con (❗).
Looking into the file on disk shows that the content is actually content (the original uploaded content), but the metadata of the file has the wrong size:

xattr -l .....
...
user.oc.blobid: f524fa38-1c8a-4eab-800f-a7e62bc7ffd3
user.oc.mtime: 2025-11-03T11:30:43.300589087Z
user.oc.parentid: 9d8f3ec4-a587-41c9-a179-8e09fdf44f89
user.oc.blobsize: 3
user.oc.id: 784bca5f-6d9a-4bb8-92ce-5dda1c2cee0c
user.oc.type: 1
user.oc.name: test.txt

The problem is also reproducible when running the above steps manually. (The manual update on disk needs to happen before the POSTPROCESSING finished)

Expected behavior

The file metadata should match the file content, whether the expected content is content or contentnew depends on whether we change happens before or during postprocessing. I'd argue that if the change happens while the file is still being processed it is ok to have the content reverted to content as long as the metadata stays consistent.

Actual behavior

Metadata and actually file disagree about the length.

test output:

Feature: create a resources using collaborative posixfs

  Background:                                                              # /work/opencloud/tests/acceptance/features/collaborativePosix/collaborativePosixFS.feature:4
    Given the config "STORAGE_USERS_POSIX_WATCH_FS" has been set to "true" # OcConfigContext::theConfigHasBeenSetTo()
    And user "Alice" has been created with default attributes              # FeatureContext::userHasBeenCreatedWithDefaultAttributes()
    And user "Alice" has created folder "/firstFolder"                     # FeatureContext::userHasCreatedFolder()

  Scenario: edit file                                                                                                  # /work/opencloud/tests/acceptance/features/collaborativePosix/collaborativePosixFS.feature:55
    Given user "Alice" has uploaded file with content "content" to "test.txt"                                          # FeatureContext::userHasUploadedAFileWithContentTo()
    When the administrator puts the content "new" into the file "test.txt" in the POSIX storage folder of user "Alice" # CliContext::theAdministratorChangesFileContent()
    Then the content of file "/test.txt" for user "Alice" should be "contentnew"                                       # FeatureContext::contentOfFileForUserShouldBe()
      │ ### RESPONSE
      │ Status: 425
      │ Headers:
      │ Content-Length: 0
      │ Content-Security-Policy: child-src 'self'; connect-src 'self' blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; default-src 'none'; font-src 'self'; frame-ancestors 'self'; frame-src 'self' blob: https://embed.diagrams.net/; img-src 'self' data: blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; manifest-src 'self'; media-src 'self'; object-src 'self' blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
      │ Date: Mon, 03 Nov 2025 12:39:10 GMT
      │ Referrer-Policy: strict-origin-when-cross-origin
      │ Strict-Transport-Security: max-age=315360000; preload
      │ Vary: Origin
      │ X-Content-Type-Options: nosniff
      │ X-Frame-Options: SAMEORIGIN
      │ X-Permitted-Cross-Domain-Policies: none
      │ X-Request-Id: aaa0fe5e9d2a/MRPx71Ttfw-000032
      │ X-Robots-Tag: none
      │ X-Xss-Protection: 1; mode=block
      │ Body:
      │ string(0) ""

      │ ### END RESPONSE
      │ ### RESPONSE
      │ Status: 425
      │ Headers:
      │ Content-Length: 0
      │ Content-Security-Policy: child-src 'self'; connect-src 'self' blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; default-src 'none'; font-src 'self'; frame-ancestors 'self'; frame-src 'self' blob: https://embed.diagrams.net/; img-src 'self' data: blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; manifest-src 'self'; media-src 'self'; object-src 'self' blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
      │ Date: Mon, 03 Nov 2025 12:39:11 GMT
      │ Referrer-Policy: strict-origin-when-cross-origin
      │ Strict-Transport-Security: max-age=315360000; preload
      │ Vary: Origin
      │ X-Content-Type-Options: nosniff
      │ X-Frame-Options: SAMEORIGIN
      │ X-Permitted-Cross-Domain-Policies: none
      │ X-Request-Id: aaa0fe5e9d2a/MRPx71Ttfw-000034
      │ X-Robots-Tag: none
      │ X-Xss-Protection: 1; mode=block
      │ Body:
      │ string(0) ""

      │ ### END RESPONSE
      │ ### RESPONSE
      │ Status: 425
      │ Headers:
      │ Content-Length: 0
      │ Content-Security-Policy: child-src 'self'; connect-src 'self' blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; default-src 'none'; font-src 'self'; frame-ancestors 'self'; frame-src 'self' blob: https://embed.diagrams.net/; img-src 'self' data: blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; manifest-src 'self'; media-src 'self'; object-src 'self' blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
      │ Date: Mon, 03 Nov 2025 12:39:12 GMT
      │ Referrer-Policy: strict-origin-when-cross-origin
      │ Strict-Transport-Security: max-age=315360000; preload
      │ Vary: Origin
      │ X-Content-Type-Options: nosniff
      │ X-Frame-Options: SAMEORIGIN
      │ X-Permitted-Cross-Domain-Policies: none
      │ X-Request-Id: aaa0fe5e9d2a/MRPx71Ttfw-000036
      │ X-Robots-Tag: none
      │ X-Xss-Protection: 1; mode=block
      │ Body:
      │ string(0) ""

      │ ### END RESPONSE
      │ ### RESPONSE
      │ Status: 425
      │ Headers:
      │ Content-Length: 0
      │ Content-Security-Policy: child-src 'self'; connect-src 'self' blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; default-src 'none'; font-src 'self'; frame-ancestors 'self'; frame-src 'self' blob: https://embed.diagrams.net/; img-src 'self' data: blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; manifest-src 'self'; media-src 'self'; object-src 'self' blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
      │ Date: Mon, 03 Nov 2025 12:39:13 GMT
      │ Referrer-Policy: strict-origin-when-cross-origin
      │ Strict-Transport-Security: max-age=315360000; preload
      │ Vary: Origin
      │ X-Content-Type-Options: nosniff
      │ X-Frame-Options: SAMEORIGIN
      │ X-Permitted-Cross-Domain-Policies: none
      │ X-Request-Id: aaa0fe5e9d2a/MRPx71Ttfw-000038
      │ X-Robots-Tag: none
      │ X-Xss-Protection: 1; mode=block
      │ Body:
      │ string(0) ""

      │ ### END RESPONSE
      │ ### RESPONSE
      │ Status: 425
      │ Headers:
      │ Content-Length: 0
      │ Content-Security-Policy: child-src 'self'; connect-src 'self' blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; default-src 'none'; font-src 'self'; frame-ancestors 'self'; frame-src 'self' blob: https://embed.diagrams.net/; img-src 'self' data: blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; manifest-src 'self'; media-src 'self'; object-src 'self' blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
      │ Date: Mon, 03 Nov 2025 12:39:14 GMT
      │ Referrer-Policy: strict-origin-when-cross-origin
      │ Strict-Transport-Security: max-age=315360000; preload
      │ Vary: Origin
      │ X-Content-Type-Options: nosniff
      │ X-Frame-Options: SAMEORIGIN
      │ X-Permitted-Cross-Domain-Policies: none
      │ X-Request-Id: aaa0fe5e9d2a/MRPx71Ttfw-000040
      │ X-Robots-Tag: none
      │ X-Xss-Protection: 1; mode=block
      │ Body:
      │ string(0) ""

      │ ### END RESPONSE
      │ ### RESPONSE
      │ Status: 425
      │ Headers:
      │ Content-Length: 0
      │ Content-Security-Policy: child-src 'self'; connect-src 'self' blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; default-src 'none'; font-src 'self'; frame-ancestors 'self'; frame-src 'self' blob: https://embed.diagrams.net/; img-src 'self' data: blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; manifest-src 'self'; media-src 'self'; object-src 'self' blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
      │ Date: Mon, 03 Nov 2025 12:39:15 GMT
      │ Referrer-Policy: strict-origin-when-cross-origin
      │ Strict-Transport-Security: max-age=315360000; preload
      │ Vary: Origin
      │ X-Content-Type-Options: nosniff
      │ X-Frame-Options: SAMEORIGIN
      │ X-Permitted-Cross-Domain-Policies: none
      │ X-Request-Id: aaa0fe5e9d2a/MRPx71Ttfw-000042
      │ X-Robots-Tag: none
      │ X-Xss-Protection: 1; mode=block
      │ Body:
      │ string(0) ""

      │ ### END RESPONSE
      │ ### RESPONSE
      │ Status: 425
      │ Headers:
      │ Content-Length: 0
      │ Content-Security-Policy: child-src 'self'; connect-src 'self' blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; default-src 'none'; font-src 'self'; frame-ancestors 'self'; frame-src 'self' blob: https://embed.diagrams.net/; img-src 'self' data: blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; manifest-src 'self'; media-src 'self'; object-src 'self' blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
      │ Date: Mon, 03 Nov 2025 12:39:16 GMT
      │ Referrer-Policy: strict-origin-when-cross-origin
      │ Strict-Transport-Security: max-age=315360000; preload
      │ Vary: Origin
      │ X-Content-Type-Options: nosniff
      │ X-Frame-Options: SAMEORIGIN
      │ X-Permitted-Cross-Domain-Policies: none
      │ X-Request-Id: aaa0fe5e9d2a/MRPx71Ttfw-000044
      │ X-Robots-Tag: none
      │ X-Xss-Protection: 1; mode=block
      │ Body:
      │ string(0) ""

      │ ### END RESPONSE
      │ ### RESPONSE
      │ Status: 425
      │ Headers:
      │ Content-Length: 0
      │ Content-Security-Policy: child-src 'self'; connect-src 'self' blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; default-src 'none'; font-src 'self'; frame-ancestors 'self'; frame-src 'self' blob: https://embed.diagrams.net/; img-src 'self' data: blob: https://raw.githubusercontent.com/opencloud-eu/awesome-apps/; manifest-src 'self'; media-src 'self'; object-src 'self' blob:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
      │ Date: Mon, 03 Nov 2025 12:39:17 GMT
      │ Referrer-Policy: strict-origin-when-cross-origin
      │ Strict-Transport-Security: max-age=315360000; preload
      │ Vary: Origin
      │ X-Content-Type-Options: nosniff
      │ X-Frame-Options: SAMEORIGIN
      │ X-Permitted-Cross-Domain-Policies: none
      │ X-Request-Id: aaa0fe5e9d2a/MRPx71Ttfw-000046
      │ X-Robots-Tag: none
      │ X-Xss-Protection: 1; mode=block
      │ Body:
      │ string(0) ""

      │ ### END RESPONSE

      The content was expected to be 'contentnew', but actually is 'con'. HTTP status was 200
      Failed asserting that two strings are equal.
      --- Expected
      +++ Actual
      @@ @@
      -'contentnew'
      +'con'

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions