diff --git a/src/ioslaves/file/file.cpp b/src/ioslaves/file/file.cpp index 6c528852..cffe93e7 100644 --- a/src/ioslaves/file/file.cpp +++ b/src/ioslaves/file/file.cpp @@ -1,1385 +1,1450 @@ /* Copyright (C) 2000-2002 Stephan Kulow Copyright (C) 2000-2002 David Faure Copyright (C) 2000-2002 Waldo Bastian Copyright (C) 2006 Allan Sandfeld Jensen Copyright (C) 2007 Thiago Macieira This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef QT_NO_CAST_FROM_ASCII #define QT_NO_CAST_FROM_ASCII #endif #include "file.h" #include #include #include #include #include "kioglobal_p.h" #include #include #ifdef Q_OS_WIN #include #include #include //struct timeval #else #include #endif #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #include #endif #include #include #include #include #include #include #include #include #if HAVE_VOLMGT #include #include #endif #include #include +#include "fdreceiver.h" + Q_LOGGING_CATEGORY(KIO_FILE, "kf5.kio.kio_file") // Pseudo plugin class to embed meta data class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.file" FILE "file.json") }; using namespace KIO; #define MAX_IPC_SIZE (1024*32) static QString readLogFile(const QByteArray &_filename); #if HAVE_POSIX_ACL static void appendACLAtoms(const QByteArray &path, UDSEntry &entry, mode_t type); #endif extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) { QCoreApplication app(argc, argv); // needed for QSocketNotifier app.setApplicationName(QStringLiteral("kio_file")); if (argc != 4) { fprintf(stderr, "Usage: kio_file protocol domain-socket1 domain-socket2\n"); exit(-1); } FileProtocol slave(argv[2], argv[3]); // Make sure the first kDebug is after the slave ctor (which sets a SIGPIPE handler) // This is useful in case kdeinit was autostarted by another app, which then exited and closed fd2 // (e.g. ctest does that, or closing the terminal window would do that) //qDebug() << "Starting" << getpid(); slave.dispatchLoop(); //qDebug() << "Done"; return 0; } static QFile::Permissions modeToQFilePermissions(int mode) { QFile::Permissions perms; if (mode & S_IRUSR) { perms |= QFile::ReadOwner; } if (mode & S_IWUSR) { perms |= QFile::WriteOwner; } if (mode & S_IXUSR) { perms |= QFile::ExeOwner; } if (mode & S_IRGRP) { perms |= QFile::ReadGroup; } if (mode & S_IWGRP) { perms |= QFile::WriteGroup; } if (mode & S_IXGRP) { perms |= QFile::ExeGroup; } if (mode & S_IROTH) { perms |= QFile::ReadOther; } if (mode & S_IWOTH) { perms |= QFile::WriteOther; } if (mode & S_IXOTH) { perms |= QFile::ExeOther; } return perms; } FileProtocol::FileProtocol(const QByteArray &pool, const QByteArray &app) : SlaveBase(QByteArrayLiteral("file"), pool, app), mFile(nullptr) { } FileProtocol::~FileProtocol() { } #if HAVE_POSIX_ACL static QString aclToText(acl_t acl) { ssize_t size = 0; char *txt = acl_to_text(acl, &size); const QString ret = QString::fromLatin1(txt, size); acl_free(txt); return ret; } #endif int FileProtocol::setACL(const char *path, mode_t perm, bool directoryDefault) { int ret = 0; #if HAVE_POSIX_ACL const QString ACLString = metaData(QStringLiteral("ACL_STRING")); const QString defaultACLString = metaData(QStringLiteral("DEFAULT_ACL_STRING")); // Empty strings mean leave as is if (!ACLString.isEmpty()) { acl_t acl = nullptr; if (ACLString == QLatin1String("ACL_DELETE")) { // user told us to delete the extended ACL, so let's write only // the minimal (UNIX permission bits) part acl = acl_from_mode(perm); } acl = acl_from_text(ACLString.toLatin1()); if (acl_valid(acl) == 0) { // let's be safe ret = acl_set_file(path, ACL_TYPE_ACCESS, acl); // qDebug() << "Set ACL on:" << path << "to:" << aclToText(acl); } acl_free(acl); if (ret != 0) { return ret; // better stop trying right away } } if (directoryDefault && !defaultACLString.isEmpty()) { if (defaultACLString == QLatin1String("ACL_DELETE")) { // user told us to delete the default ACL, do so ret += acl_delete_def_file(path); } else { acl_t acl = acl_from_text(defaultACLString.toLatin1()); if (acl_valid(acl) == 0) { // let's be safe ret += acl_set_file(path, ACL_TYPE_DEFAULT, acl); // qDebug() << "Set Default ACL on:" << path << "to:" << aclToText(acl); } acl_free(acl); } } #else Q_UNUSED(path); Q_UNUSED(perm); Q_UNUSED(directoryDefault); #endif return ret; } void FileProtocol::chmod(const QUrl &url, int permissions) { const QString path(url.toLocalFile()); const QByteArray _path(QFile::encodeName(path)); /* FIXME: Should be atomic */ #ifdef Q_OS_UNIX // QFile::Permissions does not support special attributes like sticky if (::chmod(_path.constData(), permissions) == -1 || #else if (!QFile::setPermissions(path, modeToQFilePermissions(permissions)) || #endif (setACL(_path.data(), permissions, false) == -1) || /* if not a directory, cannot set default ACLs */ (setACL(_path.data(), permissions, true) == -1 && errno != ENOTDIR)) { - - switch (errno) { - case EPERM: - case EACCES: - error(KIO::ERR_ACCESS_DENIED, path); - break; -#if defined(ENOTSUP) - case ENOTSUP: // from setACL since chmod can't return ENOTSUP - error(KIO::ERR_UNSUPPORTED_ACTION, i18n("Setting ACL for %1", path)); - break; -#endif - case ENOSPC: - error(KIO::ERR_DISK_FULL, path); - break; - default: - error(KIO::ERR_CANNOT_CHMOD, path); + if (auto err = execWithElevatedPrivilege(CHMOD, _path, permissions)) { + if (!err.wasCanceled()) { + switch (errno) { + case EPERM: + case EACCES: + error(KIO::ERR_ACCESS_DENIED, path); + break; + #if defined(ENOTSUP) + case ENOTSUP: // from setACL since chmod can't return ENOTSUP + error(KIO::ERR_UNSUPPORTED_ACTION, i18n("Setting ACL for %1", path)); + break; + #endif + case ENOSPC: + error(KIO::ERR_DISK_FULL, path); + break; + default: + error(KIO::ERR_CANNOT_CHMOD, path); + } + return; + } } - } else { - finished(); } + + finished(); } void FileProtocol::setModificationTime(const QUrl &url, const QDateTime &mtime) { const QString path(url.toLocalFile()); QT_STATBUF statbuf; if (QT_LSTAT(QFile::encodeName(path).constData(), &statbuf) == 0) { struct utimbuf utbuf; utbuf.actime = statbuf.st_atime; // access time, unchanged utbuf.modtime = mtime.toTime_t(); // modification time if (::utime(QFile::encodeName(path).constData(), &utbuf) != 0) { - // TODO: errno could be EACCES, EPERM, EROFS - error(KIO::ERR_CANNOT_SETTIME, path); + if (auto err = execWithElevatedPrivilege(UTIME, path, qint64(utbuf.actime), qint64(utbuf.modtime))) { + if (!err.wasCanceled()) { + // TODO: errno could be EACCES, EPERM, EROFS + error(KIO::ERR_CANNOT_SETTIME, path); + } + } } else { finished(); } } else { error(KIO::ERR_DOES_NOT_EXIST, path); } } void FileProtocol::mkdir(const QUrl &url, int permissions) { const QString path(url.toLocalFile()); // qDebug() << path << "permission=" << permissions; // Remove existing file or symlink, if requested (#151851) if (metaData(QStringLiteral("overwrite")) == QLatin1String("true")) { - QFile::remove(path); + if (!QFile::remove(path)) { + execWithElevatedPrivilege(DEL, path); + } } QT_STATBUF buff; if (QT_LSTAT(QFile::encodeName(path).constData(), &buff) == -1) { - if (!QDir().mkdir(path)) { - //TODO: add access denied & disk full (or another reasons) handling (into Qt, possibly) - error(KIO::ERR_CANNOT_MKDIR, path); - return; - } else { + bool dirCreated; + if (!(dirCreated = QDir().mkdir(path))) { + if (auto err = execWithElevatedPrivilege(MKDIR, path)) { + if (!err.wasCanceled()) { + //TODO: add access denied & disk full (or another reasons) handling (into Qt, possibly) + error(KIO::ERR_CANNOT_MKDIR, path); + } + return; + } + dirCreated = true; + } + + if (dirCreated) { if (permissions != -1) { chmod(url, permissions); } else { finished(); } return; } } if ((buff.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { // qDebug() << "ERR_DIR_ALREADY_EXIST"; error(KIO::ERR_DIR_ALREADY_EXIST, path); return; } error(KIO::ERR_FILE_ALREADY_EXIST, path); return; } void FileProtocol::get(const QUrl &url) { if (!url.isLocalFile()) { QUrl redir(url); redir.setScheme(config()->readEntry("DefaultRemoteProtocol", "smb")); redirection(redir); finished(); return; } const QString path(url.toLocalFile()); QT_STATBUF buff; if (QT_STAT(QFile::encodeName(path).constData(), &buff) == -1) { if (errno == EACCES) { error(KIO::ERR_ACCESS_DENIED, path); } else { error(KIO::ERR_DOES_NOT_EXIST, path); } return; } if ((buff.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { error(KIO::ERR_IS_DIRECTORY, path); return; } if ((buff.st_mode & QT_STAT_MASK) != QT_STAT_REG) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, path); return; } QFile f(path); if (!f.open(QIODevice::ReadOnly)) { - error(KIO::ERR_CANNOT_OPEN_FOR_READING, path); - return; + if (auto err = tryOpen(f, QFile::encodeName(path), O_RDONLY, S_IRUSR)) { + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_OPEN_FOR_READING, path); + } + return; + } } #if HAVE_FADVISE //TODO check return code posix_fadvise(f.handle(), 0, 0, POSIX_FADV_SEQUENTIAL); #endif // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work) // In real "remote" slaves, this is usually done using mimeTypeForFileNameAndData // after receiving some data. But we don't know how much data the mimemagic rules // need, so for local files, better use mimeTypeForFile. QMimeDatabase db; QMimeType mt = db.mimeTypeForFile(url.toLocalFile()); emit mimeType(mt.name()); // Emit total size AFTER mimetype totalSize(buff.st_size); KIO::filesize_t processed_size = 0; QString resumeOffset = metaData(QStringLiteral("range-start")); if (resumeOffset.isEmpty()) { resumeOffset = metaData(QStringLiteral("resume")); // old name } if (!resumeOffset.isEmpty()) { bool ok; KIO::fileoffset_t offset = resumeOffset.toLongLong(&ok); if (ok && (offset > 0) && (offset < buff.st_size)) { if (f.seek(offset)) { canResume(); processed_size = offset; // qDebug() << "Resume offset:" << KIO::number(offset); } } } char buffer[ MAX_IPC_SIZE ]; QByteArray array; while (1) { int n = f.read(buffer, MAX_IPC_SIZE); if (n == -1) { if (errno == EINTR) { continue; } error(KIO::ERR_CANNOT_READ, path); f.close(); return; } if (n == 0) { break; // Finished } array = QByteArray::fromRawData(buffer, n); data(array); array.clear(); processed_size += n; processedSize(processed_size); //qDebug() << "Processed: " << KIO::number (processed_size); } data(QByteArray()); f.close(); processedSize(buff.st_size); finished(); } void FileProtocol::open(const QUrl &url, QIODevice::OpenMode mode) { // qDebug() << url; QString openPath = url.toLocalFile(); QT_STATBUF buff; if (QT_STAT(QFile::encodeName(openPath).constData(), &buff) == -1) { if (errno == EACCES) { error(KIO::ERR_ACCESS_DENIED, openPath); } else { error(KIO::ERR_DOES_NOT_EXIST, openPath); } return; } if ((buff.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { error(KIO::ERR_IS_DIRECTORY, openPath); return; } if ((buff.st_mode & QT_STAT_MASK) != QT_STAT_REG) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, openPath); return; } mFile = new QFile(openPath); if (!mFile->open(mode)) { if (mode & QIODevice::ReadOnly) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, openPath); } else { error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, openPath); } return; } // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work). // If we're not opening the file ReadOnly or ReadWrite, don't attempt to // read the file and send the mimetype. if (mode & QIODevice::ReadOnly) { QMimeDatabase db; QMimeType mt = db.mimeTypeForFile(url.toLocalFile()); emit mimeType(mt.name()); } totalSize(buff.st_size); position(0); emit opened(); } void FileProtocol::read(KIO::filesize_t bytes) { // qDebug() << "File::open -- read"; Q_ASSERT(mFile && mFile->isOpen()); QVarLengthArray buffer(bytes); while (true) { QByteArray res = mFile->read(bytes); if (!res.isEmpty()) { data(res); bytes -= res.size(); } else { // empty array designates eof data(QByteArray()); if (!mFile->atEnd()) { error(KIO::ERR_CANNOT_READ, mFile->fileName()); close(); } break; } if (bytes <= 0) { break; } } } void FileProtocol::write(const QByteArray &data) { // qDebug() << "File::open -- write"; Q_ASSERT(mFile && mFile->isWritable()); if (mFile->write(data) != data.size()) { if (mFile->error() == QFileDevice::ResourceError) { // disk full error(KIO::ERR_DISK_FULL, mFile->fileName()); close(); } else { qCWarning(KIO_FILE) << "Couldn't write. Error:" << mFile->errorString(); error(KIO::ERR_CANNOT_WRITE, mFile->fileName()); close(); } } else { written(data.size()); } } void FileProtocol::seek(KIO::filesize_t offset) { // qDebug() << "File::open -- seek"; Q_ASSERT(mFile && mFile->isOpen()); if (mFile->seek(offset)) { position(offset); } else { error(KIO::ERR_CANNOT_SEEK, mFile->fileName()); close(); } } void FileProtocol::close() { // qDebug() << "File::open -- close "; Q_ASSERT(mFile); delete mFile; mFile = nullptr; finished(); } void FileProtocol::put(const QUrl &url, int _mode, KIO::JobFlags _flags) { + if (privilegeOperationUnitTestMode()) { + finished(); + return; + } + const QString dest_orig = url.toLocalFile(); // qDebug() << dest_orig << "mode=" << _mode; QString dest_part(dest_orig + QLatin1String(".part")); QT_STATBUF buff_orig; const bool bOrigExists = (QT_LSTAT(QFile::encodeName(dest_orig).constData(), &buff_orig) != -1); bool bPartExists = false; const bool bMarkPartial = config()->readEntry("MarkPartial", true); if (bMarkPartial) { QT_STATBUF buff_part; bPartExists = (QT_LSTAT(QFile::encodeName(dest_part).constData(), &buff_part) != -1); if (bPartExists && !(_flags & KIO::Resume) && !(_flags & KIO::Overwrite) && buff_part.st_size > 0 && ((buff_part.st_mode & QT_STAT_MASK) == QT_STAT_REG)) { // qDebug() << "calling canResume with" << KIO::number(buff_part.st_size); // Maybe we can use this partial file for resuming // Tell about the size we have, and the app will tell us // if it's ok to resume or not. _flags |= canResume(buff_part.st_size) ? KIO::Resume : KIO::DefaultFlags; // qDebug() << "got answer" << (_flags & KIO::Resume); } } if (bOrigExists && !(_flags & KIO::Overwrite) && !(_flags & KIO::Resume)) { if ((buff_orig.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { error(KIO::ERR_DIR_ALREADY_EXIST, dest_orig); } else { error(KIO::ERR_FILE_ALREADY_EXIST, dest_orig); } return; } int result; QString dest; QByteArray _dest; QFile f; // Loop until we got 0 (end of data) do { QByteArray buffer; dataReq(); // Request for data result = readData(buffer); if (result >= 0) { if (dest.isEmpty()) { if (bMarkPartial) { // qDebug() << "Appending .part extension to" << dest_orig; dest = dest_part; if (bPartExists && !(_flags & KIO::Resume)) { // qDebug() << "Deleting partial file" << dest_part; QFile::remove(dest_part); // Catch errors when we try to open the file. } } else { dest = dest_orig; if (bOrigExists && !(_flags & KIO::Resume)) { // qDebug() << "Deleting destination file" << dest_orig; QFile::remove(dest_orig); // Catch errors when we try to open the file. } } f.setFileName(dest); if ((_flags & KIO::Resume)) { f.open(QIODevice::ReadWrite | QIODevice::Append); } else { f.open(QIODevice::Truncate | QIODevice::WriteOnly); if (_mode != -1) { // WABA: Make sure that we keep writing permissions ourselves, // otherwise we can be in for a surprise on NFS. mode_t initialMode = _mode | S_IWUSR | S_IRUSR; f.setPermissions(modeToQFilePermissions(initialMode)); } } if (!f.isOpen()) { - // qDebug() << "####################### COULD NOT WRITE" << dest << "_mode=" << _mode; - // qDebug() << "QFile error==" << f.error() << "(" << f.errorString() << ")"; + int oflags = 0; + int filemode = _mode; - if (f.error() == QFileDevice::PermissionsError) { - error(KIO::ERR_WRITE_ACCESS_DENIED, dest); + if ((_flags & KIO::Resume)) { + oflags = O_RDWR | O_APPEND; } else { - error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest); + oflags = O_WRONLY | O_TRUNC | O_CREAT; + if (_mode != -1) { + filemode = _mode | S_IWUSR | S_IRUSR; + } + } + + if (auto err = tryOpen(f, QFile::encodeName(dest), oflags, filemode)) { + if (!err.wasCanceled()) { + // qDebug() << "####################### COULD NOT WRITE" << dest << "_mode=" << _mode; + // qDebug() << "QFile error==" << f.error() << "(" << f.errorString() << ")"; + + if (f.error() == QFileDevice::PermissionsError) { + error(KIO::ERR_WRITE_ACCESS_DENIED, dest); + } else { + error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest); + } + } + return; + } else { + if ((_flags & KIO::Resume)) { + execWithElevatedPrivilege(CHOWN, dest, getuid(), getgid()); + QFile::setPermissions(dest, modeToQFilePermissions(filemode)); + } } - return; } } if (f.write(buffer) == -1) { if (f.error() == QFile::ResourceError) { // disk full error(KIO::ERR_DISK_FULL, dest_orig); result = -2; // means: remove dest file } else { qCWarning(KIO_FILE) << "Couldn't write. Error:" << f.errorString(); error(KIO::ERR_CANNOT_WRITE, dest_orig); result = -1; } } } } while (result > 0); // An error occurred deal with it. if (result < 0) { // qDebug() << "Error during 'put'. Aborting."; if (f.isOpen()) { f.close(); QT_STATBUF buff; if (QT_STAT(QFile::encodeName(dest).constData(), &buff) == 0) { int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); if (buff.st_size < size) { QFile::remove(dest); } } } ::exit(255); } if (!f.isOpen()) { // we got nothing to write out, so we never opened the file finished(); return; } f.close(); if (f.error() != QFile::NoError) { qCWarning(KIO_FILE) << "Error when closing file descriptor:" << f.errorString(); error(KIO::ERR_CANNOT_WRITE, dest_orig); return; } // after full download rename the file back to original name if (bMarkPartial) { //QFile::rename() never overwrites the destination file unlike ::remove, //so we must remove it manually first if (_flags & KIO::Overwrite) { - QFile::remove(dest_orig); + if (!QFile::remove(dest_orig)) { + execWithElevatedPrivilege(DEL, dest_orig); + } } if (!QFile::rename(dest, dest_orig)) { - qCWarning(KIO_FILE) << " Couldn't rename " << dest << " to " << dest_orig; - error(KIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig); - return; + if (auto err = execWithElevatedPrivilege(RENAME, dest, dest_orig)) { + if (!err.wasCanceled()) { + qCWarning(KIO_FILE) << " Couldn't rename " << dest << " to " << dest_orig; + error(KIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig); + } + return; + } } org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(dest), QUrl::fromLocalFile(dest_orig)); } // set final permissions if (_mode != -1 && !(_flags & KIO::Resume)) { if (!QFile::setPermissions(dest_orig, modeToQFilePermissions(_mode))) { // couldn't chmod. Eat the error if the filesystem apparently doesn't support it. - KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(dest_orig); - if (mp && mp->testFileSystemFlag(KMountPoint::SupportsChmod)) { + if (tryChangeFileAttr(CHMOD, dest_orig, _mode)) { warning(i18n("Could not change permissions for\n%1", dest_orig)); } } } // set modification time const QString mtimeStr = metaData(QStringLiteral("modified")); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { QT_STATBUF dest_statbuf; if (QT_STAT(QFile::encodeName(dest_orig).constData(), &dest_statbuf) == 0) { #ifndef Q_OS_WIN struct timeval utbuf[2]; // access time utbuf[0].tv_sec = dest_statbuf.st_atime; // access time, unchanged ## TODO preserve msec utbuf[0].tv_usec = 0; // modification time utbuf[1].tv_sec = dt.toTime_t(); utbuf[1].tv_usec = dt.time().msec() * 1000; utimes(QFile::encodeName(dest_orig).constData(), utbuf); #else struct utimbuf utbuf; utbuf.actime = dest_statbuf.st_atime; utbuf.modtime = dt.toTime_t(); - utime(QFile::encodeName(dest_orig).constData(), &utbuf); + if (utime(QFile::encodeName(dest_orig).constData(), &utbuf) != 0) { + tryChangeFileAttr(UTIME, dest_orig, qint64(utbuf.actime), qint64(utbuf.modtime)); + } #endif } } } // We have done our job => finish finished(); } QString FileProtocol::getUserName(KUserId uid) const { if (Q_UNLIKELY(!uid.isValid())) { return QString(); } if (!mUsercache.contains(uid)) { KUser user(uid); QString name = user.loginName(); if (name.isEmpty()) { name = uid.toString(); } mUsercache.insert(uid, name); return name; } return mUsercache[uid]; } QString FileProtocol::getGroupName(KGroupId gid) const { if (Q_UNLIKELY(!gid.isValid())) { return QString(); } if (!mGroupcache.contains(gid)) { KUserGroup group(gid); QString name = group.name(); if (name.isEmpty()) { name = gid.toString(); } mGroupcache.insert(gid, name); return name; } return mGroupcache[gid]; } bool FileProtocol::createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, short int details) { assert(entry.count() == 0); // by contract :-) entry.reserve(8); entry.insert(KIO::UDSEntry::UDS_NAME, filename); mode_t type; mode_t access; QT_STATBUF buff; if (QT_LSTAT(path.data(), &buff) == 0) { if (details > 2) { entry.insert(KIO::UDSEntry::UDS_DEVICE_ID, buff.st_dev); entry.insert(KIO::UDSEntry::UDS_INODE, buff.st_ino); } if ((buff.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { #ifdef Q_OS_WIN const QString linkTarget = QFile::symLinkTarget(QFile::decodeName(path)); #else // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927) const off_t lowerLimit = 1; const off_t upperLimit = 1024; size_t bufferSize = qBound(lowerLimit, buff.st_size, upperLimit); QByteArray linkTargetBuffer; linkTargetBuffer.resize(bufferSize); while (true) { ssize_t n = readlink(path.constData(), linkTargetBuffer.data(), bufferSize); if (n < 0 && errno != ERANGE) { qCWarning(KIO_FILE) << "readlink failed!" << path; return false; } else if (n > 0 && static_cast(n) != bufferSize) { linkTargetBuffer.truncate(n); break; } bufferSize *= 2; linkTargetBuffer.resize(bufferSize); } const QString linkTarget = QFile::decodeName(linkTargetBuffer); #endif entry.insert(KIO::UDSEntry::UDS_LINK_DEST, linkTarget); // A symlink -> follow it only if details>1 if (details > 1 && QT_STAT(path.constData(), &buff) == -1) { // It is a link pointing to nowhere type = S_IFMT - 1; access = S_IRWXU | S_IRWXG | S_IRWXO; entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, type); entry.insert(KIO::UDSEntry::UDS_ACCESS, access); entry.insert(KIO::UDSEntry::UDS_SIZE, 0LL); goto notype; } } } else { // qCWarning(KIO_FILE) << "lstat didn't work on " << path.data(); return false; } type = buff.st_mode & S_IFMT; // extract file type access = buff.st_mode & 07777; // extract permissions entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, type); entry.insert(KIO::UDSEntry::UDS_ACCESS, access); entry.insert(KIO::UDSEntry::UDS_SIZE, buff.st_size); #if HAVE_POSIX_ACL if (details > 1) { /* Append an atom indicating whether the file has extended acl information * and if withACL is specified also one with the acl itself. If it's a directory * and it has a default ACL, also append that. */ appendACLAtoms(path, entry, type); } #endif notype: if (details > 0) { entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime); #ifndef Q_OS_WIN entry.insert(KIO::UDSEntry::UDS_USER, getUserName(KUserId(buff.st_uid))); entry.insert(KIO::UDSEntry::UDS_GROUP, getGroupName(KGroupId(buff.st_gid))); #else #pragma message("TODO: st_uid and st_gid are always zero, use GetSecurityInfo to find the owner") #endif entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime); #ifdef st_birthtime /* For example FreeBSD's and NetBSD's stat contains a field for * the inode birth time: st_birthtime * This however only works on UFS and ZFS, and not, on say, NFS. * Instead of setting a bogus fallback like st_mtime, only use * it if it is greater than 0. */ if (buff.st_birthtime > 0) { entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, buff.st_birthtime); } #endif #ifdef __st_birthtime /* As above, but OpenBSD calls it slightly differently. */ if (buff.__st_birthtime > 0) { entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, buff.__st_birthtime); } #endif } // Note: buff.st_ctime isn't the creation time ! // We made that mistake for KDE 2.0, but it's in fact the // "file status" change time, which we don't care about. // For FreeBSD and NetBSD, use st_birthtime. For OpenBSD, // use __st_birthtime. return true; } void FileProtocol::special(const QByteArray &data) { int tmp; QDataStream stream(data); stream >> tmp; switch (tmp) { case 1: { QString fstype, dev, point; qint8 iRo; stream >> iRo >> fstype >> dev >> point; bool ro = (iRo != 0); // qDebug() << "MOUNTING fstype=" << fstype << " dev=" << dev << " point=" << point << " ro=" << ro; bool ok = pmount(dev); if (ok) { finished(); } else { mount(ro, fstype.toLatin1().constData(), dev, point); } } break; case 2: { QString point; stream >> point; bool ok = pumount(point); if (ok) { finished(); } else { unmount(point); } } break; default: break; } } static QStringList fallbackSystemPath() { return QStringList() << QStringLiteral("/sbin") << QStringLiteral("/bin"); } void FileProtocol::mount(bool _ro, const char *_fstype, const QString &_dev, const QString &_point) { // qDebug() << "fstype=" << _fstype; #ifndef _WIN32_WCE #if HAVE_VOLMGT /* * support for Solaris volume management */ QString err; QByteArray devname = QFile::encodeName(_dev); if (volmgt_running()) { // qDebug() << "VOLMGT: vold ok."; if (volmgt_check(devname.data()) == 0) { // qDebug() << "VOLMGT: no media in " << devname.data(); err = i18n("No Media inserted or Media not recognized."); error(KIO::ERR_CANNOT_MOUNT, err); return; } else { // qDebug() << "VOLMGT: " << devname.data() << ": media ok"; finished(); return; } } else { err = i18n("\"vold\" is not running."); // qDebug() << "VOLMGT: " << err; error(KIO::ERR_CANNOT_MOUNT, err); return; } #else QTemporaryFile tmpFile; tmpFile.setAutoRemove(false); tmpFile.open(); QByteArray tmpFileName = QFile::encodeName(tmpFile.fileName()); QByteArray dev; if (_dev.startsWith(QLatin1String("LABEL="))) { // turn LABEL=foo into -L foo (#71430) QString labelName = _dev.mid(6); dev = "-L "; dev += QFile::encodeName(KShell::quoteArg(labelName)); // is it correct to assume same encoding as filesystem? } else if (_dev.startsWith(QLatin1String("UUID="))) { // and UUID=bar into -U bar QString uuidName = _dev.mid(5); dev = "-U "; dev += QFile::encodeName(KShell::quoteArg(uuidName)); } else { dev = QFile::encodeName(KShell::quoteArg(_dev)); // get those ready to be given to a shell } QByteArray point = QFile::encodeName(KShell::quoteArg(_point)); bool fstype_empty = !_fstype || !*_fstype; QByteArray fstype = KShell::quoteArg(QString::fromLatin1(_fstype)).toLatin1(); // good guess QByteArray readonly = _ro ? "-r" : ""; QByteArray mountProg = QStandardPaths::findExecutable(QStringLiteral("mount")).toLocal8Bit(); if (mountProg.isEmpty()) { mountProg = QStandardPaths::findExecutable(QStringLiteral("mount"), fallbackSystemPath()).toLocal8Bit(); } if (mountProg.isEmpty()) { error(KIO::ERR_CANNOT_MOUNT, i18n("Could not find program \"mount\"")); return; } // Two steps, in case mount doesn't like it when we pass all options for (int step = 0; step <= 1; step++) { QByteArray buffer = mountProg + ' '; // Mount using device only if no fstype nor mountpoint (KDE-1.x like) if (!dev.isEmpty() && _point.isEmpty() && fstype_empty) { buffer += dev; } else // Mount using the mountpoint, if no fstype nor device (impossible in first step) if (!_point.isEmpty() && dev.isEmpty() && fstype_empty) { buffer += point; } else // mount giving device + mountpoint but no fstype if (!_point.isEmpty() && !dev.isEmpty() && fstype_empty) { buffer += readonly + ' ' + dev + ' ' + point; } else // mount giving device + mountpoint + fstype #if defined(__svr4__) && defined(Q_OS_SOLARIS) // MARCO for Solaris 8 and I // believe this is true for SVR4 in general buffer += "-F " + fstype + ' ' + (_ro ? "-oro" : "") + ' ' + dev + ' ' + point; #else buffer += readonly + " -t " + fstype + ' ' + dev + ' ' + point; #endif buffer += " 2>" + tmpFileName; // qDebug() << buffer; int mount_ret = system(buffer.constData()); QString err = readLogFile(tmpFileName); if (err.isEmpty() && mount_ret == 0) { finished(); return; } else { // Didn't work - or maybe we just got a warning KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(_dev); // Is the device mounted ? if (mp && mount_ret == 0) { // qDebug() << "mount got a warning:" << err; warning(err); finished(); return; } else { if ((step == 0) && !_point.isEmpty()) { // qDebug() << err; // qDebug() << "Mounting with those options didn't work, trying with only mountpoint"; fstype = ""; fstype_empty = true; dev = ""; // The reason for trying with only mountpoint (instead of // only device) is that some people (hi Malte!) have the // same device associated with two mountpoints // for different fstypes, like /dev/fd0 /mnt/e2floppy and // /dev/fd0 /mnt/dosfloppy. // If the user has the same mountpoint associated with two // different devices, well they shouldn't specify the // mountpoint but just the device. } else { error(KIO::ERR_CANNOT_MOUNT, err); return; } } } } #endif /* ! HAVE_VOLMGT */ #else QString err; err = i18n("mounting is not supported by wince."); error(KIO::ERR_CANNOT_MOUNT, err); #endif } void FileProtocol::unmount(const QString &_point) { #ifndef _WIN32_WCE QByteArray buffer; QTemporaryFile tmpFile; tmpFile.setAutoRemove(false); tmpFile.open(); QByteArray tmpFileName = QFile::encodeName(tmpFile.fileName()); QString err; #if HAVE_VOLMGT /* * support for Solaris volume management */ char *devname; char *ptr; FILE *mnttab; struct mnttab mnt; if (volmgt_running()) { // qDebug() << "VOLMGT: looking for " << _point.toLocal8Bit(); if ((mnttab = QT_FOPEN(MNTTAB, "r")) == NULL) { err = QLatin1String("could not open mnttab"); // qDebug() << "VOLMGT: " << err; error(KIO::ERR_CANNOT_UNMOUNT, err); return; } /* * since there's no way to derive the device name from * the mount point through the volmgt library (and * media_findname() won't work in this case), we have to * look ourselves... */ devname = NULL; rewind(mnttab); while (getmntent(mnttab, &mnt) == 0) { if (strcmp(_point.toLocal8Bit(), mnt.mnt_mountp) == 0) { devname = mnt.mnt_special; break; } } fclose(mnttab); if (devname == NULL) { err = QLatin1String("not in mnttab"); // qDebug() << "VOLMGT: " << QFile::encodeName(_point).data() << ": " << err; error(KIO::ERR_CANNOT_UNMOUNT, err); return; } /* * strip off the directory name (volume name) * the eject(1) command will handle unmounting and * physically eject the media (if possible) */ ptr = strrchr(devname, '/'); *ptr = '\0'; QByteArray qdevname(QFile::encodeName(KShell::quoteArg(QFile::decodeName(QByteArray(devname)))).data()); buffer = "/usr/bin/eject " + qdevname + " 2>" + tmpFileName; // qDebug() << "VOLMGT: eject " << qdevname; /* * from eject(1): exit status == 0 => need to manually eject * exit status == 4 => media was ejected */ if (WEXITSTATUS(system(buffer.constData())) == 4) { /* * this is not an error, so skip "readLogFile()" * to avoid wrong/confusing error popup. The * temporary file is removed by QTemporaryFile's * destructor, so don't do that manually. */ finished(); return; } } else { /* * eject(1) should do its job without vold(1M) running, * so we probably could call eject anyway, but since the * media is mounted now, vold must've died for some reason * during the user's session, so it should be restarted... */ err = i18n("\"vold\" is not running."); // qDebug() << "VOLMGT: " << err; error(KIO::ERR_CANNOT_UNMOUNT, err); return; } #else QByteArray umountProg = QStandardPaths::findExecutable(QStringLiteral("umount")).toLocal8Bit(); if (umountProg.isEmpty()) { umountProg = QStandardPaths::findExecutable(QStringLiteral("umount"), fallbackSystemPath()).toLocal8Bit(); } if (umountProg.isEmpty()) { error(KIO::ERR_CANNOT_UNMOUNT, i18n("Could not find program \"umount\"")); return; } buffer = umountProg + ' ' + QFile::encodeName(KShell::quoteArg(_point)) + " 2>" + tmpFileName; system(buffer.constData()); #endif /* HAVE_VOLMGT */ err = readLogFile(tmpFileName); if (err.isEmpty()) { finished(); } else { error(KIO::ERR_CANNOT_UNMOUNT, err); } #else QString err; err = i18n("unmounting is not supported by wince."); error(KIO::ERR_CANNOT_MOUNT, err); #endif } /************************************* * * pmount handling * *************************************/ bool FileProtocol::pmount(const QString &dev) { #ifndef _WIN32_WCE QString pmountProg = QStandardPaths::findExecutable(QStringLiteral("pmount")); if (pmountProg.isEmpty()) { pmountProg = QStandardPaths::findExecutable(QStringLiteral("pmount"), fallbackSystemPath()); } if (pmountProg.isEmpty()) { return false; } QByteArray buffer = QFile::encodeName(pmountProg) + ' ' + QFile::encodeName(KShell::quoteArg(dev)); int res = system(buffer.constData()); return res == 0; #else return false; #endif } bool FileProtocol::pumount(const QString &point) { #ifndef _WIN32_WCE KMountPoint::Ptr mp = KMountPoint::currentMountPoints(KMountPoint::NeedRealDeviceName).findByPath(point); if (!mp) { return false; } QString dev = mp->realDeviceName(); if (dev.isEmpty()) { return false; } QString pumountProg = QStandardPaths::findExecutable(QStringLiteral("pumount")); if (pumountProg.isEmpty()) { pumountProg = QStandardPaths::findExecutable(QStringLiteral("pumount"), fallbackSystemPath()); } if (pumountProg.isEmpty()) { return false; } QByteArray buffer = QFile::encodeName(pumountProg); buffer += ' '; buffer += QFile::encodeName(KShell::quoteArg(dev)); int res = system(buffer.data()); return res == 0; #else return false; #endif } /************************************* * * Utilities * *************************************/ static QString readLogFile(const QByteArray &_filename) { QString result; QFile file(QFile::decodeName(_filename)); if (file.open(QIODevice::ReadOnly)) { result = QString::fromLocal8Bit(file.readAll()); } (void)file.remove(); return result; } /************************************* * * ACL handling helpers * *************************************/ #if HAVE_POSIX_ACL bool FileProtocol::isExtendedACL(acl_t acl) { return (acl_equiv_mode(acl, nullptr) != 0); } static void appendACLAtoms(const QByteArray &path, UDSEntry &entry, mode_t type) { // first check for a noop if (acl_extended_file(path.data()) == 0) { return; } acl_t acl = nullptr; acl_t defaultAcl = nullptr; bool isDir = (type & QT_STAT_MASK) == QT_STAT_DIR; // do we have an acl for the file, and/or a default acl for the dir, if it is one? acl = acl_get_file(path.data(), ACL_TYPE_ACCESS); /* Sadly libacl does not provided a means of checking for extended ACL and default * ACL separately. Since a directory can have both, we need to check again. */ if (isDir) { if (acl) { if (!FileProtocol::isExtendedACL(acl)) { acl_free(acl); acl = nullptr; } } defaultAcl = acl_get_file(path.data(), ACL_TYPE_DEFAULT); } if (acl || defaultAcl) { // qDebug() << path.constData() << "has extended ACL entries"; entry.insert(KIO::UDSEntry::UDS_EXTENDED_ACL, 1); if (acl) { const QString str = aclToText(acl); entry.insert(KIO::UDSEntry::UDS_ACL_STRING, str); // qDebug() << path.constData() << "ACL:" << str; acl_free(acl); } if (defaultAcl) { const QString str = aclToText(defaultAcl); entry.insert(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING, str); // qDebug() << path.constData() << "DEFAULT ACL:" << str; acl_free(defaultAcl); } } } #endif // We could port this to KTempDir::removeDir but then we wouldn't be able to tell the user // where exactly the deletion failed, in case of errors. bool FileProtocol::deleteRecursive(const QString &path) { //qDebug() << path; QDirIterator it(path, QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System | QDir::Hidden, QDirIterator::Subdirectories); QStringList dirsToDelete; while (it.hasNext()) { const QString itemPath = it.next(); //qDebug() << "itemPath=" << itemPath; const QFileInfo info = it.fileInfo(); if (info.isDir() && !info.isSymLink()) { dirsToDelete.prepend(itemPath); } else { //qDebug() << "QFile::remove" << itemPath; if (!QFile::remove(itemPath)) { - error(KIO::ERR_CANNOT_DELETE, itemPath); - return false; + if (auto err = execWithElevatedPrivilege(DEL, itemPath)) { + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_DELETE, itemPath); + } + return false; + } } } } QDir dir; Q_FOREACH (const QString &itemPath, dirsToDelete) { //qDebug() << "QDir::rmdir" << itemPath; if (!dir.rmdir(itemPath)) { - error(KIO::ERR_CANNOT_DELETE, itemPath); - return false; + if (auto err = execWithElevatedPrivilege(RMDIR, itemPath)) { + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_DELETE, itemPath); + } + return false; + } } } return true; } void FileProtocol::fileSystemFreeSpace(const QUrl &url) { if (url.isLocalFile()) { const KDiskFreeSpaceInfo spaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo(url.toLocalFile()); if (spaceInfo.isValid()) { setMetaData(QStringLiteral("total"), QString::number(spaceInfo.size())); setMetaData(QStringLiteral("available"), QString::number(spaceInfo.available())); finished(); } else { error(KIO::ERR_COULD_NOT_STAT, url.url()); } } else { error(KIO::ERR_UNSUPPORTED_PROTOCOL, url.url()); } } void FileProtocol::virtual_hook(int id, void *data) { switch(id) { case SlaveBase::GetFileSystemFreeSpace: { QUrl *url = static_cast(data); fileSystemFreeSpace(*url); } break; default: { SlaveBase::virtual_hook(id, data); } break; } } // needed for JSON file embedding #include "file.moc" diff --git a/src/ioslaves/file/file.h b/src/ioslaves/file/file.h index 8250a0ef..9509d5c4 100644 --- a/src/ioslaves/file/file.h +++ b/src/ioslaves/file/file.h @@ -1,115 +1,122 @@ /* Copyright (C) 2000-2002 Stephan Kulow Copyright (C) 2000-2002 David Faure Copyright (C) 2000-2002 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __file_h__ #define __file_h__ #include #include #include #include #include #include #include // mode_t #include #if HAVE_POSIX_ACL #include #include #endif #include "file_p.h" #include Q_DECLARE_LOGGING_CATEGORY(KIO_FILE) class FileProtocol : public QObject, public KIO::SlaveBase { Q_OBJECT public: FileProtocol(const QByteArray &pool, const QByteArray &app); virtual ~FileProtocol(); void get(const QUrl &url) Q_DECL_OVERRIDE; virtual void put(const QUrl &url, int _mode, KIO::JobFlags _flags) Q_DECL_OVERRIDE; virtual void copy(const QUrl &src, const QUrl &dest, int mode, KIO::JobFlags flags) Q_DECL_OVERRIDE; virtual void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) Q_DECL_OVERRIDE; virtual void symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags) Q_DECL_OVERRIDE; void stat(const QUrl &url) Q_DECL_OVERRIDE; void listDir(const QUrl &url) Q_DECL_OVERRIDE; void mkdir(const QUrl &url, int permissions) Q_DECL_OVERRIDE; void chmod(const QUrl &url, int permissions) Q_DECL_OVERRIDE; void chown(const QUrl &url, const QString &owner, const QString &group) Q_DECL_OVERRIDE; void setModificationTime(const QUrl &url, const QDateTime &mtime) Q_DECL_OVERRIDE; void del(const QUrl &url, bool isfile) Q_DECL_OVERRIDE; void open(const QUrl &url, QIODevice::OpenMode mode) Q_DECL_OVERRIDE; void read(KIO::filesize_t size) Q_DECL_OVERRIDE; void write(const QByteArray &data) Q_DECL_OVERRIDE; void seek(KIO::filesize_t offset) Q_DECL_OVERRIDE; void close() Q_DECL_OVERRIDE; /** * Special commands supported by this slave: * 1 - mount * 2 - unmount */ void special(const QByteArray &data) Q_DECL_OVERRIDE; void unmount(const QString &point); void mount(bool _ro, const char *_fstype, const QString &dev, const QString &point); bool pumount(const QString &point); bool pmount(const QString &dev); #if HAVE_POSIX_ACL static bool isExtendedACL(acl_t acl); #endif protected: void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; private: bool createUDSEntry(const QString &filename, const QByteArray &path, KIO::UDSEntry &entry, short int details); int setACL(const char *path, mode_t perm, bool _directoryDefault); QString getUserName(KUserId uid) const; QString getGroupName(KGroupId gid) const; bool deleteRecursive(const QString &path); void fileSystemFreeSpace(const QUrl &url); // KF6 TODO: Turn into virtual method in SlaveBase bool privilegeOperationUnitTestMode(); PrivilegeOperationReturnValue execWithElevatedPrivilege(ActionType action, const QVariant &arg1, const QVariant &arg2 = QVariant(), const QVariant &arg3 = QVariant()); + PrivilegeOperationReturnValue tryOpen(QFile &f, const QByteArray &path, int flags, int mode); + + // We want to execute chmod/chown/utime with elevated privileges (in copy & put) + // only during the brief period privileges are elevated. If it's not the case show + // a warning and continue. + PrivilegeOperationReturnValue tryChangeFileAttr(ActionType action, const QVariant &arg1, + const QVariant &arg2, const QVariant &arg3 = QVariant()); private: mutable QHash mUsercache; mutable QHash mGroupcache; QFile *mFile; }; #endif diff --git a/src/ioslaves/file/file_unix.cpp b/src/ioslaves/file/file_unix.cpp index fd17ae74..d75c8711 100644 --- a/src/ioslaves/file/file_unix.cpp +++ b/src/ioslaves/file/file_unix.cpp @@ -1,716 +1,827 @@ /* Copyright (C) 2000-2002 Stephan Kulow Copyright (C) 2000-2002 David Faure Copyright (C) 2000-2002 Waldo Bastian Copyright (C) 2006 Allan Sandfeld Jensen Copyright (C) 2007 Thiago Macieira Copyright (C) 2007 Christian Ehrlicher This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "file.h" #include #include #include #include #include #include #include #include #include #include #include +#include "fdreceiver.h" + //sendfile has different semantics in different platforms #if defined HAVE_SENDFILE && defined Q_OS_LINUX #define USE_SENDFILE 1 #endif #ifdef USE_SENDFILE #include #endif using namespace KIO; #define MAX_IPC_SIZE (1024*32) static bool same_inode(const QT_STATBUF &src, const QT_STATBUF &dest) { if (src.st_ino == dest.st_ino && src.st_dev == dest.st_dev) { return true; } return false; } static const QString socketPath() { return QStringLiteral("org_kde_kio_file_helper_%1").arg(getpid()); } bool FileProtocol::privilegeOperationUnitTestMode() { return (metaData(QStringLiteral("UnitTesting")) == QLatin1String("true")) && (requestPrivilegeOperation() == KIO::OperationAllowed); } +PrivilegeOperationReturnValue FileProtocol::tryOpen(QFile &f, const QByteArray &path, int flags, int mode) +{ + FdReceiver fdRecv(socketPath()); + if (!fdRecv.isListening()) { + return PrivilegeOperationReturnValue::failure(); + } + + QIODevice::OpenMode openMode; + if (flags & O_RDONLY) { + openMode |= QIODevice::ReadOnly; + } + if (flags & O_WRONLY || flags & O_CREAT) { + openMode |= QIODevice::WriteOnly; + } + if (flags & O_RDWR) { + openMode |= QIODevice::ReadWrite; + } + if (flags & O_TRUNC) { + openMode |= QIODevice::Truncate; + } + if (flags & O_APPEND) { + openMode |= QIODevice::Append; + } + + if (auto err = execWithElevatedPrivilege(OPEN, path, flags, mode)) { + return err; + } else { + int fd = fdRecv.fileDescriptor(); + if (fd < 3 || !f.open(fd, openMode, QFileDevice::AutoCloseHandle)) { + return PrivilegeOperationReturnValue::failure(); + } + } + return PrivilegeOperationReturnValue::success(); +} + +PrivilegeOperationReturnValue FileProtocol::tryChangeFileAttr(ActionType action, const QVariant &arg1, + const QVariant &arg2, const QVariant &arg3) +{ + KAuth::Action execAction(QStringLiteral("org.kde.kio.file.exec")); + execAction.setHelperId(QStringLiteral("org.kde.kio.file")); + if (execAction.status() == KAuth::Action::AuthorizedStatus) { + return execWithElevatedPrivilege(action, arg1, arg2, arg3); + } + return PrivilegeOperationReturnValue::failure(); +} + void FileProtocol::copy(const QUrl &srcUrl, const QUrl &destUrl, int _mode, JobFlags _flags) { + if (privilegeOperationUnitTestMode()) { + finished(); + return; + } + // qDebug() << "copy(): " << srcUrl << " -> " << destUrl << ", mode=" << _mode; const QString src = srcUrl.toLocalFile(); const QString dest = destUrl.toLocalFile(); QByteArray _src(QFile::encodeName(src)); QByteArray _dest(QFile::encodeName(dest)); QT_STATBUF buff_src; #if HAVE_POSIX_ACL acl_t acl; #endif if (QT_STAT(_src.data(), &buff_src) == -1) { if (errno == EACCES) { error(KIO::ERR_ACCESS_DENIED, src); } else { error(KIO::ERR_DOES_NOT_EXIST, src); } return; } if ((buff_src.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { error(KIO::ERR_IS_DIRECTORY, src); return; } if (S_ISFIFO(buff_src.st_mode) || S_ISSOCK(buff_src.st_mode)) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, src); return; } QT_STATBUF buff_dest; bool dest_exists = (QT_LSTAT(_dest.data(), &buff_dest) != -1); if (dest_exists) { if (same_inode(buff_dest, buff_src)) { error(KIO::ERR_IDENTICAL_FILES, dest); return; } if ((buff_dest.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { error(KIO::ERR_DIR_ALREADY_EXIST, dest); return; } if (!(_flags & KIO::Overwrite)) { error(KIO::ERR_FILE_ALREADY_EXIST, dest); return; } // If the destination is a symlink and overwrite is TRUE, // remove the symlink first to prevent the scenario where // the symlink actually points to current source! if ((_flags & KIO::Overwrite) && ((buff_dest.st_mode & QT_STAT_MASK) == QT_STAT_LNK)) { //qDebug() << "copy(): LINK DESTINATION"; - QFile::remove(dest); + if (!QFile::remove(dest)) { + if (auto err = execWithElevatedPrivilege(DEL, _dest)) { + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_DELETE_ORIGINAL, dest); + } + return; + } + } } } QFile src_file(src); if (!src_file.open(QIODevice::ReadOnly)) { - error(KIO::ERR_CANNOT_OPEN_FOR_READING, src); - return; + if (auto err = tryOpen(src_file, _src, O_RDONLY, S_IRUSR)) { + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_OPEN_FOR_READING, src); + } + return; + } } #if HAVE_FADVISE posix_fadvise(src_file.handle(), 0, 0, POSIX_FADV_SEQUENTIAL); #endif QFile dest_file(dest); if (!dest_file.open(QIODevice::Truncate | QIODevice::WriteOnly)) { - // qDebug() << "###### COULD NOT WRITE " << dest; - if (errno == EACCES) { - error(KIO::ERR_WRITE_ACCESS_DENIED, dest); - } else { - error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest); + if (auto err = tryOpen(dest_file, _dest, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)) { + if (!err.wasCanceled()) { + // qDebug() << "###### COULD NOT WRITE " << dest; + if (errno == EACCES) { + error(KIO::ERR_WRITE_ACCESS_DENIED, dest); + } else { + error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest); + } + } + src_file.close(); + return; } - src_file.close(); - return; } // nobody shall be allowed to peek into the file during creation - dest_file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); + if (!QFile::setPermissions(dest, QFileDevice::ReadOwner | QFileDevice::WriteOwner)) { + if (auto err = execWithElevatedPrivilege(CHOWN, _dest, getuid(), getgid())) { + dest_file.close(); + execWithElevatedPrivilege(DEL, _dest); + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_CHOWN, dest); + } + return; + } else { + QFile::setPermissions(dest, QFileDevice::ReadOwner | QFileDevice::WriteOwner); + } + } #if HAVE_FADVISE posix_fadvise(dest_file.handle(), 0, 0, POSIX_FADV_SEQUENTIAL); #endif #if HAVE_POSIX_ACL acl = acl_get_fd(src_file.handle()); if (acl && !isExtendedACL(acl)) { // qDebug() << _dest.data() << " doesn't have extended ACL"; acl_free(acl); acl = nullptr; } #endif totalSize(buff_src.st_size); KIO::filesize_t processed_size = 0; char buffer[ MAX_IPC_SIZE ]; int n; #ifdef USE_SENDFILE bool use_sendfile = buff_src.st_size < 0x7FFFFFFF; #endif while (1) { #ifdef USE_SENDFILE if (use_sendfile) { off_t sf = processed_size; n = ::sendfile(dest_file.handle(), src_file.handle(), &sf, MAX_IPC_SIZE); processed_size = sf; if (n == -1 && (errno == EINVAL || errno == ENOSYS)) { //not all filesystems support sendfile() // qDebug() << "sendfile() not supported, falling back "; use_sendfile = false; } } if (!use_sendfile) #endif n = ::read(src_file.handle(), buffer, MAX_IPC_SIZE); if (n == -1) { if (errno == EINTR) { continue; } #ifdef USE_SENDFILE if (use_sendfile) { // qDebug() << "sendfile() error:" << strerror(errno); if (errno == ENOSPC) { // disk full error(KIO::ERR_DISK_FULL, dest); } else { error(KIO::ERR_SLAVE_DEFINED, i18n("Cannot copy file from %1 to %2. (Errno: %3)", src, dest, errno)); } } else #endif error(KIO::ERR_CANNOT_READ, src); src_file.close(); dest_file.close(); #if HAVE_POSIX_ACL if (acl) { acl_free(acl); } #endif - dest_file.remove(); // don't keep partly copied file + if (!QFile::remove(dest)) { // don't keep partly copied file + execWithElevatedPrivilege(DEL, _dest); + } return; } if (n == 0) { break; // Finished } #ifdef USE_SENDFILE if (!use_sendfile) { #endif if (dest_file.write(buffer, n) != n) { if (dest_file.error() == QFileDevice::ResourceError) { // disk full error(KIO::ERR_DISK_FULL, dest); } else { qCWarning(KIO_FILE) << "Couldn't write[2]. Error:" << dest_file.errorString(); error(KIO::ERR_CANNOT_WRITE, dest); } #if HAVE_POSIX_ACL if (acl) { acl_free(acl); } #endif - dest_file.remove(); // don't keep partly copied file + if (!QFile::remove(dest)) { // don't keep partly copied file + execWithElevatedPrivilege(DEL, _dest); + } return; } processed_size += n; #ifdef USE_SENDFILE } #endif processedSize(processed_size); } src_file.close(); dest_file.close(); if (dest_file.error() != QFile::NoError) { qCWarning(KIO_FILE) << "Error when closing file descriptor[2]:" << dest_file.errorString(); error(KIO::ERR_CANNOT_WRITE, dest); #if HAVE_POSIX_ACL if (acl) { acl_free(acl); } #endif - dest_file.remove(); // don't keep partly copied file + if (!QFile::remove(dest)) { // don't keep partly copied file + execWithElevatedPrivilege(DEL, _dest); + } return; } // set final permissions // if no special mode given, preserve the mode from the sourcefile if (_mode == -1) { _mode = buff_src.st_mode; } if ((::chmod(_dest.data(), _mode) != 0) #if HAVE_POSIX_ACL || (acl && acl_set_file(_dest.data(), ACL_TYPE_ACCESS, acl) != 0) #endif ) { - KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(dest); - // Eat the error if the filesystem apparently doesn't support chmod. - if (mp && mp->testFileSystemFlag(KMountPoint::SupportsChmod)) { + if (tryChangeFileAttr(CHMOD, _dest, _mode)) { warning(i18n("Could not change permissions for '%1'", dest)); } } #if HAVE_POSIX_ACL if (acl) { acl_free(acl); } #endif // preserve ownership if (::chown(_dest.data(), -1 /*keep user*/, buff_src.st_gid) == 0) { // as we are the owner of the new file, we can always change the group, but // we might not be allowed to change the owner (void)::chown(_dest.data(), buff_src.st_uid, -1 /*keep group*/); } else { - qCWarning(KIO_FILE) << QStringLiteral("Couldn't preserve group for '%1'").arg(dest); + if (tryChangeFileAttr(CHOWN, _dest, buff_src.st_uid, buff_src.st_gid)) { + qCWarning(KIO_FILE) << QStringLiteral("Couldn't preserve group for '%1'").arg(dest); + } } // copy access and modification time struct utimbuf ut; ut.actime = buff_src.st_atime; ut.modtime = buff_src.st_mtime; if (::utime(_dest.data(), &ut) != 0) { - qCWarning(KIO_FILE) << QStringLiteral("Couldn't preserve access and modification time for '%1'").arg(dest); + if (tryChangeFileAttr(UTIME, _dest, qint64(ut.actime), qint64(ut.modtime))) { + qCWarning(KIO_FILE) << QStringLiteral("Couldn't preserve access and modification time for '%1'").arg(dest); + } } processedSize(buff_src.st_size); finished(); } static bool isLocalFileSameHost(const QUrl &url) { if (!url.isLocalFile()) { return false; } if (url.host().isEmpty() || (url.host() == QLatin1String("localhost"))) { return true; } char hostname[ 256 ]; hostname[ 0 ] = '\0'; if (!gethostname(hostname, 255)) { hostname[sizeof(hostname) - 1] = '\0'; } return (QString::compare(url.host(), QLatin1String(hostname), Qt::CaseInsensitive) == 0); } void FileProtocol::listDir(const QUrl &url) { if (!isLocalFileSameHost(url)) { QUrl redir(url); redir.setScheme(config()->readEntry("DefaultRemoteProtocol", "smb")); redirection(redir); // qDebug() << "redirecting to " << redir; finished(); return; } const QString path(url.toLocalFile()); const QByteArray _path(QFile::encodeName(path)); DIR *dp = opendir(_path.data()); if (dp == nullptr) { switch (errno) { case ENOENT: error(KIO::ERR_DOES_NOT_EXIST, path); return; case ENOTDIR: error(KIO::ERR_IS_FILE, path); break; #ifdef ENOMEDIUM case ENOMEDIUM: error(ERR_SLAVE_DEFINED, i18n("No media in device for %1", path)); break; #endif default: error(KIO::ERR_CANNOT_ENTER_DIRECTORY, path); break; } return; } /* set the current dir to the path to speed up in not having to pass an absolute path. We restore the path later to get out of the path - the kernel wouldn't unmount or delete directories we keep as active directory. And as the slave runs in the background, it's hard to see for the user what the problem would be */ const QString pathBuffer(QDir::currentPath()); if (!QDir::setCurrent(path)) { closedir(dp); error(ERR_CANNOT_ENTER_DIRECTORY, path); return; } const QString sDetails = metaData(QStringLiteral("details")); const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); //qDebug() << "========= LIST " << url << "details=" << details << " ========="; UDSEntry entry; #ifndef HAVE_DIRENT_D_TYPE QT_STATBUF st; #endif QT_DIRENT *ep; while ((ep = QT_READDIR(dp)) != nullptr) { entry.clear(); const QString filename = QFile::decodeName(ep->d_name); /* * details == 0 (if statement) is the fast code path. * We only get the file name and type. After that we emit * the result. * * The else statement is the slow path that requests all * file information in file.cpp. It executes a stat call * for every entry thus becoming slower. * */ if (details == 0) { entry.insert(KIO::UDSEntry::UDS_NAME, filename); #ifdef HAVE_DIRENT_D_TYPE entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, (ep->d_type == DT_DIR) ? S_IFDIR : S_IFREG); const bool isSymLink = (ep->d_type == DT_LNK); #else // oops, no fast way, we need to stat (e.g. on Solaris) if (QT_LSTAT(ep->d_name, &st) == -1) { continue; // how can stat fail? } entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, ((st.st_mode & QT_STAT_MASK) == QT_STAT_DIR) ? S_IFDIR : S_IFREG); const bool isSymLink = ((st.st_mode & QT_STAT_MASK) == QT_STAT_LNK); #endif if (isSymLink) { // for symlinks obey the UDSEntry contract and provide UDS_LINK_DEST // even if we don't know the link dest (and DeleteJob doesn't care...) entry.insert(KIO::UDSEntry::UDS_LINK_DEST, QStringLiteral("Dummy Link Target")); } listEntry(entry); } else { if (createUDSEntry(filename, QByteArray(ep->d_name), entry, details)) { listEntry(entry); } } } closedir(dp); // Restore the path QDir::setCurrent(pathBuffer); finished(); } void FileProtocol::rename(const QUrl &srcUrl, const QUrl &destUrl, KIO::JobFlags _flags) { char off_t_should_be_64_bits[sizeof(off_t) >= 8 ? 1 : -1]; (void) off_t_should_be_64_bits; const QString src = srcUrl.toLocalFile(); const QString dest = destUrl.toLocalFile(); const QByteArray _src(QFile::encodeName(src)); const QByteArray _dest(QFile::encodeName(dest)); QT_STATBUF buff_src; if (QT_LSTAT(_src.data(), &buff_src) == -1) { if (errno == EACCES) { error(KIO::ERR_ACCESS_DENIED, src); } else { error(KIO::ERR_DOES_NOT_EXIST, src); } return; } QT_STATBUF buff_dest; // stat symlinks here (lstat, not stat), to avoid ERR_IDENTICAL_FILES when replacing symlink // with its target (#169547) bool dest_exists = (QT_LSTAT(_dest.data(), &buff_dest) != -1); if (dest_exists) { if (same_inode(buff_dest, buff_src)) { error(KIO::ERR_IDENTICAL_FILES, dest); return; } if ((buff_dest.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { error(KIO::ERR_DIR_ALREADY_EXIST, dest); return; } if (!(_flags & KIO::Overwrite)) { error(KIO::ERR_FILE_ALREADY_EXIST, dest); return; } } if (::rename(_src.data(), _dest.data())) { - if ((errno == EACCES) || (errno == EPERM)) { - error(KIO::ERR_ACCESS_DENIED, dest); - } else if (errno == EXDEV) { - error(KIO::ERR_UNSUPPORTED_ACTION, QStringLiteral("rename")); - } else if (errno == EROFS) { // The file is on a read-only filesystem - error(KIO::ERR_CANNOT_DELETE, src); - } else { - error(KIO::ERR_CANNOT_RENAME, src); + if (auto err = execWithElevatedPrivilege(RENAME, _src, _dest)) { + if (!err.wasCanceled()) { + if ((errno == EACCES) || (errno == EPERM)) { + error(KIO::ERR_ACCESS_DENIED, dest); + } else if (errno == EXDEV) { + error(KIO::ERR_UNSUPPORTED_ACTION, QStringLiteral("rename")); + } else if (errno == EROFS) { // The file is on a read-only filesystem + error(KIO::ERR_CANNOT_DELETE, src); + } else { + error(KIO::ERR_CANNOT_RENAME, src); + } + } + return; } - return; } finished(); } void FileProtocol::symlink(const QString &target, const QUrl &destUrl, KIO::JobFlags flags) { const QString dest = destUrl.toLocalFile(); // Assume dest is local too (wouldn't be here otherwise) if (::symlink(QFile::encodeName(target).constData(), QFile::encodeName(dest).constData()) == -1) { // Does the destination already exist ? if (errno == EEXIST) { if ((flags & KIO::Overwrite)) { // Try to delete the destination if (unlink(QFile::encodeName(dest).constData()) != 0) { - error(KIO::ERR_CANNOT_DELETE, dest); - return; + if (auto err = execWithElevatedPrivilege(DEL, dest)) { + if (!err.wasCanceled()) { + error(KIO::ERR_CANNOT_DELETE, dest); + } + return; + } } // Try again - this won't loop forever since unlink succeeded symlink(target, destUrl, flags); return; } else { QT_STATBUF buff_dest; if (QT_LSTAT(QFile::encodeName(dest).constData(), &buff_dest) == 0 && ((buff_dest.st_mode & QT_STAT_MASK) == QT_STAT_DIR)) { error(KIO::ERR_DIR_ALREADY_EXIST, dest); } else { error(KIO::ERR_FILE_ALREADY_EXIST, dest); } return; } } else { - // Some error occurred while we tried to symlink - error(KIO::ERR_CANNOT_SYMLINK, dest); - return; + if (auto err = execWithElevatedPrivilege(SYMLINK, dest, target)) { + if (!err.wasCanceled()) { + // Some error occurred while we tried to symlink + error(KIO::ERR_CANNOT_SYMLINK, dest); + } + return; + } } } finished(); } void FileProtocol::del(const QUrl &url, bool isfile) { const QString path = url.toLocalFile(); const QByteArray _path(QFile::encodeName(path)); /***** * Delete files *****/ if (isfile) { // qDebug() << "Deleting file "<< url; if (unlink(_path.data()) == -1) { - if ((errno == EACCES) || (errno == EPERM)) { - error(KIO::ERR_ACCESS_DENIED, path); - } else if (errno == EISDIR) { - error(KIO::ERR_IS_DIRECTORY, path); - } else { - error(KIO::ERR_CANNOT_DELETE, path); + if (auto err = execWithElevatedPrivilege(DEL, _path)) { + if (!err.wasCanceled()) { + if ((errno == EACCES) || (errno == EPERM)) { + error(KIO::ERR_ACCESS_DENIED, path); + } else if (errno == EISDIR) { + error(KIO::ERR_IS_DIRECTORY, path); + } else { + error(KIO::ERR_CANNOT_DELETE, path); + } + } + return; } - return; } } else { /***** * Delete empty directory *****/ // qDebug() << "Deleting directory " << url; if (metaData(QStringLiteral("recurse")) == QLatin1String("true")) { if (!deleteRecursive(path)) { return; } } if (QT_RMDIR(_path.data()) == -1) { - if ((errno == EACCES) || (errno == EPERM)) { - error(KIO::ERR_ACCESS_DENIED, path); - } else { - // qDebug() << "could not rmdir " << perror; - error(KIO::ERR_CANNOT_RMDIR, path); + if (auto err = execWithElevatedPrivilege(RMDIR, _path)) { + if (!err.wasCanceled()) { + if ((errno == EACCES) || (errno == EPERM)) { + error(KIO::ERR_ACCESS_DENIED, path); + } else { + // qDebug() << "could not rmdir " << perror; + error(KIO::ERR_CANNOT_RMDIR, path); + } + } return; } } } finished(); } void FileProtocol::chown(const QUrl &url, const QString &owner, const QString &group) { const QString path = url.toLocalFile(); const QByteArray _path(QFile::encodeName(path)); uid_t uid; gid_t gid; // get uid from given owner { struct passwd *p = ::getpwnam(owner.toLocal8Bit().constData()); if (! p) { error(KIO::ERR_SLAVE_DEFINED, i18n("Could not get user id for given user name %1", owner)); return; } uid = p->pw_uid; } // get gid from given group { struct group *p = ::getgrnam(group.toLocal8Bit().constData()); if (! p) { error(KIO::ERR_SLAVE_DEFINED, i18n("Could not get group id for given group name %1", group)); return; } gid = p->gr_gid; } if (::chown(_path.constData(), uid, gid) == -1) { - switch (errno) { - case EPERM: - case EACCES: - error(KIO::ERR_ACCESS_DENIED, path); - break; - case ENOSPC: - error(KIO::ERR_DISK_FULL, path); - break; - default: - error(KIO::ERR_CANNOT_CHOWN, path); + if (auto err = execWithElevatedPrivilege(CHOWN, _path, uid, gid)) { + if (!err.wasCanceled()) { + switch (errno) { + case EPERM: + case EACCES: + error(KIO::ERR_ACCESS_DENIED, path); + break; + case ENOSPC: + error(KIO::ERR_DISK_FULL, path); + break; + default: + error(KIO::ERR_CANNOT_CHOWN, path); + } + } } } else { finished(); } } void FileProtocol::stat(const QUrl &url) { if (!isLocalFileSameHost(url)) { QUrl redir(url); redir.setScheme(config()->readEntry("DefaultRemoteProtocol", "smb")); redirection(redir); // qDebug() << "redirecting to " << redir; finished(); return; } /* directories may not have a slash at the end if * we want to stat() them; it requires that we * change into it .. which may not be allowed * stat("/is/unaccessible") -> rwx------ * stat("/is/unaccessible/") -> EPERM H.Z. * This is the reason for the -1 */ const QString path(url.adjusted(QUrl::StripTrailingSlash).toLocalFile()); const QByteArray _path(QFile::encodeName(path)); const QString sDetails = metaData(QStringLiteral("details")); const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); UDSEntry entry; if (!createUDSEntry(url.fileName(), _path, entry, details)) { error(KIO::ERR_DOES_NOT_EXIST, path); return; } #if 0 ///////// debug code MetaData::iterator it1 = mOutgoingMetaData.begin(); for (; it1 != mOutgoingMetaData.end(); it1++) { // qDebug() << it1.key() << " = " << it1.data(); } ///////// #endif statEntry(entry); finished(); } PrivilegeOperationReturnValue FileProtocol::execWithElevatedPrivilege(ActionType action, const QVariant &arg1, const QVariant &arg2, const QVariant &arg3) { if (privilegeOperationUnitTestMode()) { return PrivilegeOperationReturnValue::success(); } if (!(errno == EACCES || errno == EPERM)) { return PrivilegeOperationReturnValue::failure(); } KIO::PrivilegeOperationStatus opStatus = requestPrivilegeOperation(); if (opStatus != KIO::OperationAllowed) { if (opStatus == KIO::OperationCanceled) { error(KIO::ERR_USER_CANCELED, QString()); return PrivilegeOperationReturnValue::canceled(); } return PrivilegeOperationReturnValue::failure(); } if (action == CHMOD || action == CHOWN || action == UTIME) { KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(arg1.toString()); // Test for chmod and utime will return the same result as test for chown. if (mp && !mp->testFileSystemFlag(KMountPoint::SupportsChown)) { return PrivilegeOperationReturnValue::failure(); } } QByteArray helperArgs; QDataStream out(&helperArgs, QIODevice::WriteOnly); out << action << arg1 << arg2 << arg3; if (action == OPEN || action == OPENDIR) { out << QVariant::fromValue(socketPath()); } KAuth::Action execAction(QStringLiteral("org.kde.kio.file.exec")); execAction.setHelperId(QStringLiteral("org.kde.kio.file")); QVariantMap argv; argv.insert(QStringLiteral("arguments"), helperArgs); execAction.setArguments(argv); auto reply = execAction.execute(); if (reply->exec()) { return PrivilegeOperationReturnValue::success(); } return PrivilegeOperationReturnValue::failure(); } diff --git a/src/ioslaves/file/file_win.cpp b/src/ioslaves/file/file_win.cpp index de1cfd76..d9f0616e 100644 --- a/src/ioslaves/file/file_win.cpp +++ b/src/ioslaves/file/file_win.cpp @@ -1,394 +1,403 @@ /* Copyright (C) 2000-2002 Stephan Kulow Copyright (C) 2000-2002 David Faure Copyright (C) 2000-2002 Waldo Bastian Copyright (C) 2006 Allan Sandfeld Jensen Copyright (C) 2007 Thiago Macieira Copyright (C) 2007 Christian Ehrlicher This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "file.h" #include #include #include #include #include #include #include "kioglobal_p.h" using namespace KIO; static DWORD CALLBACK CopyProgressRoutine( LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData ) { FileProtocol *f = reinterpret_cast(lpData); f->processedSize(TotalBytesTransferred.QuadPart); return PROGRESS_CONTINUE; } static UDSEntry createUDSEntryWin(const QFileInfo &fileInfo) { UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, fileInfo.fileName()); if (fileInfo.isSymLink()) { entry.insert(KIO::UDSEntry::UDS_TARGET_URL, fileInfo.symLinkTarget()); /* TODO - or not useful on windows? if ( details > 1 ) { // It is a link pointing to nowhere type = S_IFMT - 1; access = S_IRWXU | S_IRWXG | S_IRWXO; entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, type ); entry.insert( KIO::UDSEntry::UDS_ACCESS, access ); entry.insert( KIO::UDSEntry::UDS_SIZE, 0LL ); goto notype; } */ } int type = S_IFREG; int access = 0; if (fileInfo.isDir()) { type = S_IFDIR; } else if (fileInfo.isSymLink()) { type = QT_STAT_LNK; } if (fileInfo.isReadable()) { access |= S_IRUSR; } if (fileInfo.isWritable()) { access |= S_IWUSR; } if (fileInfo.isExecutable()) { access |= S_IXUSR; } entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, type); entry.insert(KIO::UDSEntry::UDS_ACCESS, access); entry.insert(KIO::UDSEntry::UDS_SIZE, fileInfo.size()); if (fileInfo.isHidden()) { entry.insert(KIO::UDSEntry::UDS_HIDDEN, true); } entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, fileInfo.lastModified().toTime_t()); entry.insert(KIO::UDSEntry::UDS_USER, fileInfo.owner()); entry.insert(KIO::UDSEntry::UDS_GROUP, fileInfo.group()); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, fileInfo.lastRead().toTime_t()); return entry; } void FileProtocol::copy(const QUrl &src, const QUrl &dest, int _mode, JobFlags _flags) { // qDebug() << "copy(): " << src << " -> " << dest << ", mode=" << _mode; QFileInfo _src(src.toLocalFile()); QFileInfo _dest(dest.toLocalFile()); DWORD dwFlags = COPY_FILE_FAIL_IF_EXISTS; if (_src == _dest) { error(KIO::ERR_IDENTICAL_FILES, _dest.filePath()); return; } if (!_src.exists()) { error(KIO::ERR_DOES_NOT_EXIST, _src.filePath()); return; } if (_src.isDir()) { error(KIO::ERR_IS_DIRECTORY, _src.filePath()); return; } if (_dest.exists()) { if (_dest.isDir()) { error(KIO::ERR_DIR_ALREADY_EXIST, _dest.filePath()); return; } if (!(_flags & KIO::Overwrite)) { error(KIO::ERR_FILE_ALREADY_EXIST, _dest.filePath()); return; } dwFlags = 0; } if (!QFileInfo(_dest.dir().absolutePath()).exists()) { _dest.dir().mkdir(_dest.dir().absolutePath()); } if (CopyFileExW((LPCWSTR) _src.filePath().utf16(), (LPCWSTR) _dest.filePath().utf16(), CopyProgressRoutine, (LPVOID) this, FALSE, dwFlags) == 0) { DWORD dwLastErr = GetLastError(); if (dwLastErr == ERROR_FILE_NOT_FOUND) { error(KIO::ERR_DOES_NOT_EXIST, _src.filePath()); } else if (dwLastErr == ERROR_ACCESS_DENIED) { error(KIO::ERR_ACCESS_DENIED, _dest.filePath()); } else { #if 0 LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwLastErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL); OutputDebugString((WCHAR *)lpMsgBuf); #endif error(KIO::ERR_CANNOT_RENAME, _src.filePath()); // qDebug() << "Copying file " << _src.filePath() << " failed (" << dwLastErr << ")"; } return; } finished(); } void FileProtocol::listDir(const QUrl &url) { // qDebug() << "========= LIST " << url << " ========="; if (!url.isLocalFile()) { QUrl redir(url); redir.setScheme(config()->readEntry("DefaultRemoteProtocol", "smb")); redirection(redir); // qDebug() << "redirecting to " << redir; finished(); return; } const QString path = url.toLocalFile(); const QFileInfo info(path); if (info.isFile()) { error(KIO::ERR_IS_FILE, path); return; } QDir dir(path); dir.setFilter(QDir::AllEntries | QDir::Hidden); if (!dir.exists()) { // qDebug() << "========= ERR_DOES_NOT_EXIST ========="; error(KIO::ERR_DOES_NOT_EXIST, path); return; } if (!dir.isReadable()) { // qDebug() << "========= ERR_CANNOT_ENTER_DIRECTORY ========="; error(KIO::ERR_CANNOT_ENTER_DIRECTORY, path); return; } QDirIterator it(dir); UDSEntry entry; while (it.hasNext()) { it.next(); UDSEntry entry = createUDSEntryWin(it.fileInfo()); listEntry(entry); entry.clear(); } // qDebug() << "============= COMPLETED LIST ============"; finished(); } void FileProtocol::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags _flags) { // qDebug() << "rename(): " << src << " -> " << dest; QFileInfo _src(src.toLocalFile()); QFileInfo _dest(dest.toLocalFile()); DWORD dwFlags = 0; if (_src == _dest) { error(KIO::ERR_IDENTICAL_FILES, _dest.filePath()); return; } if (!_src.exists()) { error(KIO::ERR_DOES_NOT_EXIST, _src.filePath()); return; } if (_dest.exists()) { if (_dest.isDir()) { error(KIO::ERR_DIR_ALREADY_EXIST, _dest.filePath()); return; } if (!(_flags & KIO::Overwrite)) { error(KIO::ERR_FILE_ALREADY_EXIST, _dest.filePath()); return; } #ifndef _WIN32_WCE dwFlags = MOVEFILE_REPLACE_EXISTING; #endif } // To avoid error 17 - The system cannot move the file to a different disk drive. #ifndef _WIN32_WCE dwFlags |= MOVEFILE_COPY_ALLOWED; if (MoveFileExW((LPCWSTR) _src.filePath().utf16(), (LPCWSTR) _dest.filePath().utf16(), dwFlags) == 0) #else if (MoveFileW((LPCWSTR) _src.filePath().utf16(), (LPCWSTR) _dest.filePath().utf16()) == 0) #endif { DWORD dwLastErr = GetLastError(); if (dwLastErr == ERROR_FILE_NOT_FOUND) { error(KIO::ERR_DOES_NOT_EXIST, _src.filePath()); } else if (dwLastErr == ERROR_ACCESS_DENIED) { error(KIO::ERR_ACCESS_DENIED, _dest.filePath()); } else { error(KIO::ERR_CANNOT_RENAME, _src.filePath()); qCDebug(KIO_FILE) << "Renaming file " << _src.filePath() << " failed (" << dwLastErr << ")"; } return; } finished(); } void FileProtocol::symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags) { QString localDest = dest.toLocalFile(); //TODO handle overwrite, etc if (!KIOPrivate::createSymlink(target, localDest)) { error(KIO::ERR_UNKNOWN, localDest); } } void FileProtocol::del(const QUrl &url, bool isfile) { QString _path(url.toLocalFile()); /***** * Delete files *****/ if (isfile) { // qDebug() << "Deleting file " << _path; if (DeleteFileW((LPCWSTR) _path.utf16()) == 0) { DWORD dwLastErr = GetLastError(); if (dwLastErr == ERROR_PATH_NOT_FOUND) { error(KIO::ERR_DOES_NOT_EXIST, _path); } else if (dwLastErr == ERROR_ACCESS_DENIED) { error(KIO::ERR_ACCESS_DENIED, _path); } else { error(KIO::ERR_CANNOT_DELETE, _path); // qDebug() << "Deleting file " << _path << " failed (" << dwLastErr << ")"; } } } else { // qDebug() << "Deleting directory " << _path; if (!deleteRecursive(_path)) { return; } if (RemoveDirectoryW((LPCWSTR) _path.utf16()) == 0) { DWORD dwLastErr = GetLastError(); if (dwLastErr == ERROR_FILE_NOT_FOUND) { error(KIO::ERR_DOES_NOT_EXIST, _path); } else if (dwLastErr == ERROR_ACCESS_DENIED) { error(KIO::ERR_ACCESS_DENIED, _path); } else { error(KIO::ERR_CANNOT_DELETE, _path); // qDebug() << "Deleting directory " << _path << " failed (" << dwLastErr << ")"; } } } finished(); } void FileProtocol::chown(const QUrl &url, const QString &, const QString &) { error(KIO::ERR_CANNOT_CHOWN, url.toLocalFile()); } void FileProtocol::stat(const QUrl &url) { if (!url.isLocalFile()) { QUrl redir(url); redir.setScheme(config()->readEntry("DefaultRemoteProtocol", "smb")); redirection(redir); // qDebug() << "redirecting to " << redir; finished(); return; } const QString sDetails = metaData(QLatin1String("details")); int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); // qDebug() << "FileProtocol::stat details=" << details; const QString localFile = url.toLocalFile(); QFileInfo fileInfo(localFile); if (!fileInfo.exists()) { error(KIO::ERR_DOES_NOT_EXIST, localFile); return; } UDSEntry entry = createUDSEntryWin(fileInfo); statEntry(entry); finished(); } bool FileProtocol::privilegeOperationUnitTestMode() { return false; } PrivilegeOperationReturnValue FileProtocol::execWithElevatedPrivilege(ActionType, const QVariant &, const QVariant &, const QVariant &) { return PrivilegeOperationReturnValue::failure(); } +PrivilegeOperationReturnValue tryOpen(QFile &f, const QByteArray &, int , int) +{ + return PrivilegeOperationReturnValue::failure(); +} + +PrivilegeOperationReturnValue tryChangeFileAttr(ActionType , const QVariant &, const QVariant &, const QVariant &) +{ + return PrivilegeOperationReturnValue::failure(); +} diff --git a/src/ioslaves/file/kauth/filehelper.cpp b/src/ioslaves/file/kauth/filehelper.cpp index 9bd26382..dd55e0cd 100644 --- a/src/ioslaves/file/kauth/filehelper.cpp +++ b/src/ioslaves/file/kauth/filehelper.cpp @@ -1,139 +1,140 @@ /*** Copyright (C) 2017 by Chinmoy Ranjan Pradhan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . ***/ #include #include #include #include #include +#include #include "filehelper.h" #include "fdsender.h" #include "../file_p.h" static bool sendFileDescriptor(int fd, const char *socketPath) { FdSender fdSender(socketPath); if (fdSender.isConnected() && fdSender.sendFileDescriptor(fd)) { return true; } return false; } ActionReply FileHelper::exec(const QVariantMap &args) { ActionReply reply; QByteArray data = args["arguments"].toByteArray(); QDataStream in(data); int action; QVariant arg1, arg2, arg3, arg4; in >> action >> arg1 >> arg2 >> arg3 >> arg4; // the path of an existing or a new file/dir upon which the method will operate const QByteArray path = arg1.toByteArray(); switch(action) { case CHMOD: { int mode = arg2.toInt(); if (chmod(path.data(), mode) == 0) { return reply; } break; } case CHOWN: { int uid = arg2.toInt(); int gid = arg3.toInt(); if (chown(path.data(), uid, gid) == 0) { return reply; } break; } case DEL: { if (unlink(path.data()) == 0) { return reply; } break; } case MKDIR: { if (mkdir(path.data(), 0777) == 0) { return reply; } break; } case OPEN: { int oflags = arg2.toInt(); int mode = arg3.toInt(); int fd = open(path.data(), oflags, mode); bool success = (fd != -1) && sendFileDescriptor(fd, arg4.toByteArray().constData()); close(fd); if (success) { return reply; } break; } case OPENDIR: { DIR *dp = opendir(path.data()); bool success = false; if (dp) { int fd = dirfd(dp); success = (fd != -1) && sendFileDescriptor(fd, arg4.toByteArray().constData()); closedir(dp); if (success) { return reply; } } break; } case RENAME: { const QByteArray newName = arg2.toByteArray(); if (rename(path.data(), newName.data()) == 0) { return reply; } break; } case RMDIR: { if (rmdir(path.data()) == 0) { return reply; } break; } case SYMLINK: { const QByteArray target = arg2.toByteArray(); if (symlink(target.data(), path.data()) == 0) { return reply; } break; } case UTIME: { utimbuf ut; ut.actime = arg2.toULongLong(); ut.modtime = arg3.toULongLong(); if (utime(path.data(), &ut) == 0) { return reply; } break; } }; reply.setError(errno ? errno : -1); return reply; } KAUTH_HELPER_MAIN("org.kde.kio.file", FileHelper)