Skip to content

Commit 135faf5

Browse files
authored
Merge pull request #9627 from nextcloud/backport/9566/stable-4.0
[stable-4.0] Bugfix/better handling of locked files during upload
2 parents 180ba03 + 843f4de commit 135faf5

12 files changed

+148
-29
lines changed

src/common/filesystembase.cpp

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -671,32 +671,70 @@ bool FileSystem::moveToTrash(const QString &fileName, QString *errorString)
671671
return true;
672672
}
673673

674-
bool FileSystem::isFileLocked(const QString &fileName)
674+
namespace {
675+
676+
/**
677+
* This function creates a file handle with the desired LockMode
678+
*/
679+
#if defined Q_OS_WIN
680+
Utility::Handle lockFile(const QString &fileName, FileSystem::LockMode mode)
675681
{
676-
#ifdef Q_OS_WIN
682+
const QString fName = FileSystem::longWinPath(fileName);
683+
int shareMode = 0;
684+
int accessMode = GENERIC_READ | GENERIC_WRITE;
685+
switch (mode) {
686+
case FileSystem::LockMode::Exclusive:
687+
shareMode = 0;
688+
break;
689+
case FileSystem::LockMode::Shared:
690+
shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
691+
break;
692+
case FileSystem::LockMode::SharedRead:
693+
shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
694+
accessMode = GENERIC_READ;
695+
break;
696+
}
677697
// Check if file exists
678-
const QString fName = longWinPath(fileName);
679698
DWORD attr = GetFileAttributesW(reinterpret_cast<const wchar_t *>(fName.utf16()));
680699
if (attr != INVALID_FILE_ATTRIBUTES) {
681700
// Try to open the file with as much access as possible..
682-
HANDLE win_h = CreateFileW(
683-
reinterpret_cast<const wchar_t *>(fName.utf16()),
684-
GENERIC_READ | GENERIC_WRITE,
685-
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
686-
nullptr, OPEN_EXISTING,
687-
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
688-
nullptr);
689-
690-
if (win_h == INVALID_HANDLE_VALUE) {
691-
/* could not be opened, so locked? */
692-
/* 32 == ERROR_SHARING_VIOLATION */
701+
auto out = Utility::Handle{CreateFileW(reinterpret_cast<const wchar_t *>(fName.utf16()), accessMode, shareMode, nullptr, OPEN_EXISTING,
702+
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr)};
703+
704+
if (out) {
705+
LARGE_INTEGER start;
706+
start.QuadPart = 0;
707+
LARGE_INTEGER end;
708+
end.QuadPart = -1;
709+
if (LockFile(out.handle(), start.LowPart, start.HighPart, end.LowPart, end.HighPart)) {
710+
return out;
711+
} else {
712+
return {};
713+
}
714+
}
715+
return out;
716+
}
717+
return {};
718+
}
719+
#endif
720+
721+
}
722+
723+
bool FileSystem::isFileLocked(const QString &fileName, LockMode mode)
724+
{
725+
#ifdef Q_OS_WIN
726+
const auto handle = lockFile(fileName, mode);
727+
if (!handle) {
728+
const auto error = GetLastError();
729+
if (error == ERROR_SHARING_VIOLATION || error == ERROR_LOCK_VIOLATION) {
693730
return true;
694-
} else {
695-
CloseHandle(win_h);
731+
} else if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND) {
732+
qCWarning(lcFileSystem()) << Q_FUNC_INFO << Utility::formatWinError(error);
696733
}
697734
}
698735
#else
699736
Q_UNUSED(fileName);
737+
Q_UNUSED(mode);
700738
#endif
701739
return false;
702740
}

