Skip to content

Commit

Permalink
PropagateDownloadFile/FileSystem: Preserve permissions for symlinks
Browse files Browse the repository at this point in the history
This commit will preserve the permissions set on the symlink if a new
file is downloaded in the same place and the symlink is overwritten.
That fixes a bug which caused the downloaded file to become unreadable
if the symlink was broken and no permissions could be retrieved.
  • Loading branch information
taminob committed Dec 8, 2023
1 parent cfe27b5 commit 489725a
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 5 deletions.
27 changes: 27 additions & 0 deletions src/libsync/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

#include "filesystem.h"

#include <filesystem>

#include "common/utility.h"
#include <QFile>
#include <QFileInfo>
Expand Down Expand Up @@ -140,6 +142,31 @@ qint64 FileSystem::getSize(const QString &filename)
return info.size();
}

QFile::Permissions FileSystem::getPermissions(const QString &filename)
{
using qtStdFilePermissionsMapping = std::pair<QFile::Permission, std::filesystem::perms>;
constexpr std::array<qtStdFilePermissionsMapping, 9> qtStdFilePermissionsMappings = {
std::make_pair(QFileDevice::ReadOwner, std::filesystem::perms::owner_read),
{QFileDevice::WriteOwner, std::filesystem::perms::owner_write},
{QFileDevice::ExeOwner, std::filesystem::perms::owner_exec},
{QFileDevice::ReadGroup, std::filesystem::perms::group_read},
{QFileDevice::WriteGroup, std::filesystem::perms::group_write},
{QFileDevice::ExeGroup, std::filesystem::perms::group_exec},
{QFileDevice::ReadOther, std::filesystem::perms::others_read},
{QFileDevice::WriteOther, std::filesystem::perms::others_write},
{QFileDevice::ExeOther, std::filesystem::perms::others_exec}};

auto fileStatus = std::filesystem::symlink_status(filename.toStdString());
auto permissions = fileStatus.permissions();

QFile::Permissions resultPermissions;
for (auto [qtPermissionFlag, stdPermissionFlag] : qtStdFilePermissionsMappings) {
auto isPermissionSet = (permissions & stdPermissionFlag) != std::filesystem::perms::none;
resultPermissions.setFlag(qtPermissionFlag, isPermissionSet);
}
return resultPermissions;
}

// Code inspired from Qt5's QDir::removeRecursively
bool FileSystem::removeRecursively(const QString &path, const std::function<void(const QString &path, bool isDir)> &onDeleted, QStringList *errors)
{
Expand Down
7 changes: 7 additions & 0 deletions src/libsync/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ namespace FileSystem {
*/
qint64 OWNCLOUDSYNC_EXPORT getSize(const QString &filename);

/**
* @brief Get permissions for a file
*
* If the file is a symlink, the permissions for the symlink are returned.
*/
QFile::Permissions OWNCLOUDSYNC_EXPORT getPermissions(const QString &filename);

/**
* @brief Retrieve a file inode with csync
*/
Expand Down
11 changes: 6 additions & 5 deletions src/libsync/propagatedownload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,8 @@ namespace { // Anonymous namespace for the recall feature
static void preserveGroupOwnership(const QString &fileName, const QFileInfo &fi)
{
#ifdef Q_OS_UNIX
int chownErr = chown(fileName.toLocal8Bit().constData(), -1, fi.groupId());
int chownErr = fchownat(AT_FDCWD, fileName.toLocal8Bit().constData(), -1,
fi.groupId(), AT_SYMLINK_NOFOLLOW);
if (chownErr) {
// TODO: Consider further error handling!
qCWarning(lcPropagateDownload) << QString("preserveGroupOwnership: chown error %1: setting group %2 failed on file %3").arg(chownErr).arg(fi.groupId()).arg(fileName);
Expand Down Expand Up @@ -1220,11 +1221,11 @@ void PropagateDownloadFile::downloadFinished()
auto previousFileExists = FileSystem::fileExists(filename) && _item->_instruction != CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT;
if (previousFileExists) {
// Preserve the existing file permissions.
const auto existingFile = QFileInfo{filename};
if (existingFile.permissions() != _tmpFile.permissions()) {
_tmpFile.setPermissions(existingFile.permissions());
auto previousPermissions = FileSystem::getPermissions(filename);
if (previousPermissions != FileSystem::getPermissions(_tmpFile.fileName())) {
_tmpFile.setPermissions(previousPermissions);
}
preserveGroupOwnership(_tmpFile.fileName(), existingFile);
preserveGroupOwnership(_tmpFile.fileName(), QFileInfo(filename));

// Make the file a hydrated placeholder if possible
const auto result = propagator()->syncOptions()._vfs->convertToPlaceholder(_tmpFile.fileName(), *_item, filename);
Expand Down

0 comments on commit 489725a

Please sign in to comment.