Skip to content

Commit d0ab91e

Browse files
authored
Merge pull request #8095 from nextcloud/bugfix/reset-mtime
fix: try to correct mtime on upsyncs
2 parents 91ada99 + 8cd1bee commit d0ab91e

File tree

11 files changed

+149
-62
lines changed

11 files changed

+149
-62
lines changed

src/common/utility.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,6 @@ namespace Utility {
294294
OCSYNC_EXPORT bool registryWalkValues(HKEY hRootKey, const QString &subKey, const std::function<void(const QString &, bool *)> &callback);
295295
OCSYNC_EXPORT QRect getTaskbarDimensions();
296296

297-
// Possibly refactor to share code with UnixTimevalToFileTime in c_time.c
298-
OCSYNC_EXPORT void UnixTimeToFiletime(time_t t, FILETIME *filetime);
299-
OCSYNC_EXPORT void FiletimeToLargeIntegerFiletime(FILETIME *filetime, LARGE_INTEGER *hundredNSecs);
300297
OCSYNC_EXPORT void UnixTimeToLargeIntegerFiletime(time_t t, LARGE_INTEGER *hundredNSecs);
301298

302299
OCSYNC_EXPORT QString formatWinError(long error);

src/common/utility_win.cpp

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -483,24 +483,9 @@ DWORD Utility::convertSizeToDWORD(size_t &convertVar)
483483
return static_cast<DWORD>(convertVar);
484484
}
485485

486-
void Utility::UnixTimeToFiletime(time_t t, FILETIME *filetime)
487-
{
488-
LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
489-
filetime->dwLowDateTime = (DWORD) ll;
490-
filetime->dwHighDateTime = ll >>32;
491-
}
492-
493-
void Utility::FiletimeToLargeIntegerFiletime(FILETIME *filetime, LARGE_INTEGER *hundredNSecs)
494-
{
495-
hundredNSecs->LowPart = filetime->dwLowDateTime;
496-
hundredNSecs->HighPart = filetime->dwHighDateTime;
497-
}
498-
499486
void Utility::UnixTimeToLargeIntegerFiletime(time_t t, LARGE_INTEGER *hundredNSecs)
500487
{
501-
LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
502-
hundredNSecs->LowPart = (DWORD) ll;
503-
hundredNSecs->HighPart = ll >>32;
488+
hundredNSecs->QuadPart = (t * 10000000LL) + 116444736000000000LL;
504489
}
505490

506491
bool Utility::canCreateFileInPath(const QString &path)

src/csync/std/c_time.cpp

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,43 +25,45 @@
2525
#include <QFile>
2626

