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