src/common/filesystembase.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem)
3535
* @brief This file contains file system helper
3636
*/
3737
namespace FileSystem {
38+
OCSYNC_EXPORT Q_NAMESPACE;
39+
3840
enum class FolderPermissions {
3941
ReadOnly,
4042
ReadWrite,
@@ -178,10 +180,17 @@ namespace FileSystem {
178180
bool OCSYNC_EXPORT setAclPermission(const QString &path, FileSystem::FolderPermissions permissions);
179181
#endif
180182

183+
enum class LockMode {
184+
Shared,
185+
Exclusive,
186+
SharedRead,
187+
};
188+
Q_ENUM_NS(LockMode);
189+
181190
/**
182191
* Returns true when a file is locked. (Windows only)
183192
*/
184-
bool OCSYNC_EXPORT isFileLocked(const QString &fileName);
193+
bool OCSYNC_EXPORT isFileLocked(const QString &fileName, LockMode mode);
185194

186195
/**
187196
* Returns whether the file is a shortcut file (ends with .lnk)

src/common/utility.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,51 @@ namespace Utility {
4949
*/
5050
OCSYNC_EXPORT QVector<ProcessInfosForOpenFile> queryProcessInfosKeepingFileOpen(const QString &filePath);
5151

52+
#ifdef Q_OS_WIN
53+
class OCSYNC_EXPORT Handle
54+
{
55+
public:
56+
/**
57+
* A RAAI for Windows Handles
58+
*/
59+
Handle() = default;
60+
explicit Handle(HANDLE h);
61+
explicit Handle(HANDLE h, std::function<void(HANDLE)> &&close);
62+
63+
Handle(const Handle &) = delete;
64+
Handle &operator=(const Handle &) = delete;
65+
66+
Handle(Handle &&other)
67+
{
68+
std::swap(_handle, other._handle);
69+
std::swap(_close, other._close);
70+
}
71+
72+
Handle &operator=(Handle &&other)
73+
{
74+
if (this != &other) {
75+
std::swap(_handle, other._handle);
76+
std::swap(_close, other._close);
77+
}
78+
return *this;
79+
}
80+
81+
~Handle();
82+
83+
HANDLE &handle() { return _handle; }
84+
85+
void close();
86+
87+
explicit operator bool() const { return _handle != INVALID_HANDLE_VALUE; }
88+
89+
operator HANDLE() const { return _handle; }
90+
91+
private:
92+
HANDLE _handle = INVALID_HANDLE_VALUE;
93+
std::function<void(HANDLE)> _close;
94+
};
95+
#endif
96+
5297
OCSYNC_EXPORT int rand();
5398
OCSYNC_EXPORT void sleep(int sec);
5499
OCSYNC_EXPORT void usleep(int usec);

src/common/utility_win.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,4 +609,29 @@ void Utility::LocalFreeDeleter::operator()(void *p) const
609609
::LocalFree(reinterpret_cast<HLOCAL>(p));
610610
}
611611

612+
Utility::Handle::Handle(HANDLE h, std::function<void(HANDLE)> &&close)
613+
: _handle(h)
614+
, _close(std::move(close))
615+
{
616+
}
617+
618+
Utility::Handle::Handle(HANDLE h)
619+
: _handle(h)
620+
, _close(&CloseHandle)
621+
{
622+
}
623+
624+
Utility::Handle::~Handle()
625+
{
626+
close();
627+
}
628+
629+
void Utility::Handle::close()
630+
{
631+
if (_handle != INVALID_HANDLE_VALUE) {
632+
_close(_handle);
633+
_handle = INVALID_HANDLE_VALUE;
634+
}
635+
}
636+
612637
} // namespace OCC

src/gui/lockwatcher.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ void LockWatcher::checkFiles()
4545
QSet<QString> unlocked;
4646

4747
for (const auto &path : std::as_const(_watchedPaths)) {
48-
if (!FileSystem::isFileLocked(path)) {
48+
if (!FileSystem::isFileLocked(path, FileSystem::LockMode::SharedRead)) {
4949
qCInfo(lcLockWatcher) << "Lock of" << path << "was released";
5050
emit fileUnlocked(path);
5151
unlocked.insert(path);

src/libsync/bulkpropagatorjob.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ void BulkPropagatorJob::triggerUpload()
241241

242242
// If the file is currently locked, we want to retry the sync
243243
// when it becomes available again.
244-
if (FileSystem::isFileLocked(singleFile._localPath)) {
244+
if (FileSystem::isFileLocked(singleFile._localPath, FileSystem::LockMode::SharedRead)) {
245245
emit propagator()->seenLockedFile(singleFile._localPath);
246246
}
247247

src/libsync/owncloudpropagator.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ bool OwncloudPropagator::createConflict(const SyncFileItemPtr &item,
981981

982982
// If the file is locked, we want to retry this sync when it
983983
// becomes available again.
984-
if (FileSystem::isFileLocked(fn)) {
984+
if (FileSystem::isFileLocked(fn, FileSystem::LockMode::SharedRead)) {
985985
emit seenLockedFile(fn);
986986
}
987987

@@ -1055,7 +1055,7 @@ OCC::Optional<QString> OwncloudPropagator::createCaseClashConflict(const SyncFil
10551055

10561056
// If the file is locked, we want to retry this sync when it
10571057
// becomes available again.
1058-
if (FileSystem::isFileLocked(filename)) {
1058+
if (FileSystem::isFileLocked(filename, FileSystem::LockMode::SharedRead)) {
10591059
emit seenLockedFile(filename);
10601060
}
10611061

src/libsync/propagatedownload.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1329,7 +1329,7 @@ void PropagateDownloadFile::downloadFinished()
13291329
qCWarning(lcPropagateDownload) << QStringLiteral("Rename failed: %1 => %2").arg(_tmpFile.fileName()).arg(filename);
13301330
// If the file is locked, we want to retry this sync when it
13311331
// becomes available again, otherwise try again directly
1332-
if (FileSystem::isFileLocked(filename)) {
1332+
if (FileSystem::isFileLocked(filename, FileSystem::LockMode::SharedRead)) {
13331333
emit propagator()->seenLockedFile(filename);
13341334
} else {
13351335
propagator()->_anotherSyncNeeded = true;

src/libsync/propagateupload.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
namespace OCC {
3737

3838
Q_LOGGING_CATEGORY(lcPutJob, "nextcloud.sync.networkjob.put", QtInfoMsg)
39+
Q_LOGGING_CATEGORY(lcUploadDevice, "nextcloud.sync.uploaddevice", QtInfoMsg)
3940
Q_LOGGING_CATEGORY(lcPollJob, "nextcloud.sync.networkjob.poll", QtInfoMsg)
4041
Q_LOGGING_CATEGORY(lcPropagateUpload, "nextcloud.sync.propagator.upload", QtInfoMsg)
4142
Q_LOGGING_CATEGORY(lcPropagateUploadV1, "nextcloud.sync.propagator.upload.v1", QtInfoMsg)
@@ -549,6 +550,7 @@ qint64 UploadDevice::readData(char *data, qint64 maxlen)
549550
setErrorString({});
550551
return c;
551552
} else if (c < 0) {
553+
qCWarning(lcUploadDevice) << _file.errorString() << c;
552554
setErrorString(_file.errorString());
553555
return -1;
554556
}

src/libsync/propagateuploadng.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,12 +352,12 @@ void PropagateUploadFileNG::startNextChunk()
352352

353353
const auto fileName = _fileToUpload._path;
354354
auto device = std::make_unique<UploadDevice>(fileName, _sent, _currentChunkSize, &propagator()->_bandwidthManager);
355-
if (!device->open(QIODevice::ReadOnly)) {
355+
if (auto isLocked = FileSystem::isFileLocked(fileName, FileSystem::LockMode::SharedRead); isLocked || !device->open(QIODevice::ReadOnly)) {
356356
qCWarning(lcPropagateUploadNG) << "Could not prepare upload device: " << device->errorString();
357357

358358
// If the file is currently locked, we want to retry the sync
359359
// when it becomes available again.
360-
if (FileSystem::isFileLocked(fileName)) {
360+
if (isLocked) {
361361
emit propagator()->seenLockedFile(fileName);
362362
}
363363
// Soft error because this is likely caused by the user modifying his files while syncing

0 commit comments

Comments
 (0)