2727
#ifdef HAVE_UTIMES
28-
int c_utimes(const QString &uri, const struct timeval *times) {
29-
int ret = utimes(QFile::encodeName(uri).constData(), times);
30-
return ret;
28+
int c_utimes(const QString &uri, const time_t time)
29+
{
30+
struct timeval times[2];
31+
times[0].tv_sec = times[1].tv_sec = time;
32+
times[0].tv_usec = times[1].tv_usec = 0;
33+
return utimes(QFile::encodeName(uri).constData(), times);
3134
}
35+
3236
#else // HAVE_UTIMES
3337

3438
#ifdef _WIN32
35-
// implementation for utimes taken from KDE mingw headers
39+
// based on the implementation for utimes from KDE mingw headers
3640

3741
#include <errno.h>
3842
#include <wtypes.h>
39-
#define CSYNC_SECONDS_SINCE_1601 11644473600LL
40-
#define CSYNC_USEC_IN_SEC 1000000LL
41-
//after Microsoft KB167296
42-
static void UnixTimevalToFileTime(struct timeval t, LPFILETIME pft)
43+
44+
constexpr long long CSYNC_SECONDS_SINCE_1601 = 11644473600LL;
45+
constexpr long long CSYNC_USEC_IN_SEC = 1000000LL;
46+
47+
// after Microsoft KB167296, except it uses a `time_t` instead of a `struct timeval`.
48+
//
49+
// `struct timeval` is defined in the winsock.h header of all places, and its fields are two `long`s,
50+
// which even on x64 Windows is 4 bytes wide (i.e. int32). `time_t` on the other hand is 8 bytes
51+
// wide (int64) on x64 Windows as well.
52+
static void UnixTimeToFiletime(const time_t time, LPFILETIME pft)
4353
{
44-
LONGLONG ll = 0;
45-
ll = Int32x32To64(t.tv_sec, CSYNC_USEC_IN_SEC*10) + t.tv_usec*10 + CSYNC_SECONDS_SINCE_1601*CSYNC_USEC_IN_SEC*10;
54+
LONGLONG ll = time * CSYNC_USEC_IN_SEC * 10 + CSYNC_SECONDS_SINCE_1601 * CSYNC_USEC_IN_SEC * 10;
4655
pft->dwLowDateTime = (DWORD)ll;
4756
pft->dwHighDateTime = ll >> 32;
4857
}
4958

50-
int c_utimes(const QString &uri, const struct timeval *times) {
51-
FILETIME LastAccessTime;
52-
FILETIME LastModificationTime;
59+
int c_utimes(const QString &uri, const time_t time)
60+
{
61+
FILETIME filetime;
5362
HANDLE hFile = nullptr;
5463

5564
auto wuri = uri.toStdWString();
5665

57-
if(times) {
58-
UnixTimevalToFileTime(times[0], &LastAccessTime);
59-
UnixTimevalToFileTime(times[1], &LastModificationTime);
60-
}
61-
else {
62-
GetSystemTimeAsFileTime(&LastAccessTime);
63-
GetSystemTimeAsFileTime(&LastModificationTime);
64-
}
66+
UnixTimeToFiletime(time, &filetime);
6567

6668
hFile=CreateFileW(wuri.data(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
6769
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL+FILE_FLAG_BACKUP_SEMANTICS, nullptr);
@@ -87,7 +89,7 @@ int c_utimes(const QString &uri, const struct timeval *times) {
8789
return -1;
8890
}
8991

90-
if(!SetFileTime(hFile, nullptr, &LastAccessTime, &LastModificationTime)) {
92+
if (!SetFileTime(hFile, nullptr, &filetime, &filetime)) {
9193
//can this happen?
9294
errno=ENOENT;
9395
CloseHandle(hFile);

src/csync/std/c_time.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#include <sys/time.h>
3232
#endif
3333

34-
OCSYNC_EXPORT int c_utimes(const QString &uri, const struct timeval *times);
34+
OCSYNC_EXPORT int c_utimes(const QString &uri, time_t time);
3535

3636

3737
#endif /* _C_TIME_H */

src/libsync/bulkpropagatorjob.cpp

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,17 @@ void BulkPropagatorJob::doStartUpload(SyncFileItemPtr item,
195195

196196
item->_modtime = FileSystem::getModTime(newFilePathAbsolute);
197197
if (item->_modtime <= 0) {
198-
_pendingChecksumFiles.remove(item->_file);
199-
slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
200-
checkPropagationIsDone();
201-
return;
198+
const auto now = QDateTime::currentSecsSinceEpoch();
199+
qCInfo(lcPropagateUpload) << "File" << item->_file << "has invalid modification time of" << item->_modtime << "-- trying to update it to" << now;
200+
if (FileSystem::setModTime(newFilePathAbsolute, now)) {
201+
item->_modtime = now;
202+
} else {
203+
qCWarning(lcPropagateUpload) << "Could not update modification time for" << item->_file;
204+
_pendingChecksumFiles.remove(item->_file);
205+
slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
206+
checkPropagationIsDone();
207+
return;
208+
}
202209
}
203210
}
204211

@@ -325,18 +332,26 @@ void BulkPropagatorJob::slotStartUpload(SyncFileItemPtr item,
325332
return;
326333
}
327334

328-
const auto prevModtime = item->_modtime; // the _item value was set in PropagateUploadFile::start()
335+
const auto prevModtime = item->_modtime; // the item value was set in PropagateUploadFile::start()
329336
// but a potential checksum calculation could have taken some time during which the file could
330337
// have been changed again, so better check again here.
331338

332339
item->_modtime = FileSystem::getModTime(originalFilePath);
340+
qCDebug(lcPropagateUpload) << "fullFilePath" << fullFilePath << "originalFilePath" << originalFilePath << "prevModtime" << prevModtime << "item->_modtime" << item->_modtime;
333341
if (item->_modtime <= 0) {
334-
_pendingChecksumFiles.remove(item->_file);
335-
slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
336-
checkPropagationIsDone();
337-
return;
342+
const auto now = QDateTime::currentSecsSinceEpoch();
343+
qCInfo(lcPropagateUpload) << "File" << item->_file << "has invalid modification time of" << item->_modtime << "-- trying to update it to" << now;
344+
if (FileSystem::setModTime(originalFilePath, now)) {
345+
item->_modtime = now;
346+
} else {
347+
qCWarning(lcPropagateUpload) << "Could not update modification time for" << item->_file;
348+
_pendingChecksumFiles.remove(item->_file);
349+
slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
350+
checkPropagationIsDone();
351+
return;
352+
}
338353
}
339-
if (prevModtime != item->_modtime) {
354+
if (prevModtime > 0 && prevModtime != item->_modtime) {
340355
propagator()->_anotherSyncNeeded = true;
341356
_pendingChecksumFiles.remove(item->_file);
342357

src/libsync/discovery.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
11211121
}
11221122

11231123
if ((item->_direction == SyncFileItem::Down || item->_instruction == CSYNC_INSTRUCTION_CONFLICT || item->_instruction == CSYNC_INSTRUCTION_NEW || item->_instruction == CSYNC_INSTRUCTION_SYNC) &&
1124+
item->_direction != SyncFileItem::Up &&
11241125
(item->_modtime <= 0 || item->_modtime >= 0xFFFFFFFF)) {
11251126
item->_instruction = CSYNC_INSTRUCTION_ERROR;
11261127
item->_errorString = tr("Cannot sync due to invalid modification time");

src/libsync/filesystem.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,7 @@ time_t FileSystem::getModTime(const QString &filename)
196196

197197
bool FileSystem::setModTime(const QString &filename, time_t modTime)
198198
{
199-
struct timeval times[2];
200-
times[0].tv_sec = times[1].tv_sec = modTime;
201-
times[0].tv_usec = times[1].tv_usec = 0;
202-
int rc = c_utimes(filename, times);
199+
int rc = c_utimes(filename, modTime);
203200
if (rc != 0) {
204201
qCWarning(lcFileSystem) << "Error setting mtime for" << filename
205202
<< "failed: rc" << rc << ", errno:" << errno;

src/libsync/propagateupload.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,15 @@ void PropagateUploadFileCommon::slotComputeContentChecksum()
324324
// probably temporary one.
325325
_item->_modtime = FileSystem::getModTime(filePath);
326326
if (_item->_modtime <= 0) {
327-
slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file)));
328-
return;
327+
const auto now = QDateTime::currentSecsSinceEpoch();
328+
qCInfo(lcPropagateUpload) << "File" << _item->_file << "has invalid modification time of" << _item->_modtime << "-- trying to update it to" << now;
329+
if (FileSystem::setModTime(filePath, now)) {
330+
_item->_modtime = now;
331+
} else {
332+
qCWarning(lcPropagateUpload) << "Could not update modification time for" << _item->_file;
333+
slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file)));
334+
return;
335+
}
329336
}
330337

331338
const QByteArray checksumType = propagator()->account()->capabilities().preferredUploadChecksumType();

test/syncenginetestutils.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ FakePutMultiFileReply::FakePutMultiFileReply(FileInfo &remoteRootFileInfo, QNetw
535535

536536
QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload, const QString &contentType)
537537
{
538+
Q_UNUSED(request)
538539
QVector<FileInfo *> result;
539540

540541
auto stringPutPayload = QString::fromUtf8(putPayload);
@@ -564,7 +565,7 @@ QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRoot
564565
// Assume that the file is filled with the same character
565566
fileInfo = remoteRootFileInfo.create(fileName, onePartBody.size(), onePartBody.at(0).toLatin1());
566567
}
567-
fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("x-oc-mtime").toLongLong());
568+
fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(modtime);
568569
remoteRootFileInfo.find(fileName, /*invalidateEtags=*/true);
569570
result.push_back(fileInfo);
570571
}

test/testsyncengine.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,71 @@ private slots:
14111411
QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
14121412
}
14131413

1414+
void testLocalInvalidMtimeCorrection()
1415+
{
1416+
const auto INVALID_MTIME = QDateTime::fromSecsSinceEpoch(0);
1417+
const auto RECENT_MTIME = QDateTime::fromSecsSinceEpoch(1743004783); // 2025-03-26T16:59:43+0100
1418+
1419+
FakeFolder fakeFolder{FileInfo{}};
1420+
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
1421+
1422+
fakeFolder.localModifier().insert(QStringLiteral("invalid"));
1423+
fakeFolder.localModifier().setModTime("invalid", INVALID_MTIME);
1424+
fakeFolder.localModifier().insert(QStringLiteral("recent"));
1425+
fakeFolder.localModifier().setModTime("recent", RECENT_MTIME);
1426+
1427+
QVERIFY(fakeFolder.syncOnce());
1428+
1429+
// "invalid" file had a mtime of 0, so it's been updated to the current time during testing
1430+
const auto currentMtime = fakeFolder.currentLocalState().find("invalid")->lastModified;
1431+
QCOMPARE_GT(currentMtime, RECENT_MTIME);
1432+
QCOMPARE_GT(fakeFolder.currentRemoteState().find("invalid")->lastModified, RECENT_MTIME);
1433+
1434+
// "recent" file had a mtime of RECENT_MTIME, so it shouldn't have been changed
1435+
QCOMPARE(fakeFolder.currentLocalState().find("recent")->lastModified, RECENT_MTIME);
1436+
QCOMPARE(fakeFolder.currentRemoteState().find("recent")->lastModified, RECENT_MTIME);
1437+
1438+
QVERIFY(fakeFolder.syncOnce());
1439+
1440+
// verify that the mtime of "invalid" hasn't changed since the last sync that fixed it
1441+
QCOMPARE(fakeFolder.currentLocalState().find("invalid")->lastModified, currentMtime);
1442+
1443+
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
1444+
}
1445+
1446+
void testLocalInvalidMtimeCorrectionBulkUpload()
1447+
{
1448+
const auto INVALID_MTIME = QDateTime::fromSecsSinceEpoch(0);
1449+
const auto RECENT_MTIME = QDateTime::fromSecsSinceEpoch(1743004783); // 2025-03-26T16:59:43+0100
1450+
1451+
FakeFolder fakeFolder{FileInfo{}};
1452+
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
1453+
fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"bulkupload", "1.0"} } } });
1454+
1455+
fakeFolder.localModifier().insert(QStringLiteral("invalid"));
1456+
fakeFolder.localModifier().setModTime("invalid", INVALID_MTIME);
1457+
fakeFolder.localModifier().insert(QStringLiteral("recent"));
1458+
fakeFolder.localModifier().setModTime("recent", RECENT_MTIME);
1459+
1460+
QVERIFY(fakeFolder.syncOnce()); // this will use the BulkPropagatorJob
1461+
1462+
// "invalid" file had a mtime of 0, so it's been updated to the current time during testing
1463+
const auto currentMtime = fakeFolder.currentLocalState().find("invalid")->lastModified;
1464+
QCOMPARE_GT(currentMtime, RECENT_MTIME);
1465+
QCOMPARE_GT(fakeFolder.currentRemoteState().find("invalid")->lastModified, RECENT_MTIME);
1466+
1467+
// "recent" file had a mtime of RECENT_MTIME, so it shouldn't have been changed
1468+
QCOMPARE(fakeFolder.currentLocalState().find("recent")->lastModified, RECENT_MTIME);
1469+
QCOMPARE(fakeFolder.currentRemoteState().find("recent")->lastModified, RECENT_MTIME);
1470+
1471+
QVERIFY(fakeFolder.syncOnce()); // this will not propagate anything
1472+
1473+
// verify that the mtime of "invalid" hasn't changed since the last sync that fixed it
1474+
QCOMPARE(fakeFolder.currentLocalState().find("invalid")->lastModified, currentMtime);
1475+
1476+
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
1477+
}
1478+
14141479
void testServerUpdatingMTimeShouldNotCreateConflicts()
14151480
{
14161481
constexpr auto testFile = "test.txt";

0 commit comments

Comments
 (0)