Warning, file /frameworks/kio/src/ioslaves/file/file_unix.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2000-2002 Stephan Kulow <coolo@kde.org>
0003     SPDX-FileCopyrightText: 2000-2002 David Faure <faure@kde.org>
0004     SPDX-FileCopyrightText: 2000-2002 Waldo Bastian <bastian@kde.org>
0005     SPDX-FileCopyrightText: 2006 Allan Sandfeld Jensen <sandfeld@kde.org>
0006     SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
0007     SPDX-FileCopyrightText: 2007 Christian Ehrlicher <ch.ehrlicher@gmx.de>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 
0012 #include "file.h"
0013 
0014 #include "config-kioslave-file.h"
0015 
0016 #include "../utils_p.h"
0017 
0018 #if HAVE_POSIX_ACL
0019 #include <../../aclhelpers_p.h>
0020 #endif
0021 
0022 #include <QDir>
0023 #include <QFile>
0024 #include <QMimeDatabase>
0025 #include <QStandardPaths>
0026 #include <QThread>
0027 #include <qplatformdefs.h>
0028 
0029 #include <KConfigGroup>
0030 #include <KFileSystemType>
0031 #include <KLocalizedString>
0032 #include <QDebug>
0033 #include <kmountpoint.h>
0034 
0035 #include <array>
0036 #include <cerrno>
0037 #include <stdint.h>
0038 #include <utime.h>
0039 
0040 #include <KAuth/Action>
0041 #include <KAuth/ExecuteJob>
0042 #include <KRandom>
0043 
0044 #include "fdreceiver.h"
0045 #include "statjob.h"
0046 
0047 #ifdef Q_OS_LINUX
0048 
0049 #include <linux/fs.h>
0050 #include <sys/ioctl.h>
0051 #include <unistd.h>
0052 
0053 #endif // Q_OS_LINUX
0054 
0055 #if HAVE_STATX
0056 #include <sys/stat.h>
0057 #include <sys/sysmacros.h> // for makedev()
0058 #endif
0059 
0060 #if HAVE_COPY_FILE_RANGE
0061 // sys/types.h must be included before unistd.h,
0062 // and it needs to be included explicitly for FreeBSD
0063 #include <sys/types.h>
0064 #include <unistd.h>
0065 #endif
0066 
0067 #if HAVE_SYS_XATTR_H
0068 #include <sys/xattr.h>
0069 // BSD uses a different include
0070 #elif HAVE_SYS_EXTATTR_H
0071 #include <sys/types.h> // For FreeBSD, this must be before sys/extattr.h
0072 
0073 #include <sys/extattr.h>
0074 #endif
0075 
0076 using namespace KIO;
0077 
0078 /* 512 kB */
0079 static constexpr int s_maxIPCSize = 1024 * 512;
0080 
0081 static bool same_inode(const QT_STATBUF &src, const QT_STATBUF &dest)
0082 {
0083     if (src.st_ino == dest.st_ino && src.st_dev == dest.st_dev) {
0084         return true;
0085     }
0086 
0087     return false;
0088 }
0089 
0090 static const QString socketPath()
0091 {
0092     const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
0093     return QStringLiteral("%1/filehelper%2%3").arg(runtimeDir, KRandom::randomString(6)).arg(qlonglong(QThread::currentThreadId()));
0094 }
0095 
0096 static QString actionDetails(ActionType actionType, const QVariantList &args)
0097 {
0098     QString action;
0099     QString detail;
0100     switch (actionType) {
0101     case CHMOD:
0102         action = i18n("Change File Permissions");
0103         detail = i18n("New Permissions: %1", args[1].toInt());
0104         break;
0105     case CHOWN:
0106         action = i18n("Change File Owner");
0107         detail = i18n("New Owner: UID=%1, GID=%2", args[1].toInt(), args[2].toInt());
0108         break;
0109     case DEL:
0110         action = i18n("Remove File");
0111         break;
0112     case RMDIR:
0113         action = i18n("Remove Directory");
0114         break;
0115     case MKDIR:
0116         action = i18n("Create Directory");
0117         detail = i18n("Directory Permissions: %1", args[1].toInt());
0118         break;
0119     case OPEN:
0120         action = i18n("Open File");
0121         break;
0122     case OPENDIR:
0123         action = i18n("Open Directory");
0124         break;
0125     case RENAME:
0126         action = i18n("Rename");
0127         detail = i18n("New Filename: %1", args[1].toString());
0128         break;
0129     case SYMLINK:
0130         action = i18n("Create Symlink");
0131         detail = i18n("Target: %1", args[1].toString());
0132         break;
0133     case UTIME:
0134         action = i18n("Change Timestamp");
0135         break;
0136     case COPY:
0137         action = i18n("Copy");
0138         detail = i18n("From: %1, To: %2", args[0].toString(), args[1].toString());
0139         break;
0140     default:
0141         action = i18n("Unknown Action");
0142         break;
0143     }
0144 
0145     const QString metadata = i18n(
0146         "Action: %1\n"
0147         "Source: %2\n"
0148         "%3",
0149         action,
0150         args[0].toString(),
0151         detail);
0152     return metadata;
0153 }
0154 
0155 bool FileProtocol::privilegeOperationUnitTestMode()
0156 {
0157     return (metaData(QStringLiteral("UnitTesting")) == QLatin1String("true"))
0158         && (requestPrivilegeOperation(QStringLiteral("Test Call")) == KIO::OperationAllowed);
0159 }
0160 
0161 #if HAVE_POSIX_ACL
0162 bool FileProtocol::isExtendedACL(acl_t acl)
0163 {
0164     return (ACLPortability::acl_equiv_mode(acl, nullptr) != 0);
0165 }
0166 #endif
0167 
0168 static QString getUserName(KUserId uid)
0169 {
0170     thread_local QHash<KUserId, QString> staticUserCache;
0171     if (Q_UNLIKELY(!uid.isValid())) {
0172         return QString();
0173     }
0174     auto it = staticUserCache.find(uid);
0175     if (it == staticUserCache.end()) {
0176         KUser user(uid);
0177         QString name = user.loginName();
0178         if (name.isEmpty()) {
0179             name = uid.toString();
0180         }
0181         it = staticUserCache.insert(uid, name);
0182     }
0183     return *it;
0184 }
0185 
0186 static QString getGroupName(KGroupId gid)
0187 {
0188     thread_local QHash<KGroupId, QString> staticGroupCache;
0189     if (Q_UNLIKELY(!gid.isValid())) {
0190         return QString();
0191     }
0192     auto it = staticGroupCache.find(gid);
0193     if (it == staticGroupCache.end()) {
0194         KUserGroup group(gid);
0195         QString name = group.name();
0196         if (name.isEmpty()) {
0197             name = gid.toString();
0198         }
0199         it = staticGroupCache.insert(gid, name);
0200     }
0201     return *it;
0202 }
0203 
0204 #if HAVE_STATX
0205 // statx syscall is available
0206 inline int LSTAT(const char *path, struct statx *buff, KIO::StatDetails details)
0207 {
0208     uint32_t mask = 0;
0209     if (details & KIO::StatBasic) {
0210         // filename, access, type, size, linkdest
0211         mask |= STATX_SIZE | STATX_TYPE;
0212     }
0213     if (details & KIO::StatUser) {
0214         // uid, gid
0215         mask |= STATX_UID | STATX_GID;
0216     }
0217     if (details & KIO::StatTime) {
0218         // atime, mtime, btime
0219         mask |= STATX_ATIME | STATX_MTIME | STATX_BTIME;
0220     }
0221     if (details & KIO::StatInode) {
0222         // dev, inode
0223         mask |= STATX_INO;
0224     }
0225     return statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, mask, buff);
0226 }
0227 inline int STAT(const char *path, struct statx *buff, const KIO::StatDetails &details)
0228 {
0229     uint32_t mask = 0;
0230     // KIO::StatAcl needs type
0231     if (details & (KIO::StatBasic | KIO::StatAcl | KIO::StatResolveSymlink)) {
0232         // filename, access, type
0233         mask |= STATX_TYPE;
0234     }
0235     if (details & (KIO::StatBasic | KIO::StatResolveSymlink)) {
0236         // size, linkdest
0237         mask |= STATX_SIZE;
0238     }
0239     if (details & KIO::StatUser) {
0240         // uid, gid
0241         mask |= STATX_UID | STATX_GID;
0242     }
0243     if (details & KIO::StatTime) {
0244         // atime, mtime, btime
0245         mask |= STATX_ATIME | STATX_MTIME | STATX_BTIME;
0246     }
0247     // KIO::Inode is ignored as when STAT is called, the entry inode field has already been filled
0248     return statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT, mask, buff);
0249 }
0250 inline static uint16_t stat_mode(const struct statx &buf)
0251 {
0252     return buf.stx_mode;
0253 }
0254 inline static dev_t stat_dev(const struct statx &buf)
0255 {
0256     return makedev(buf.stx_dev_major, buf.stx_dev_minor);
0257 }
0258 inline static uint64_t stat_ino(const struct statx &buf)
0259 {
0260     return buf.stx_ino;
0261 }
0262 inline static uint64_t stat_size(const struct statx &buf)
0263 {
0264     return buf.stx_size;
0265 }
0266 inline static uint32_t stat_uid(const struct statx &buf)
0267 {
0268     return buf.stx_uid;
0269 }
0270 inline static uint32_t stat_gid(const struct statx &buf)
0271 {
0272     return buf.stx_gid;
0273 }
0274 inline static int64_t stat_atime(const struct statx &buf)
0275 {
0276     return buf.stx_atime.tv_sec;
0277 }
0278 inline static int64_t stat_mtime(const struct statx &buf)
0279 {
0280     return buf.stx_mtime.tv_sec;
0281 }
0282 #else
0283 // regular stat struct
0284 inline int LSTAT(const char *path, QT_STATBUF *buff, KIO::StatDetails details)
0285 {
0286     Q_UNUSED(details)
0287     return QT_LSTAT(path, buff);
0288 }
0289 inline int STAT(const char *path, QT_STATBUF *buff, KIO::StatDetails details)
0290 {
0291     Q_UNUSED(details)
0292     return QT_STAT(path, buff);
0293 }
0294 inline static mode_t stat_mode(const QT_STATBUF &buf)
0295 {
0296     return buf.st_mode;
0297 }
0298 inline static dev_t stat_dev(const QT_STATBUF &buf)
0299 {
0300     return buf.st_dev;
0301 }
0302 inline static ino_t stat_ino(const QT_STATBUF &buf)
0303 {
0304     return buf.st_ino;
0305 }
0306 inline static off_t stat_size(const QT_STATBUF &buf)
0307 {
0308     return buf.st_size;
0309 }
0310 inline static uid_t stat_uid(const QT_STATBUF &buf)
0311 {
0312     return buf.st_uid;
0313 }
0314 inline static gid_t stat_gid(const QT_STATBUF &buf)
0315 {
0316     return buf.st_gid;
0317 }
0318 inline static time_t stat_atime(const QT_STATBUF &buf)
0319 {
0320     return buf.st_atime;
0321 }
0322 inline static time_t stat_mtime(const QT_STATBUF &buf)
0323 {
0324     return buf.st_mtime;
0325 }
0326 #endif
0327 
0328 static bool isOnCifsMount(const QString &filePath)
0329 {
0330     const auto mount = KMountPoint::currentMountPoints().findByPath(filePath);
0331     if (!mount) {
0332         return false;
0333     }
0334     return mount->mountType() == QStringLiteral("cifs") || mount->mountType() == QStringLiteral("smb3");
0335 }
0336 
0337 static bool createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, KIO::StatDetails details, const QString &fullPath)
0338 {
0339     assert(entry.count() == 0); // by contract :-)
0340     int entries = 0;
0341     if (details & KIO::StatBasic) {
0342         // filename, access, type, size, linkdest
0343         entries += 5;
0344     }
0345     if (details & KIO::StatUser) {
0346         // uid, gid
0347         entries += 2;
0348     }
0349     if (details & KIO::StatTime) {
0350         // atime, mtime, btime
0351         entries += 3;
0352     }
0353     if (details & KIO::StatAcl) {
0354         // acl data
0355         entries += 3;
0356     }
0357     if (details & KIO::StatInode) {
0358         // dev, inode
0359         entries += 2;
0360     }
0361     if (details & KIO::StatMimeType) {
0362         // mimetype
0363         entries += 1;
0364     }
0365     entry.reserve(entries);
0366 
0367     if (details & KIO::StatBasic) {
0368         entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename);
0369     }
0370 
0371     bool isBrokenSymLink = false;
0372 #if HAVE_POSIX_ACL
0373     QByteArray targetPath = path;
0374 #endif
0375 
0376 #if HAVE_STATX
0377     // statx syscall is available
0378     struct statx buff;
0379 #else
0380     QT_STATBUF buff;
0381 #endif
0382 
0383     if (LSTAT(path.data(), &buff, details) == 0) {
0384         if (Utils::isLinkMask(stat_mode(buff))) {
0385             QByteArray linkTargetBuffer;
0386             if (details & (KIO::StatBasic | KIO::StatResolveSymlink)) {
0387 // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927)
0388 #if HAVE_STATX
0389                 size_t lowerBound = 256;
0390                 size_t higherBound = 1024;
0391                 uint64_t s = stat_size(buff);
0392                 if (s > SIZE_MAX) {
0393                     qCWarning(KIO_FILE) << "file size bigger than SIZE_MAX, too big for readlink use!" << path;
0394                     return false;
0395                 }
0396                 size_t size = static_cast<size_t>(s);
0397                 using SizeType = size_t;
0398 #else
0399                 off_t lowerBound = 256;
0400                 off_t higherBound = 1024;
0401                 off_t size = stat_size(buff);
0402                 using SizeType = off_t;
0403 #endif
0404                 SizeType bufferSize = qBound(lowerBound, size + 1, higherBound);
0405                 linkTargetBuffer.resize(bufferSize);
0406                 while (true) {
0407                     ssize_t n = readlink(path.constData(), linkTargetBuffer.data(), bufferSize);
0408                     if (n < 0 && errno != ERANGE) {
0409                         qCWarning(KIO_FILE) << "readlink failed!" << path;
0410                         return false;
0411                     } else if (n > 0 && static_cast<SizeType>(n) != bufferSize) {
0412                         // the buffer was not filled in the last iteration
0413                         // we are finished reading, break the loop
0414                         linkTargetBuffer.truncate(n);
0415                         break;
0416                     }
0417                     bufferSize *= 2;
0418                     linkTargetBuffer.resize(bufferSize);
0419                 }
0420                 const QString linkTarget = QFile::decodeName(linkTargetBuffer);
0421                 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkTarget);
0422             }
0423 
0424             // A symlink
0425             if (details & KIO::StatResolveSymlink) {
0426                 if (STAT(path.constData(), &buff, details) == -1) {
0427                     isBrokenSymLink = true;
0428                 } else {
0429 #if HAVE_POSIX_ACL
0430                     if (details & KIO::StatAcl) {
0431                         // valid symlink, will get the ACLs of the destination
0432                         targetPath = linkTargetBuffer;
0433                     }
0434 #endif
0435                 }
0436             }
0437         }
0438     } else {
0439         // qCWarning(KIO_FILE) << "lstat didn't work on " << path.data();
0440         return false;
0441     }
0442 
0443     mode_t type = 0;
0444     if (details & (KIO::StatBasic | KIO::StatAcl)) {
0445         mode_t access;
0446         signed long long size;
0447         if (isBrokenSymLink) {
0448             // It is a link pointing to nowhere
0449             type = S_IFMT - 1;
0450             access = S_IRWXU | S_IRWXG | S_IRWXO;
0451             size = 0LL;
0452         } else {
0453             type = stat_mode(buff) & S_IFMT; // extract file type
0454             access = stat_mode(buff) & 07777; // extract permissions
0455             size = stat_size(buff);
0456         }
0457 
0458         if (details & KIO::StatBasic) {
0459             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type);
0460             entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access);
0461             entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size);
0462         }
0463 
0464 #if HAVE_POSIX_ACL
0465         if (details & KIO::StatAcl) {
0466             /* Append an atom indicating whether the file has extended acl information
0467              * and if withACL is specified also one with the acl itself. If it's a directory
0468              * and it has a default ACL, also append that. */
0469             appendACLAtoms(targetPath, entry, type);
0470         }
0471 #endif
0472     }
0473 
0474     if (details & KIO::StatUser) {
0475         entry.fastInsert(KIO::UDSEntry::UDS_USER, getUserName(KUserId(stat_uid(buff))));
0476         entry.fastInsert(KIO::UDSEntry::UDS_GROUP, getGroupName(KGroupId(stat_gid(buff))));
0477     }
0478 
0479     if (details & KIO::StatTime) {
0480         entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, stat_mtime(buff));
0481         entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, stat_atime(buff));
0482 
0483 #ifdef st_birthtime
0484         /* For example FreeBSD's and NetBSD's stat contains a field for
0485          * the inode birth time: st_birthtime
0486          * This however only works on UFS and ZFS, and not, on say, NFS.
0487          * Instead of setting a bogus fallback like st_mtime, only use
0488          * it if it is greater than 0. */
0489         if (buff.st_birthtime > 0) {
0490             entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.st_birthtime);
0491         }
0492 #elif defined __st_birthtime
0493         /* As above, but OpenBSD calls it slightly differently. */
0494         if (buff.__st_birthtime > 0) {
0495             entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.__st_birthtime);
0496         }
0497 #elif HAVE_STATX
0498         /* And linux version using statx syscall */
0499         if (buff.stx_mask & STATX_BTIME) {
0500             entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.stx_btime.tv_sec);
0501         }
0502 #endif
0503     }
0504 
0505     if (details & KIO::StatInode) {
0506         entry.fastInsert(KIO::UDSEntry::UDS_DEVICE_ID, stat_dev(buff));
0507         entry.fastInsert(KIO::UDSEntry::UDS_INODE, stat_ino(buff));
0508     }
0509 
0510     if (details & KIO::StatMimeType) {
0511         QMimeDatabase db;
0512         entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, db.mimeTypeForFile(fullPath).name());
0513     }
0514 
0515     return true;
0516 }
0517 
0518 PrivilegeOperationReturnValue FileProtocol::tryOpen(QFile &f, const QByteArray &path, int flags, int mode, int errcode)
0519 {
0520     const QString sockPath = socketPath();
0521     FdReceiver fdRecv(QFile::encodeName(sockPath).toStdString());
0522     if (!fdRecv.isListening()) {
0523         return PrivilegeOperationReturnValue::failure(errcode);
0524     }
0525 
0526     QIODevice::OpenMode openMode;
0527     if (flags & O_RDONLY) {
0528         openMode |= QIODevice::ReadOnly;
0529     }
0530     if (flags & O_WRONLY || flags & O_CREAT) {
0531         openMode |= QIODevice::WriteOnly;
0532     }
0533     if (flags & O_RDWR) {
0534         openMode |= QIODevice::ReadWrite;
0535     }
0536     if (flags & O_TRUNC) {
0537         openMode |= QIODevice::Truncate;
0538     }
0539     if (flags & O_APPEND) {
0540         openMode |= QIODevice::Append;
0541     }
0542 
0543     if (auto err = execWithElevatedPrivilege(OPEN, {path, flags, mode, sockPath}, errcode)) {
0544         return err;
0545     } else {
0546         int fd = fdRecv.fileDescriptor();
0547         if (fd < 3 || !f.open(fd, openMode, QFileDevice::AutoCloseHandle)) {
0548             return PrivilegeOperationReturnValue::failure(errcode);
0549         }
0550     }
0551     return PrivilegeOperationReturnValue::success();
0552 }
0553 
0554 PrivilegeOperationReturnValue FileProtocol::tryChangeFileAttr(ActionType action, const QVariantList &args, int errcode)
0555 {
0556     KAuth::Action execAction(QStringLiteral("org.kde.kio.file.exec"));
0557     execAction.setHelperId(QStringLiteral("org.kde.kio.file"));
0558     if (execAction.status() == KAuth::Action::AuthorizedStatus) {
0559         return execWithElevatedPrivilege(action, args, errcode);
0560     }
0561     return PrivilegeOperationReturnValue::failure(errcode);
0562 }
0563 
0564 #if HAVE_SYS_XATTR_H || HAVE_SYS_EXTATTR_H
0565 bool FileProtocol::copyXattrs(const int src_fd, const int dest_fd)
0566 {
0567     // Get the list of keys
0568     ssize_t listlen = 0;
0569     QByteArray keylist;
0570     while (true) {
0571         keylist.resize(listlen);
0572 #if HAVE_SYS_XATTR_H && !defined(__stub_getxattr) && !defined(Q_OS_MAC)
0573         listlen = flistxattr(src_fd, keylist.data(), listlen);
0574 #elif defined(Q_OS_MAC)
0575         listlen = flistxattr(src_fd, keylist.data(), listlen, 0);
0576 #elif HAVE_SYS_EXTATTR_H
0577         listlen = extattr_list_fd(src_fd, EXTATTR_NAMESPACE_USER, listlen == 0 ? nullptr : keylist.data(), listlen);
0578 #endif
0579         if (listlen > 0 && keylist.size() == 0) {
0580             continue;
0581         }
0582         if (listlen > 0 && keylist.size() > 0) {
0583             break;
0584         }
0585         if (listlen == -1 && errno == ERANGE) {
0586             listlen = 0;
0587             continue;
0588         }
0589         if (listlen == 0) {
0590             qCDebug(KIO_FILE) << "the file doesn't have any xattr";
0591             return true;
0592         }
0593         Q_ASSERT_X(listlen == -1, "copyXattrs", "unexpected return value from listxattr");
0594         if (listlen == -1 && errno == ENOTSUP) {
0595             qCDebug(KIO_FILE) << "source filesystem does not support xattrs";
0596         }
0597         return false;
0598     }
0599 
0600     keylist.resize(listlen);
0601 
0602     // Linux and MacOS return a list of null terminated strings, each string = [data,'\0']
0603     // BSDs return a list of items, each item consisting of the size byte
0604     // prepended to the key = [size, data]
0605     auto keyPtr = keylist.cbegin();
0606     size_t keyLen;
0607     QByteArray value;
0608 
0609     // For each key
0610     while (keyPtr != keylist.cend()) {
0611         // Get size of the key
0612 #if HAVE_SYS_XATTR_H
0613         keyLen = strlen(keyPtr);
0614         auto next_key = [&]() {
0615             keyPtr += keyLen + 1;
0616         };
0617 #elif HAVE_SYS_EXTATTR_H
0618         keyLen = static_cast<unsigned char>(*keyPtr);
0619         keyPtr++;
0620         auto next_key = [&]() {
0621             keyPtr += keyLen;
0622         };
0623 #endif
0624         QByteArray key(keyPtr, keyLen);
0625 
0626         // Get the value for key
0627         ssize_t valuelen = 0;
0628         do {
0629             value.resize(valuelen);
0630 #if HAVE_SYS_XATTR_H && !defined(__stub_getxattr) && !defined(Q_OS_MAC)
0631             valuelen = fgetxattr(src_fd, key.constData(), value.data(), valuelen);
0632 #elif defined(Q_OS_MAC)
0633             valuelen = fgetxattr(src_fd, key.constData(), value.data(), valuelen, 0, 0);
0634 #elif HAVE_SYS_EXTATTR_H
0635             valuelen = extattr_get_fd(src_fd, EXTATTR_NAMESPACE_USER, key.constData(), valuelen == 0 ? nullptr : value.data(), valuelen);
0636 #endif
0637             if (valuelen > 0 && value.size() == 0) {
0638                 continue;
0639             }
0640             if (valuelen > 0 && value.size() > 0) {
0641                 break;
0642             }
0643             if (valuelen == -1 && errno == ERANGE) {
0644                 valuelen = 0;
0645                 continue;
0646             }
0647             // happens when attr value is an empty string
0648             if (valuelen == 0) {
0649                 break;
0650             }
0651             Q_ASSERT_X(valuelen == -1, "copyXattrs", "unexpected return value from getxattr");
0652             // Some other error, skip to the next attribute, most notably
0653             // - ENOTSUP: invalid (inaccassible) attribute namespace, e.g. with SELINUX
0654             break;
0655         } while (true);
0656 
0657         if (valuelen < 0) {
0658             // Skip to next attribute.
0659             next_key();
0660             continue;
0661         }
0662 
0663         // Write key:value pair on destination
0664 #if HAVE_SYS_XATTR_H && !defined(__stub_getxattr) && !defined(Q_OS_MAC)
0665         ssize_t destlen = fsetxattr(dest_fd, key.constData(), value.constData(), valuelen, 0);
0666 #elif defined(Q_OS_MAC)
0667         ssize_t destlen = fsetxattr(dest_fd, key.constData(), value.constData(), valuelen, 0, 0);
0668 #elif HAVE_SYS_EXTATTR_H
0669         ssize_t destlen = extattr_set_fd(dest_fd, EXTATTR_NAMESPACE_USER, key.constData(), value.constData(), valuelen);
0670 #endif
0671         if (destlen == -1 && errno == ENOTSUP) {
0672             qCDebug(KIO_FILE) << "Destination filesystem does not support xattrs";
0673             return false;
0674         }
0675         if (destlen == -1 && (errno == ENOSPC || errno == EDQUOT)) {
0676             return false;
0677         }
0678 
0679         next_key();
0680     }
0681     return true;
0682 }
0683 #endif // HAVE_SYS_XATTR_H || HAVE_SYS_EXTATTR_H
0684 
0685 void FileProtocol::copy(const QUrl &srcUrl, const QUrl &destUrl, int _mode, JobFlags _flags)
0686 {
0687     if (privilegeOperationUnitTestMode()) {
0688         finished();
0689         return;
0690     }
0691 
0692     qCDebug(KIO_FILE) << "copy()" << srcUrl << "to" << destUrl << "mode=" << _mode;
0693 
0694     const QString src = srcUrl.toLocalFile();
0695     QString dest = destUrl.toLocalFile();
0696     QByteArray _src(QFile::encodeName(src));
0697     QByteArray _dest(QFile::encodeName(dest));
0698     QByteArray _destBackup;
0699 
0700     QT_STATBUF buffSrc;
0701     if (QT_STAT(_src.data(), &buffSrc) == -1) {
0702         if (errno == EACCES) {
0703             error(KIO::ERR_ACCESS_DENIED, src);
0704         } else {
0705             error(KIO::ERR_DOES_NOT_EXIST, src);
0706         }
0707         return;
0708     }
0709 
0710     if (S_ISDIR(buffSrc.st_mode)) {
0711         error(KIO::ERR_IS_DIRECTORY, src);
0712         return;
0713     }
0714     if (S_ISFIFO(buffSrc.st_mode) || S_ISSOCK(buffSrc.st_mode)) {
0715         error(KIO::ERR_CANNOT_OPEN_FOR_READING, src);
0716         return;
0717     }
0718 
0719     QT_STATBUF buffDest;
0720     bool dest_exists = (QT_LSTAT(_dest.data(), &buffDest) != -1);
0721     if (dest_exists) {
0722         if (same_inode(buffDest, buffSrc)) {
0723             error(KIO::ERR_IDENTICAL_FILES, dest);
0724             return;
0725         }
0726 
0727         if (S_ISDIR(buffDest.st_mode)) {
0728             error(KIO::ERR_DIR_ALREADY_EXIST, dest);
0729             return;
0730         }
0731 
0732         if (_flags & KIO::Overwrite) {
0733             // If the destination is a symlink and overwrite is TRUE,
0734             // remove the symlink first to prevent the scenario where
0735             // the symlink actually points to current source!
0736             if (S_ISLNK(buffDest.st_mode)) {
0737                 // qDebug() << "copy(): LINK DESTINATION";
0738                 if (!QFile::remove(dest)) {
0739                     if (auto err = execWithElevatedPrivilege(DEL, {_dest}, errno)) {
0740                         if (!err.wasCanceled()) {
0741                             error(KIO::ERR_CANNOT_DELETE_ORIGINAL, dest);
0742                         }
0743                         return;
0744                     }
0745                 }
0746             } else if (S_ISREG(buffDest.st_mode) && !isOnCifsMount(dest)) {
0747                 _destBackup = _dest;
0748                 dest.append(QStringLiteral(".part"));
0749                 _dest = QFile::encodeName(dest);
0750             }
0751         } else {
0752             error(KIO::ERR_FILE_ALREADY_EXIST, dest);
0753             return;
0754         }
0755     }
0756 
0757     QFile srcFile(src);
0758     if (!srcFile.open(QIODevice::ReadOnly)) {
0759         if (auto err = tryOpen(srcFile, _src, O_RDONLY, S_IRUSR, errno)) {
0760             if (!err.wasCanceled()) {
0761                 error(KIO::ERR_CANNOT_OPEN_FOR_READING, src);
0762             }
0763             return;
0764         }
0765     }
0766 
0767 #if HAVE_FADVISE
0768     posix_fadvise(srcFile.handle(), 0, 0, POSIX_FADV_SEQUENTIAL);
0769 #endif
0770 
0771     QFile destFile(dest);
0772     if (!destFile.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
0773         if (auto err = tryOpen(destFile, _dest, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR, errno)) {
0774             if (!err.wasCanceled()) {
0775                 // qDebug() << "###### COULD NOT WRITE " << dest;
0776                 if (err == EACCES) {
0777                     error(KIO::ERR_WRITE_ACCESS_DENIED, dest);
0778                 } else {
0779                     error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest);
0780                 }
0781             }
0782             return;
0783         }
0784     }
0785 
0786     // _mode == -1 means don't touch dest permissions, leave it with the system default ones
0787     if (_mode != -1) {
0788         if (::chmod(_dest.constData(), _mode) == -1) {
0789             const int errCode = errno;
0790             KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(dest);
0791             // Eat the error if the filesystem apparently doesn't support chmod.
0792             // This test isn't fullproof though, vboxsf (VirtualBox shared folder) supports
0793             // chmod if the host is Linux, and doesn't if the host is Windows. Hard to detect.
0794             if (mp && mp->testFileSystemFlag(KMountPoint::SupportsChmod)) {
0795                 if (tryChangeFileAttr(CHMOD, {_dest, _mode}, errCode)) {
0796                     qCWarning(KIO_FILE) << "Could not change permissions for" << dest;
0797                 }
0798             }
0799         }
0800     }
0801 
0802 #if HAVE_FADVISE
0803     posix_fadvise(destFile.handle(), 0, 0, POSIX_FADV_SEQUENTIAL);
0804 #endif
0805 
0806     const auto srcSize = buffSrc.st_size;
0807     totalSize(srcSize);
0808 
0809     off_t sizeProcessed = 0;
0810 
0811 #ifdef FICLONE
0812     // Share data blocks ("reflink") on supporting filesystems, like brfs and XFS
0813     int ret = ::ioctl(destFile.handle(), FICLONE, srcFile.handle());
0814     if (ret != -1) {
0815         sizeProcessed = srcSize;
0816         processedSize(srcSize);
0817     }
0818     // if fs does not support reflinking, files are on different devices...
0819 #endif
0820 
0821     bool existingDestDeleteAttempted = false;
0822 
0823     processedSize(sizeProcessed);
0824 
0825 #if HAVE_COPY_FILE_RANGE
0826     while (!wasKilled() && sizeProcessed < srcSize) {
0827         if (testMode && destFile.fileName().contains(QLatin1String("slow"))) {
0828             QThread::msleep(50);
0829         }
0830 
0831         const ssize_t copiedBytes = ::copy_file_range(srcFile.handle(), nullptr, destFile.handle(), nullptr, s_maxIPCSize, 0);
0832 
0833         if (copiedBytes == -1) {
0834             // ENOENT is returned on cifs in some cases, probably a kernel bug
0835             // (s.a. https://git.savannah.gnu.org/cgit/coreutils.git/commit/?id=7fc84d1c0f6b35231b0b4577b70aaa26bf548a7c)
0836             if (errno == EINVAL || errno == EXDEV || errno == ENOENT) {
0837                 break; // will continue with next copy mechanism
0838             }
0839 
0840             if (errno == EINTR) { // Interrupted
0841                 continue;
0842             }
0843 
0844             if (errno == ENOSPC) { // disk full
0845                 // attempt to free disk space occupied by file being overwritten
0846                 if (!_destBackup.isEmpty() && !existingDestDeleteAttempted) {
0847                     ::unlink(_destBackup.constData());
0848                     existingDestDeleteAttempted = true;
0849                     continue;
0850                 }
0851                 error(KIO::ERR_DISK_FULL, dest);
0852             } else {
0853                 error(KIO::ERR_WORKER_DEFINED, i18n("Cannot copy file from %1 to %2. (Errno: %3)", src, dest, errno));
0854             }
0855 
0856             if (!QFile::remove(dest)) { // don't keep partly copied file
0857                 execWithElevatedPrivilege(DEL, {_dest}, errno);
0858             }
0859             return;
0860         }
0861 
0862         sizeProcessed += copiedBytes;
0863         processedSize(sizeProcessed);
0864     }
0865 #endif
0866 
0867     /* standard read/write fallback */
0868     if (sizeProcessed < srcSize) {
0869         std::array<char, s_maxIPCSize> buffer;
0870         while (!wasKilled() && sizeProcessed < srcSize) {
0871             if (testMode && destFile.fileName().contains(QLatin1String("slow"))) {
0872                 QThread::msleep(50);
0873             }
0874 
0875             const ssize_t readBytes = ::read(srcFile.handle(), &buffer, s_maxIPCSize);
0876 
0877             if (readBytes == -1) {
0878                 if (errno == EINTR) { // Interrupted
0879                     continue;
0880                 } else {
0881                     qCWarning(KIO_FILE) << "Couldn't read[2]. Error:" << srcFile.errorString();
0882                     error(KIO::ERR_CANNOT_READ, src);
0883                 }
0884 
0885                 if (!QFile::remove(dest)) { // don't keep partly copied file
0886                     execWithElevatedPrivilege(DEL, {_dest}, errno);
0887                 }
0888                 return;
0889             }
0890 
0891             if (destFile.write(buffer.data(), readBytes) != readBytes) {
0892                 if (destFile.error() == QFileDevice::ResourceError) { // disk full
0893                     // attempt to free disk space occupied by file being overwritten
0894                     if (!_destBackup.isEmpty() && !existingDestDeleteAttempted) {
0895                         ::unlink(_destBackup.constData());
0896                         existingDestDeleteAttempted = true;
0897                         if (destFile.write(buffer.data(), readBytes) == readBytes) { // retry
0898                             continue;
0899                         }
0900                     }
0901                     error(KIO::ERR_DISK_FULL, dest);
0902                 } else {
0903                     qCWarning(KIO_FILE) << "Couldn't write[2]. Error:" << destFile.errorString();
0904                     error(KIO::ERR_CANNOT_WRITE, dest);
0905                 }
0906 
0907                 if (!QFile::remove(dest)) { // don't keep partly copied file
0908                     execWithElevatedPrivilege(DEL, {_dest}, errno);
0909                 }
0910                 return;
0911             }
0912             sizeProcessed += readBytes;
0913             processedSize(sizeProcessed);
0914         }
0915     }
0916 
0917     // Copy Extended attributes
0918 #if HAVE_SYS_XATTR_H || HAVE_SYS_EXTATTR_H
0919     if (!copyXattrs(srcFile.handle(), destFile.handle())) {
0920         qCDebug(KIO_FILE) << "can't copy Extended attributes";
0921     }
0922 #endif
0923 
0924     srcFile.close();
0925 
0926     destFile.flush(); // so the write() happens before futimes()
0927 
0928     // copy access and modification time
0929     if (!wasKilled()) {
0930 #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
0931         // with nano secs precision
0932         struct timespec ut[2];
0933         ut[0] = buffSrc.st_atim;
0934         ut[1] = buffSrc.st_mtim;
0935         // need to do this with the dest file still opened, or this fails
0936         if (::futimens(destFile.handle(), ut) != 0) {
0937 #else
0938         struct timeval ut[2];
0939         ut[0].tv_sec = buffSrc.st_atime;
0940         ut[0].tv_usec = 0;
0941         ut[1].tv_sec = buffSrc.st_mtime;
0942         ut[1].tv_usec = 0;
0943         if (::futimes(destFile.handle(), ut) != 0) {
0944 #endif
0945             if (tryChangeFileAttr(UTIME, {_dest, qint64(buffSrc.st_atime), qint64(buffSrc.st_mtime)}, errno)) {
0946                 qCWarning(KIO_FILE) << "Couldn't preserve access and modification time for" << dest;
0947             }
0948         }
0949     }
0950 
0951     destFile.close();
0952 
0953     if (wasKilled()) {
0954         qCDebug(KIO_FILE) << "Clean dest file after ioslave was killed:" << dest;
0955         if (!QFile::remove(dest)) { // don't keep partly copied file
0956             execWithElevatedPrivilege(DEL, {_dest}, errno);
0957         }
0958         error(KIO::ERR_USER_CANCELED, dest);
0959         return;
0960     }
0961 
0962     if (destFile.error() != QFile::NoError) {
0963         qCWarning(KIO_FILE) << "Error when closing file descriptor[2]:" << destFile.errorString();
0964         error(KIO::ERR_CANNOT_WRITE, dest);
0965 
0966         if (!QFile::remove(dest)) { // don't keep partly copied file
0967             execWithElevatedPrivilege(DEL, {_dest}, errno);
0968         }
0969         return;
0970     }
0971 
0972 #if HAVE_POSIX_ACL
0973     // If no special mode is given, preserve the ACL attributes from the source file
0974     if (_mode == -1) {
0975         acl_t acl = acl_get_fd(srcFile.handle());
0976         if (acl && acl_set_file(_dest.data(), ACL_TYPE_ACCESS, acl) != 0) {
0977             qCWarning(KIO_FILE) << "Could not set ACL permissions for" << dest;
0978         }
0979     }
0980 #endif
0981 
0982     // preserve ownership
0983     if (_mode != -1) {
0984         if (::chown(_dest.data(), -1 /*keep user*/, buffSrc.st_gid) == 0) {
0985             // as we are the owner of the new file, we can always change the group, but
0986             // we might not be allowed to change the owner
0987             (void)::chown(_dest.data(), buffSrc.st_uid, -1 /*keep group*/);
0988         } else {
0989             if (tryChangeFileAttr(CHOWN, {_dest, buffSrc.st_uid, buffSrc.st_gid}, errno)) {
0990                 qCWarning(KIO_FILE) << "Couldn't preserve group for" << dest;
0991             }
0992         }
0993     }
0994 
0995     if (!_destBackup.isEmpty()) { // Overwrite final dest file with new file
0996         if (::unlink(_destBackup.constData()) == -1) {
0997             qCWarning(KIO_FILE) << "Couldn't remove original dest" << _destBackup << "(" << strerror(errno) << ")";
0998         }
0999 
1000         if (::rename(_dest.constData(), _destBackup.constData()) == -1) {
1001             qCWarning(KIO_FILE) << "Couldn't rename" << _dest << "to" << _destBackup << "(" << strerror(errno) << ")";
1002         }
1003     }
1004 
1005     processedSize(srcSize);
1006     finished();
1007 }
1008 
1009 static bool isLocalFileSameHost(const QUrl &url)
1010 {
1011     if (!url.isLocalFile()) {
1012         return false;
1013     }
1014 
1015     if (url.host().isEmpty() || (url.host() == QLatin1String("localhost"))) {
1016         return true;
1017     }
1018 
1019     char hostname[256];
1020     hostname[0] = '\0';
1021     if (!gethostname(hostname, 255)) {
1022         hostname[sizeof(hostname) - 1] = '\0';
1023     }
1024 
1025     return (QString::compare(url.host(), QLatin1String(hostname), Qt::CaseInsensitive) == 0);
1026 }
1027 
1028 #if HAVE_SYS_XATTR_H
1029 static bool isNtfsHidden(const QString &filename)
1030 {
1031     constexpr auto attrName = "system.ntfs_attrib_be";
1032     const auto filenameEncoded = QFile::encodeName(filename);
1033 
1034     uint32_t intAttr = 0;
1035     constexpr size_t xattr_size = sizeof(intAttr);
1036     char strAttr[xattr_size];
1037 #ifdef Q_OS_MACOS
1038     auto length = getxattr(filenameEncoded.data(), attrName, strAttr, xattr_size, 0, XATTR_NOFOLLOW);
1039 #else
1040     auto length = getxattr(filenameEncoded.data(), attrName, strAttr, xattr_size);
1041 #endif
1042     if (length <= 0) {
1043         return false;
1044     }
1045 
1046     char *c = strAttr;
1047     for (decltype(length) n = 0; n < length; ++n, ++c) {
1048         intAttr <<= 8;
1049         intAttr |= static_cast<uchar>(*c);
1050     }
1051 
1052     constexpr auto FILE_ATTRIBUTE_HIDDEN = 0x2u;
1053     return static_cast<bool>(intAttr & FILE_ATTRIBUTE_HIDDEN);
1054 }
1055 #endif
1056 
1057 void FileProtocol::listDir(const QUrl &url)
1058 {
1059     if (!isLocalFileSameHost(url)) {
1060         QUrl redir(url);
1061         redir.setScheme(configValue(QStringLiteral("DefaultRemoteProtocol"), QStringLiteral("smb")));
1062         redirection(redir);
1063         // qDebug() << "redirecting to " << redir;
1064         finished();
1065         return;
1066     }
1067     const QString path(url.toLocalFile());
1068     const QByteArray _path(QFile::encodeName(path));
1069     DIR *dp = opendir(_path.data());
1070     if (dp == nullptr) {
1071         switch (errno) {
1072         case ENOENT:
1073             error(KIO::ERR_DOES_NOT_EXIST, path);
1074             return;
1075         case ENOTDIR:
1076             error(KIO::ERR_IS_FILE, path);
1077             break;
1078 #ifdef ENOMEDIUM
1079         case ENOMEDIUM:
1080             error(ERR_WORKER_DEFINED, i18n("No media in device for %1", path));
1081             break;
1082 #endif
1083         default:
1084             error(KIO::ERR_CANNOT_ENTER_DIRECTORY, path);
1085             break;
1086         }
1087         return;
1088     }
1089 
1090     const QByteArray encodedBasePath = _path + '/';
1091 
1092     const KIO::StatDetails details = getStatDetails();
1093     // qDebug() << "========= LIST " << url << "details=" << details << " =========";
1094     UDSEntry entry;
1095 
1096 #ifndef HAVE_DIRENT_D_TYPE
1097     QT_STATBUF st;
1098 #endif
1099     QT_DIRENT *ep;
1100     while ((ep = QT_READDIR(dp)) != nullptr) {
1101         entry.clear();
1102 
1103         const QString filename = QFile::decodeName(ep->d_name);
1104 
1105         /*
1106          * details == 0 (if statement) is the fast code path.
1107          * We only get the file name and type. After that we emit
1108          * the result.
1109          *
1110          * The else statement is the slow path that requests all
1111          * file information in file.cpp. It executes a stat call
1112          * for every entry thus becoming slower.
1113          *
1114          */
1115         if (details == KIO::StatBasic) {
1116             entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename);
1117 #ifdef HAVE_DIRENT_D_TYPE
1118             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, (ep->d_type == DT_DIR) ? S_IFDIR : S_IFREG);
1119             const bool isSymLink = (ep->d_type == DT_LNK);
1120 #else
1121             // oops, no fast way, we need to stat (e.g. on Solaris)
1122             if (QT_LSTAT(ep->d_name, &st) == -1) {
1123                 continue; // how can stat fail?
1124             }
1125             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_ISDIR(st.st_mode) ? S_IFDIR : S_IFREG);
1126             const bool isSymLink = S_ISLNK(st.st_mode);
1127 #endif
1128             if (isSymLink) {
1129                 // for symlinks obey the UDSEntry contract and provide UDS_LINK_DEST
1130                 // even if we don't know the link dest (and DeleteJob doesn't care...)
1131                 entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, QStringLiteral("Dummy Link Target"));
1132             }
1133             listEntry(entry);
1134 
1135         } else {
1136             QString fullPath = Utils::slashAppended(path);
1137             fullPath += filename;
1138 
1139             if (createUDSEntry(filename, encodedBasePath + QByteArray(ep->d_name), entry, details, fullPath)) {
1140 #if HAVE_SYS_XATTR_H
1141                 if (isNtfsHidden(fullPath)) {
1142                     bool ntfsHidden = true;
1143 
1144                     // Bug 392913: NTFS root volume is always "hidden", ignore this
1145                     if (ep->d_type == DT_DIR || ep->d_type == DT_UNKNOWN || ep->d_type == DT_LNK) {
1146                         const QString fullFilePath = QDir(fullPath).canonicalPath();
1147                         auto mountPoint = KMountPoint::currentMountPoints().findByPath(fullFilePath);
1148                         if (mountPoint && mountPoint->mountPoint() == fullFilePath) {
1149                             ntfsHidden = false;
1150                         }
1151                     }
1152 
1153                     if (ntfsHidden) {
1154                         entry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1);
1155                     }
1156                 }
1157 #endif
1158                 listEntry(entry);
1159             }
1160         }
1161     }
1162 
1163     closedir(dp);
1164 
1165     finished();
1166 }
1167 
1168 void FileProtocol::rename(const QUrl &srcUrl, const QUrl &destUrl, KIO::JobFlags _flags)
1169 {
1170     char off_t_should_be_64_bits[sizeof(off_t) >= 8 ? 1 : -1];
1171     (void)off_t_should_be_64_bits;
1172     const QString src = srcUrl.toLocalFile();
1173     const QString dest = destUrl.toLocalFile();
1174     const QByteArray _src(QFile::encodeName(src));
1175     const QByteArray _dest(QFile::encodeName(dest));
1176     QT_STATBUF buff_src;
1177     if (QT_LSTAT(_src.data(), &buff_src) == -1) {
1178         if (errno == EACCES) {
1179             error(KIO::ERR_ACCESS_DENIED, src);
1180         } else {
1181             error(KIO::ERR_DOES_NOT_EXIST, src);
1182         }
1183         return;
1184     }
1185 
1186     QT_STATBUF buff_dest;
1187     // stat symlinks here (lstat, not stat), to avoid ERR_IDENTICAL_FILES when replacing symlink
1188     // with its target (#169547)
1189     bool dest_exists = (QT_LSTAT(_dest.data(), &buff_dest) != -1);
1190     if (dest_exists) {
1191         // Try QFile::rename(), this can help when renaming 'a' to 'A' on a case-insensitive
1192         // filesystem, e.g. FAT32/VFAT.
1193         if (src != dest && QString::compare(src, dest, Qt::CaseInsensitive) == 0) {
1194             qCDebug(KIO_FILE) << "Dest already exists; detected special case of lower/uppercase renaming"
1195                               << "in same dir on a case-insensitive filesystem, try with QFile::rename()"
1196                               << "(which uses 2 rename calls)";
1197             if (QFile::rename(src, dest)) {
1198                 finished();
1199                 return;
1200             }
1201         }
1202 
1203         if (same_inode(buff_dest, buff_src)) {
1204             error(KIO::ERR_IDENTICAL_FILES, dest);
1205             return;
1206         }
1207 
1208         if (S_ISDIR(buff_dest.st_mode)) {
1209             error(KIO::ERR_DIR_ALREADY_EXIST, dest);
1210             return;
1211         }
1212 
1213         if (!(_flags & KIO::Overwrite)) {
1214             error(KIO::ERR_FILE_ALREADY_EXIST, dest);
1215             return;
1216         }
1217     }
1218 
1219     if (::rename(_src.data(), _dest.data()) == -1) {
1220         if (auto err = execWithElevatedPrivilege(RENAME, {_src, _dest}, errno)) {
1221             if (!err.wasCanceled()) {
1222                 if ((err == EACCES) || (err == EPERM)) {
1223                     error(KIO::ERR_WRITE_ACCESS_DENIED, dest);
1224                 } else if (err == EXDEV) {
1225                     error(KIO::ERR_UNSUPPORTED_ACTION, QStringLiteral("rename"));
1226                 } else if (err == EROFS) { // The file is on a read-only filesystem
1227                     error(KIO::ERR_CANNOT_DELETE, src);
1228                 } else {
1229                     error(KIO::ERR_CANNOT_RENAME, src);
1230                 }
1231             }
1232             return;
1233         }
1234     }
1235 
1236     finished();
1237 }
1238 
1239 void FileProtocol::symlink(const QString &target, const QUrl &destUrl, KIO::JobFlags flags)
1240 {
1241     // Assume dest is local too (wouldn't be here otherwise)
1242     const QString dest = destUrl.toLocalFile();
1243     const QByteArray dest_c = QFile::encodeName(dest);
1244 
1245     if (::symlink(QFile::encodeName(target).constData(), dest_c.constData()) == 0) {
1246         finished();
1247         return;
1248     }
1249 
1250     // Does the destination already exist ?
1251     if (errno == EEXIST) {
1252         if (flags & KIO::Overwrite) {
1253             // Try to delete the destination
1254             if (unlink(dest_c.constData()) != 0) {
1255                 if (auto err = execWithElevatedPrivilege(DEL, {dest}, errno)) {
1256                     if (!err.wasCanceled()) {
1257                         error(KIO::ERR_CANNOT_DELETE, dest);
1258                     }
1259 
1260                     return;
1261                 }
1262             }
1263 
1264             // Try again - this won't loop forever since unlink succeeded
1265             symlink(target, destUrl, flags);
1266             return;
1267         } else {
1268             if (QT_STATBUF buff_dest; QT_LSTAT(dest_c.constData(), &buff_dest) == 0) {
1269                 error(S_ISDIR(buff_dest.st_mode) ? KIO::ERR_DIR_ALREADY_EXIST : KIO::ERR_FILE_ALREADY_EXIST, dest);
1270             } else { // Can't happen, we already know "dest" exists
1271                 error(KIO::ERR_CANNOT_SYMLINK, dest);
1272             }
1273 
1274             return;
1275         }
1276     }
1277 
1278     // Permission error, could be that the filesystem doesn't support symlinks
1279     if (errno == EPERM) {
1280         // "dest" doesn't exist, get the filesystem type of the parent dir
1281         const QString parentDir = destUrl.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename).toLocalFile();
1282         const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(parentDir);
1283 
1284         if (fsType == KFileSystemType::Fat || fsType == KFileSystemType::Exfat) {
1285             const QString msg = i18nc(
1286                 "The first arg is the path to the symlink that couldn't be created, the second"
1287                 "arg is the filesystem type (e.g. vfat, exfat)",
1288                 "Could not create symlink \"%1\".\n"
1289                 "The destination filesystem (%2) doesn't support symlinks.",
1290                 dest,
1291                 KFileSystemType::fileSystemName(fsType));
1292 
1293             error(KIO::ERR_WORKER_DEFINED, msg);
1294             return;
1295         }
1296     }
1297 
1298     auto res = execWithElevatedPrivilege(SYMLINK, {dest, target}, errno);
1299     if (res == PrivilegeOperationReturnValue::success()) {
1300         finished();
1301     } else if (!res.wasCanceled()) {
1302         // Some error occurred while we tried to symlink
1303         error(KIO::ERR_CANNOT_SYMLINK, dest);
1304     }
1305 }
1306 
1307 void FileProtocol::del(const QUrl &url, bool isfile)
1308 {
1309     const QString path = url.toLocalFile();
1310     const QByteArray _path(QFile::encodeName(path));
1311     /*****
1312      * Delete files
1313      *****/
1314 
1315     if (isfile) {
1316         // qDebug() << "Deleting file "<< url;
1317 
1318         if (unlink(_path.data()) == -1) {
1319             if (auto err = execWithElevatedPrivilege(DEL, {_path}, errno)) {
1320                 if (!err.wasCanceled()) {
1321                     if ((err == EACCES) || (err == EPERM)) {
1322                         error(KIO::ERR_ACCESS_DENIED, path);
1323                     } else if (err == EISDIR) {
1324                         error(KIO::ERR_IS_DIRECTORY, path);
1325                     } else {
1326                         error(KIO::ERR_CANNOT_DELETE, path);
1327                     }
1328                 }
1329                 return;
1330             }
1331         }
1332     } else {
1333         /*****
1334          * Delete empty directory
1335          *****/
1336 
1337         // qDebug() << "Deleting directory " << url;
1338         if (metaData(QStringLiteral("recurse")) == QLatin1String("true")) {
1339             if (!deleteRecursive(path)) {
1340                 return;
1341             }
1342         }
1343         if (QT_RMDIR(_path.data()) == -1) {
1344             if (auto err = execWithElevatedPrivilege(RMDIR, {_path}, errno)) {
1345                 if (!err.wasCanceled()) {
1346                     if ((err == EACCES) || (err == EPERM)) {
1347                         error(KIO::ERR_ACCESS_DENIED, path);
1348                     } else {
1349                         // qDebug() << "could not rmdir " << perror;
1350                         error(KIO::ERR_CANNOT_RMDIR, path);
1351                     }
1352                 }
1353                 return;
1354             }
1355         }
1356     }
1357 
1358     finished();
1359 }
1360 
1361 void FileProtocol::chown(const QUrl &url, const QString &owner, const QString &group)
1362 {
1363     const QString path = url.toLocalFile();
1364     const QByteArray _path(QFile::encodeName(path));
1365     uid_t uid;
1366     gid_t gid;
1367 
1368     // get uid from given owner
1369     {
1370         struct passwd *p = ::getpwnam(owner.toLocal8Bit().constData());
1371 
1372         if (!p) {
1373             error(KIO::ERR_WORKER_DEFINED, i18n("Could not get user id for given user name %1", owner));
1374             return;
1375         }
1376 
1377         uid = p->pw_uid;
1378     }
1379 
1380     // get gid from given group
1381     {
1382         struct group *p = ::getgrnam(group.toLocal8Bit().constData());
1383 
1384         if (!p) {
1385             error(KIO::ERR_WORKER_DEFINED, i18n("Could not get group id for given group name %1", group));
1386             return;
1387         }
1388 
1389         gid = p->gr_gid;
1390     }
1391 
1392     if (::chown(_path.constData(), uid, gid) == -1) {
1393         if (auto err = execWithElevatedPrivilege(CHOWN, {_path, uid, gid}, errno)) {
1394             if (!err.wasCanceled()) {
1395                 switch (err) {
1396                 case EPERM:
1397                 case EACCES:
1398                     error(KIO::ERR_ACCESS_DENIED, path);
1399                     break;
1400                 case ENOSPC:
1401                     error(KIO::ERR_DISK_FULL, path);
1402                     break;
1403                 default:
1404                     error(KIO::ERR_CANNOT_CHOWN, path);
1405                 }
1406             }
1407         }
1408     } else {
1409         finished();
1410     }
1411 }
1412 
1413 KIO::StatDetails FileProtocol::getStatDetails()
1414 {
1415     // takes care of converting old metadata details to new StatDetails
1416     // TODO KF6 : remove legacy "details" code path
1417     KIO::StatDetails details;
1418 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69)
1419     if (hasMetaData(QStringLiteral("statDetails"))) {
1420 #endif
1421         const QString statDetails = metaData(QStringLiteral("statDetails"));
1422         details = statDetails.isEmpty() ? KIO::StatDefaultDetails : static_cast<KIO::StatDetails>(statDetails.toInt());
1423 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69)
1424     } else {
1425         const QString sDetails = metaData(QStringLiteral("details"));
1426         details = sDetails.isEmpty() ? KIO::StatDefaultDetails : KIO::detailsToStatDetails(sDetails.toInt());
1427     }
1428 #endif
1429     return details;
1430 }
1431 
1432 void FileProtocol::stat(const QUrl &url)
1433 {
1434     if (!isLocalFileSameHost(url)) {
1435         redirect(url);
1436         return;
1437     }
1438 
1439     /* directories may not have a slash at the end if
1440      * we want to stat() them; it requires that we
1441      * change into it .. which may not be allowed
1442      * stat("/is/unaccessible")  -> rwx------
1443      * stat("/is/unaccessible/") -> EPERM            H.Z.
1444      * This is the reason for the -1
1445      */
1446     const QString path(url.adjusted(QUrl::StripTrailingSlash).toLocalFile());
1447     const QByteArray _path(QFile::encodeName(path));
1448 
1449     const KIO::StatDetails details = getStatDetails();
1450 
1451     UDSEntry entry;
1452     if (!createUDSEntry(url.fileName(), _path, entry, details, path)) {
1453         error(KIO::ERR_DOES_NOT_EXIST, path);
1454         return;
1455     }
1456     statEntry(entry);
1457 
1458     finished();
1459 }
1460 
1461 PrivilegeOperationReturnValue FileProtocol::execWithElevatedPrivilege(ActionType action, const QVariantList &args, int errcode)
1462 {
1463     if (privilegeOperationUnitTestMode()) {
1464         return PrivilegeOperationReturnValue::success();
1465     }
1466 
1467     // temporarily disable privilege execution
1468     if (true) {
1469         return PrivilegeOperationReturnValue::failure(errcode);
1470     }
1471 
1472     if (!(errcode == EACCES || errcode == EPERM)) {
1473         return PrivilegeOperationReturnValue::failure(errcode);
1474     }
1475 
1476     const QString operationDetails = actionDetails(action, args);
1477     KIO::PrivilegeOperationStatus opStatus = requestPrivilegeOperation(operationDetails);
1478     if (opStatus != KIO::OperationAllowed) {
1479         if (opStatus == KIO::OperationCanceled) {
1480             error(KIO::ERR_USER_CANCELED, QString());
1481             return PrivilegeOperationReturnValue::canceled();
1482         }
1483         return PrivilegeOperationReturnValue::failure(errcode);
1484     }
1485 
1486     const QUrl targetUrl = QUrl::fromLocalFile(args.first().toString()); // target is always the first item.
1487     const bool useParent = action != CHOWN && action != CHMOD && action != UTIME;
1488     const QString targetPath = useParent ? targetUrl.adjusted(QUrl::RemoveFilename).toLocalFile() : targetUrl.toLocalFile();
1489     bool userIsOwner = QFileInfo(targetPath).ownerId() == getuid();
1490     if (action == RENAME) { // for rename check src and dest owner
1491         QString dest = QUrl(args[1].toString()).toLocalFile();
1492         userIsOwner = userIsOwner && QFileInfo(dest).ownerId() == getuid();
1493     }
1494     if (userIsOwner) {
1495         error(KIO::ERR_PRIVILEGE_NOT_REQUIRED, targetPath);
1496         return PrivilegeOperationReturnValue::canceled();
1497     }
1498 
1499     QByteArray helperArgs;
1500     QDataStream out(&helperArgs, QIODevice::WriteOnly);
1501     out << action;
1502     for (const QVariant &arg : args) {
1503         out << arg;
1504     }
1505 
1506     const QString actionId = QStringLiteral("org.kde.kio.file.exec");
1507     KAuth::Action execAction(actionId);
1508     execAction.setHelperId(QStringLiteral("org.kde.kio.file"));
1509 
1510     QVariantMap argv;
1511     argv.insert(QStringLiteral("arguments"), helperArgs);
1512     execAction.setArguments(argv);
1513 
1514     auto reply = execAction.execute();
1515     if (reply->exec()) {
1516         addTemporaryAuthorization(actionId);
1517         return PrivilegeOperationReturnValue::success();
1518     }
1519 
1520     return PrivilegeOperationReturnValue::failure(KIO::ERR_ACCESS_DENIED);
1521 }
1522 
1523 int FileProtocol::setACL(const char *path, mode_t perm, bool directoryDefault)
1524 {
1525     int ret = 0;
1526 #if HAVE_POSIX_ACL
1527 
1528     const QString ACLString = metaData(QStringLiteral("ACL_STRING"));
1529     const QString defaultACLString = metaData(QStringLiteral("DEFAULT_ACL_STRING"));
1530     // Empty strings mean leave as is
1531     if (!ACLString.isEmpty()) {
1532         acl_t acl = nullptr;
1533         if (ACLString == QLatin1String("ACL_DELETE")) {
1534             // user told us to delete the extended ACL, so let's write only
1535             // the minimal (UNIX permission bits) part
1536             acl = ACLPortability::acl_from_mode(perm);
1537         }
1538         acl = acl_from_text(ACLString.toLatin1().constData());
1539         if (acl_valid(acl) == 0) { // let's be safe
1540             ret = acl_set_file(path, ACL_TYPE_ACCESS, acl);
1541             // qDebug() << "Set ACL on:" << path << "to:" << aclToText(acl);
1542         }
1543         acl_free(acl);
1544         if (ret != 0) {
1545             return ret; // better stop trying right away
1546         }
1547     }
1548 
1549     if (directoryDefault && !defaultACLString.isEmpty()) {
1550         if (defaultACLString == QLatin1String("ACL_DELETE")) {
1551             // user told us to delete the default ACL, do so
1552             ret += acl_delete_def_file(path);
1553         } else {
1554             acl_t acl = acl_from_text(defaultACLString.toLatin1().constData());
1555             if (acl_valid(acl) == 0) { // let's be safe
1556                 ret += acl_set_file(path, ACL_TYPE_DEFAULT, acl);
1557                 // qDebug() << "Set Default ACL on:" << path << "to:" << aclToText(acl);
1558             }
1559             acl_free(acl);
1560         }
1561     }
1562 #else
1563     Q_UNUSED(path);
1564     Q_UNUSED(perm);
1565     Q_UNUSED(directoryDefault);
1566 #endif
1567     return ret;
1568 }