Repository: nextcloud/desktop (C++)

Issue:
[Bug]: Desktop Client updates inode status timestamp
### ⚠️ Before submitting, please verify the following: ⚠️

- [X] This is a **bug**, not a question or a configuration issue.
- [X] This issue is **not** already reported on Github (I've searched it).
- [X] Nextcloud Server and Desktop Client are **up to date**. See [Server Maintenance and Release Schedule](https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule) and [Desktop Releases](https://nextcloud.com/install/#install-clients) for supported versions.
- [X] I agree to follow Nextcloud's [Code of Conduct](https://nextcloud.com/contribute/code-of-conduct/)

### Bug description

During synchronization scan, the inode status timestamp (`st_ctimespec`) is updated.
This behaviour confuses applications that currently have access to the file since they can "believe" that the file has changed, the owner has changed, etc. 
<img width="2447" alt="nextcloud-stat" src="https://github.com/nextcloud/desktop/assets/18702470/b147bc2e-b9a8-43c9-83c8-8c64f0acd862">


### Steps to reproduce

Within a terminal:

1. `> touch foo`
2. `> stat foo`
3. _wait for the next scan_
4. `> stat foo`
5. Observation: the `st_ctimespec` timestamps differ.


### Expected behavior

The `st_ctimespec` timestamp should remain unchanged.

### Which files are affected by this bug

As far I examined: any

### Operating system

Mac OS

### Which version of the operating system you are running.

Ventura 13.4.1 (intel)

### Package

Appimage

### Nextcloud Server version

25.0.7 Enterprise

### Nextcloud Desktop Client version

3.9.0

### Is this bug present after an update or on a fresh install?

Updated to a major version (ex. 3.3.6 to 3.4.0)

### Are you using the Nextcloud Server Encryption module?

Encryption is Disabled

### Are you using an external user-backend?

- [ ] Default internal user-backend
- [ ] LDAP/ Active Directory
- [X] SSO - SAML
- [ ] Other

### Nextcloud Server logs

_No response_

### Additional info

_No response_



Fix (patch):
diff --git a/src/common/filesystembase.cpp b/src/common/filesystembase.cpp
index 89c6426921938..ef9ba1efd6778 100644
--- a/src/common/filesystembase.cpp
+++ b/src/common/filesystembase.cpp
@@ -114,17 +114,17 @@ void FileSystem::setFolderMinimumPermissions(const QString &filename)
 #endif
 }
 
-
-void FileSystem::setFileReadOnlyWeak(const QString &filename, bool readonly)
+bool FileSystem::setFileReadOnlyWeak(const QString &filename, bool readonly)
 {
     QFile file(filename);
     QFile::Permissions permissions = file.permissions();
 
     if (!readonly && (permissions & QFile::WriteOwner)) {
-        return; // already writable enough
+        return false; // already writable enough
     }
 
     setFileReadOnly(filename, readonly);
+    return true;
 }
 
 bool FileSystem::rename(const QString &originFileName,
diff --git a/src/common/filesystembase.h b/src/common/filesystembase.h
index bc0b592c23aff..fd0572804dcb2 100644
--- a/src/common/filesystembase.h
+++ b/src/common/filesystembase.h
@@ -65,7 +65,7 @@ namespace FileSystem {
      * This means that it will preserve explicitly set rw-r--r-- permissions even
      * when the umask is 0002. (setFileReadOnly() would adjust to rw-rw-r--)
      */
-    void OCSYNC_EXPORT setFileReadOnlyWeak(const QString &filename, bool readonly);
+    bool OCSYNC_EXPORT setFileReadOnlyWeak(const QString &filename, bool readonly);
 
     /**
      * @brief Try to set permissions so that other users on the local machine can not
diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp
index 52985fd4454ab..656689a550e60 100644
--- a/src/libsync/syncengine.cpp
+++ b/src/libsync/syncengine.cpp
@@ -357,6 +357,7 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
         // mini-jobs later on, we just update metadata right now.
 
         if (item->_direction == SyncFileItem::Down) {
+            auto modificationHappened = false; // Decides whether or not we modify file metadata
             QString filePath = _localPath + item->_file;
 
             // If the 'W' remote permission changed, update the local filesystem
@@ -365,15 +366,18 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
                 && prev.isValid()
                 && prev._remotePerm.hasPermission(RemotePermissions::CanWrite) != item->_remotePerm.hasPermission(RemotePermissions::CanWrite)) {
                 const bool isReadOnly = !item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite);
-                FileSystem::setFileReadOnlyWeak(filePath, isReadOnly);
+                modificationHappened = FileSystem::setFileReadOnlyWeak(filePath, isReadOnly);
             }
+
+            modificationHappened |= item->_size != prev._fileSize;
+
             auto rec = item->toSyncJournalFileRecordWithInode(filePath);
             if (rec._checksumHeader.isEmpty())
                 rec._checksumHeader = prev._checksumHeader;
             rec._serverHasIgnoredFiles |= prev._serverHasIgnoredFiles;
 
             // Ensure it's a placeholder file on disk
-            if (item->_type == ItemTypeFile) {
+            if (item->_type == ItemTypeFile && _syncOptions._vfs->mode() != Vfs::Off) {
                 const auto result = _syncOptions._vfs->convertToPlaceholder(filePath, *item);
                 if (!result) {
                     item->_status = SyncFileItem::Status::NormalError;
@@ -382,10 +386,11 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
                     emit itemCompleted(item, ErrorCategory::GenericError);
                     return;
                 }
+                modificationHappened = true;
             }
 
             // Update on-disk virtual file metadata
-            if (item->_type == ItemTypeVirtualFile) {
+            if (modificationHappened && item->_type == ItemTypeVirtualFile) {
                 auto r = _syncOptions._vfs->updateMetadata(filePath, item->_modtime, item->_size, item->_fileId);
                 if (!r) {
                     item->_status = SyncFileItem::Status::NormalError;
@@ -394,7 +399,7 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
                     emit itemCompleted(item, ErrorCategory::GenericError);
                     return;
                 }
-            } else {
+            } else if (modificationHappened || prev._modtime != item->_modtime) {
                 if (!FileSystem::setModTime(filePath, item->_modtime)) {
                     item->_instruction = CSYNC_INSTRUCTION_ERROR;
                     item->_errorString = tr("Could not update file metadata: %1").arg(filePath);