diff --git a/src/core/global.h b/src/core/global.h index 770dc906..90796539 100644 --- a/src/core/global.h +++ b/src/core/global.h @@ -1,344 +1,345 @@ /* This file is part of the KDE libraries Copyright (C) 2000-2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 KIO_GLOBAL_H #define KIO_GLOBAL_H #include "kiocore_export.h" #include #include // for QFile::Permissions #include #include "metadata.h" // for source compat #include "jobtracker.h" // for source compat class QUrl; class QTime; #if defined(Q_OS_WIN) && defined(Q_CC_MSVC) // on windows ssize_t is not defined, only SSIZE_T exists #include typedef SSIZE_T ssize_t; #endif /** * @short A namespace for KIO globals * */ namespace KIO { /// 64-bit file offset typedef qlonglong fileoffset_t; /// 64-bit file size typedef qulonglong filesize_t; /** * Converts @p size from bytes to the string representation. * * @param size size in bytes * @return converted size as a string - e.g. 123.4 KiB , 12.0 MiB */ KIOCORE_EXPORT QString convertSize(KIO::filesize_t size); /** * Converts a size to a string representation * Not unlike QString::number(...) * * @param size size in bytes * @return converted size as a string - e.g. 123456789 */ KIOCORE_EXPORT QString number(KIO::filesize_t size); /** * Converts size from kibi-bytes (2^10) to the string representation. * * @param kibSize size in kibi-bytes (2^10) * @return converted size as a string - e.g. 123.4 KiB , 12.0 MiB */ KIOCORE_EXPORT QString convertSizeFromKiB(KIO::filesize_t kibSize); /** * Calculates remaining time in seconds from total size, processed size and speed. * * @param totalSize total size in bytes * @param processedSize processed size in bytes * @param speed speed in bytes per second * @return calculated remaining time in seconds */ KIOCORE_EXPORT unsigned int calculateRemainingSeconds(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed); /** * Convert @p seconds to a string representing number of days, hours, minutes and seconds * * @param seconds number of seconds to convert * @return string representation in a locale depending format */ KIOCORE_EXPORT QString convertSeconds(unsigned int seconds); /** * Calculates remaining time from total size, processed size and speed. * Warning: As QTime is limited to 23:59:59, use calculateRemainingSeconds() instead * * @param totalSize total size in bytes * @param processedSize processed size in bytes * @param speed speed in bytes per second * @return calculated remaining time */ #ifndef KIOCORE_NO_DEPRECATED KIOCORE_DEPRECATED_EXPORT QTime calculateRemaining(KIO::filesize_t totalSize, KIO::filesize_t processedSize, KIO::filesize_t speed); #endif /** * Helper for showing information about a set of files and directories * @param items the number of items (= @p files + @p dirs + number of symlinks :) * @param files the number of files * @param dirs the number of dirs * @param size the sum of the size of the @p files * @param showSize whether to show the size in the result * @return the summary string */ KIOCORE_EXPORT QString itemsSummaryString(uint items, uint files, uint dirs, KIO::filesize_t size, bool showSize); /** * Encodes (from the text displayed to the real filename) * This translates '/' into a "unicode fraction slash", QChar(0x2044). * Used by KIO::link, for instance. * @param str the file name to encode * @return the encoded file name */ KIOCORE_EXPORT QString encodeFileName(const QString &str); /** * Decodes (from the filename to the text displayed) * This doesn't do anything anymore, it used to do the opposite of encodeFileName * when encodeFileName was using %2F for '/'. * @param str the file name to decode * @return the decoded file name */ KIOCORE_EXPORT QString decodeFileName(const QString &str); /** * Given a directory path and a filename (which usually exists already), * this function returns a suggested name for a file that doesn't exist * in that directory. The existence is only checked for local urls though. * The suggested file name is of the form "foo 1", "foo 2" etc. * @since 5.0 */ KIOCORE_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName); /** * Error codes that can be emitted by KIO. */ enum Error { ERR_CANNOT_OPEN_FOR_READING = KJob::UserDefinedError + 1, ERR_CANNOT_OPEN_FOR_WRITING = KJob::UserDefinedError + 2, ERR_CANNOT_LAUNCH_PROCESS = KJob::UserDefinedError + 3, ERR_INTERNAL = KJob::UserDefinedError + 4, ERR_MALFORMED_URL = KJob::UserDefinedError + 5, ERR_UNSUPPORTED_PROTOCOL = KJob::UserDefinedError + 6, ERR_NO_SOURCE_PROTOCOL = KJob::UserDefinedError + 7, ERR_UNSUPPORTED_ACTION = KJob::UserDefinedError + 8, ERR_IS_DIRECTORY = KJob::UserDefinedError + 9, ///< ... where a file was expected ERR_IS_FILE = KJob::UserDefinedError + 10, ///< ... where a directory was expected (e.g. listing) ERR_DOES_NOT_EXIST = KJob::UserDefinedError + 11, ERR_FILE_ALREADY_EXIST = KJob::UserDefinedError + 12, ERR_DIR_ALREADY_EXIST = KJob::UserDefinedError + 13, ERR_UNKNOWN_HOST = KJob::UserDefinedError + 14, ERR_ACCESS_DENIED = KJob::UserDefinedError + 15, ERR_WRITE_ACCESS_DENIED = KJob::UserDefinedError + 16, ERR_CANNOT_ENTER_DIRECTORY = KJob::UserDefinedError + 17, ERR_PROTOCOL_IS_NOT_A_FILESYSTEM = KJob::UserDefinedError + 18, ERR_CYCLIC_LINK = KJob::UserDefinedError + 19, ERR_USER_CANCELED = KJob::KilledJobError, ERR_CYCLIC_COPY = KJob::UserDefinedError + 21, ERR_COULD_NOT_CREATE_SOCKET = KJob::UserDefinedError + 22, ///< @deprecated ERR_CANNOT_CREATE_SOCKET = KJob::UserDefinedError + 22, ERR_COULD_NOT_CONNECT = KJob::UserDefinedError + 23, ///< @deprecated ERR_CANNOT_CONNECT = KJob::UserDefinedError + 23, ERR_CONNECTION_BROKEN = KJob::UserDefinedError + 24, ERR_NOT_FILTER_PROTOCOL = KJob::UserDefinedError + 25, ERR_COULD_NOT_MOUNT = KJob::UserDefinedError + 26, ///< @deprecated ERR_CANNOT_MOUNT = KJob::UserDefinedError + 26, ERR_COULD_NOT_UNMOUNT = KJob::UserDefinedError + 27, ///< @deprecated ERR_CANNOT_UNMOUNT = KJob::UserDefinedError + 27, ERR_COULD_NOT_READ = KJob::UserDefinedError + 28, ///< @deprecated ERR_CANNOT_READ = KJob::UserDefinedError + 28, ERR_COULD_NOT_WRITE = KJob::UserDefinedError + 29, ///< @deprecated ERR_CANNOT_WRITE = KJob::UserDefinedError + 29, ERR_COULD_NOT_BIND = KJob::UserDefinedError + 30, ///< @deprecated ERR_CANNOT_BIND = KJob::UserDefinedError + 30, ERR_COULD_NOT_LISTEN = KJob::UserDefinedError + 31, ///< @deprecated ERR_CANNOT_LISTEN = KJob::UserDefinedError + 31, ERR_COULD_NOT_ACCEPT = KJob::UserDefinedError + 32, ///< @deprecated ERR_CANNOT_ACCEPT = KJob::UserDefinedError + 32, ERR_COULD_NOT_LOGIN = KJob::UserDefinedError + 33, ///< @deprecated ERR_CANNOT_LOGIN = KJob::UserDefinedError + 33, ERR_COULD_NOT_STAT = KJob::UserDefinedError + 34, ///< @deprecated ERR_CANNOT_STAT = KJob::UserDefinedError + 34, ERR_COULD_NOT_CLOSEDIR = KJob::UserDefinedError + 35, ///< @deprecated ERR_CANNOT_CLOSEDIR = KJob::UserDefinedError + 35, ERR_COULD_NOT_MKDIR = KJob::UserDefinedError + 37, ///< @deprecated ERR_CANNOT_MKDIR = KJob::UserDefinedError + 37, ERR_COULD_NOT_RMDIR = KJob::UserDefinedError + 38, ///< @deprecated ERR_CANNOT_RMDIR = KJob::UserDefinedError + 38, ERR_CANNOT_RESUME = KJob::UserDefinedError + 39, ERR_CANNOT_RENAME = KJob::UserDefinedError + 40, ERR_CANNOT_CHMOD = KJob::UserDefinedError + 41, ERR_CANNOT_DELETE = KJob::UserDefinedError + 42, // The text argument is the protocol that the dead slave supported. // This means for example: file, ftp, http, ... ERR_SLAVE_DIED = KJob::UserDefinedError + 43, ERR_OUT_OF_MEMORY = KJob::UserDefinedError + 44, ERR_UNKNOWN_PROXY_HOST = KJob::UserDefinedError + 45, ERR_COULD_NOT_AUTHENTICATE = KJob::UserDefinedError + 46, ///< @deprecated ERR_CANNOT_AUTHENTICATE = KJob::UserDefinedError + 46, ERR_ABORTED = KJob::UserDefinedError + 47, ///< Action got aborted from application side ERR_INTERNAL_SERVER = KJob::UserDefinedError + 48, ERR_SERVER_TIMEOUT = KJob::UserDefinedError + 49, ERR_SERVICE_NOT_AVAILABLE = KJob::UserDefinedError + 50, ERR_UNKNOWN = KJob::UserDefinedError + 51, // (was a warning) ERR_CHECKSUM_MISMATCH = 52, ERR_UNKNOWN_INTERRUPT = KJob::UserDefinedError + 53, ERR_CANNOT_DELETE_ORIGINAL = KJob::UserDefinedError + 54, ERR_CANNOT_DELETE_PARTIAL = KJob::UserDefinedError + 55, ERR_CANNOT_RENAME_ORIGINAL = KJob::UserDefinedError + 56, ERR_CANNOT_RENAME_PARTIAL = KJob::UserDefinedError + 57, ERR_NEED_PASSWD = KJob::UserDefinedError + 58, ERR_CANNOT_SYMLINK = KJob::UserDefinedError + 59, ERR_NO_CONTENT = KJob::UserDefinedError + 60, ///< Action succeeded but no content will follow. ERR_DISK_FULL = KJob::UserDefinedError + 61, ERR_IDENTICAL_FILES = KJob::UserDefinedError + 62, ///< src==dest when moving/copying ERR_SLAVE_DEFINED = KJob::UserDefinedError + 63, ///< for slave specified errors that can be ///< rich text. Email links will be handled ///< by the standard email app and all hrefs ///< will be handled by the standard browser. ///< This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "job.h" #include "kioglobal_p.h" #include #include #include #include #include #include #include // S_IRUSR etc static const int s_maxFilePathLength = 80; QString KIO::Job::errorString() const { return KIO::buildErrorString(error(), errorText()); } KIOCORE_EXPORT QString KIO::buildErrorString(int errorCode, const QString &errorText) { QString result; switch (errorCode) { case KIO::ERR_CANNOT_OPEN_FOR_READING: result = i18n("Could not read %1.", errorText); break; case KIO::ERR_CANNOT_OPEN_FOR_WRITING: result = i18n("Could not write to %1.", KStringHandler::csqueeze(errorText, s_maxFilePathLength)); break; case KIO::ERR_CANNOT_LAUNCH_PROCESS: result = i18n("Could not start process %1.", errorText); break; case KIO::ERR_INTERNAL: result = i18n("Internal Error\nPlease send a full bug report at https://bugs.kde.org\n%1", errorText); break; case KIO::ERR_MALFORMED_URL: result = i18n("Malformed URL %1.", errorText); break; case KIO::ERR_UNSUPPORTED_PROTOCOL: result = i18n("The protocol %1 is not supported.", errorText); break; case KIO::ERR_NO_SOURCE_PROTOCOL: result = i18n("The protocol %1 is only a filter protocol.", errorText); break; case KIO::ERR_UNSUPPORTED_ACTION: result = errorText; // result = i18n( "Unsupported action %1" ).arg( errorText ); break; case KIO::ERR_IS_DIRECTORY: result = i18n("%1 is a folder, but a file was expected.", errorText); break; case KIO::ERR_IS_FILE: result = i18n("%1 is a file, but a folder was expected.", errorText); break; case KIO::ERR_DOES_NOT_EXIST: result = i18n("The file or folder %1 does not exist.", errorText); break; case KIO::ERR_FILE_ALREADY_EXIST: result = i18n("A file named %1 already exists.", errorText); break; case KIO::ERR_DIR_ALREADY_EXIST: result = i18n("A folder named %1 already exists.", errorText); break; case KIO::ERR_UNKNOWN_HOST: result = errorText.isEmpty() ? i18n("No hostname specified.") : i18n("Unknown host %1", errorText); break; case KIO::ERR_ACCESS_DENIED: result = i18n("Access denied to %1.", errorText); break; case KIO::ERR_WRITE_ACCESS_DENIED: result = i18n("Access denied.\nCould not write to %1.", errorText); break; case KIO::ERR_CANNOT_ENTER_DIRECTORY: result = i18n("Could not enter folder %1.", errorText); break; case KIO::ERR_PROTOCOL_IS_NOT_A_FILESYSTEM: result = i18n("The protocol %1 does not implement a folder service.", errorText); break; case KIO::ERR_CYCLIC_LINK: result = i18n("Found a cyclic link in %1.", errorText); break; case KIO::ERR_USER_CANCELED: // Do nothing in this case. The user doesn't need to be told what he just did. break; case KIO::ERR_CYCLIC_COPY: result = i18n("Found a cyclic link while copying %1.", errorText); break; case KIO::ERR_CANNOT_CREATE_SOCKET: result = i18n("Could not create socket for accessing %1.", errorText); break; case KIO::ERR_CANNOT_CONNECT: result = i18n("Could not connect to host %1.", errorText.isEmpty() ? QStringLiteral("localhost") : errorText); break; case KIO::ERR_CONNECTION_BROKEN: result = i18n("Connection to host %1 is broken.", errorText); break; case KIO::ERR_NOT_FILTER_PROTOCOL: result = i18n("The protocol %1 is not a filter protocol.", errorText); break; case KIO::ERR_CANNOT_MOUNT: result = i18n("Could not mount device.\nThe reported error was:\n%1", errorText); break; case KIO::ERR_CANNOT_UNMOUNT: result = i18n("Could not unmount device.\nThe reported error was:\n%1", errorText); break; case KIO::ERR_CANNOT_READ: result = i18n("Could not read file %1.", errorText); break; case KIO::ERR_CANNOT_WRITE: result = i18n("Could not write to file %1.", errorText); break; case KIO::ERR_CANNOT_BIND: result = i18n("Could not bind %1.", errorText); break; case KIO::ERR_CANNOT_LISTEN: result = i18n("Could not listen %1.", errorText); break; case KIO::ERR_CANNOT_ACCEPT: result = i18n("Could not accept %1.", errorText); break; case KIO::ERR_CANNOT_LOGIN: result = errorText; break; case KIO::ERR_CANNOT_STAT: result = i18n("Could not access %1.", errorText); break; case KIO::ERR_CANNOT_CLOSEDIR: result = i18n("Could not terminate listing %1.", errorText); break; case KIO::ERR_CANNOT_MKDIR: result = i18n("Could not make folder %1.", KStringHandler::csqueeze(errorText, s_maxFilePathLength)); break; case KIO::ERR_CANNOT_RMDIR: result = i18n("Could not remove folder %1.", errorText); break; case KIO::ERR_CANNOT_RESUME: result = i18n("Could not resume file %1.", errorText); break; case KIO::ERR_CANNOT_RENAME: result = i18n("Could not rename file %1.", KStringHandler::csqueeze(errorText, s_maxFilePathLength)); break; case KIO::ERR_CANNOT_CHMOD: result = i18n("Could not change permissions for %1.", errorText); break; case KIO::ERR_CANNOT_CHOWN: result = i18n("Could not change ownership for %1.", errorText); break; case KIO::ERR_CANNOT_DELETE: result = i18n("Could not delete file %1.", errorText); break; case KIO::ERR_SLAVE_DIED: result = i18n("The process for the %1 protocol died unexpectedly.", errorText); break; case KIO::ERR_OUT_OF_MEMORY: result = i18n("Error. Out of memory.\n%1", errorText); break; case KIO::ERR_UNKNOWN_PROXY_HOST: result = i18n("Unknown proxy host\n%1", errorText); break; case KIO::ERR_CANNOT_AUTHENTICATE: result = i18n("Authorization failed, %1 authentication not supported", errorText); break; case KIO::ERR_ABORTED: result = i18n("User canceled action\n%1", errorText); break; case KIO::ERR_INTERNAL_SERVER: result = i18n("Internal error in server\n%1", errorText); break; case KIO::ERR_SERVER_TIMEOUT: result = i18n("Timeout on server\n%1", errorText); break; case KIO::ERR_UNKNOWN: result = i18n("Unknown error\n%1", errorText); break; case KIO::ERR_UNKNOWN_INTERRUPT: result = i18n("Unknown interrupt\n%1", errorText); break; /* case KIO::ERR_CHECKSUM_MISMATCH: if (errorText) result = i18n( "Warning: MD5 Checksum for %1 does not match checksum returned from server" ).arg(errorText); else result = i18n( "Warning: MD5 Checksum for %1 does not match checksum returned from server" ).arg("document"); break; */ case KIO::ERR_CANNOT_DELETE_ORIGINAL: result = i18n("Could not delete original file %1.\nPlease check permissions.", errorText); break; case KIO::ERR_CANNOT_DELETE_PARTIAL: result = i18n("Could not delete partial file %1.\nPlease check permissions.", errorText); break; case KIO::ERR_CANNOT_RENAME_ORIGINAL: result = i18n("Could not rename original file %1.\nPlease check permissions.", errorText); break; case KIO::ERR_CANNOT_RENAME_PARTIAL: result = i18n("Could not rename partial file %1.\nPlease check permissions.", errorText); break; case KIO::ERR_CANNOT_SYMLINK: result = i18n("Could not create symlink %1.\nPlease check permissions.", errorText); break; case KIO::ERR_NO_CONTENT: result = errorText; break; case KIO::ERR_DISK_FULL: result = i18n("There is not enough space on the disk to write %1.", errorText); break; case KIO::ERR_IDENTICAL_FILES: result = i18n("The source and destination are the same file.\n%1", errorText); break; case KIO::ERR_SLAVE_DEFINED: result = errorText; break; case KIO::ERR_UPGRADE_REQUIRED: result = i18n("%1 is required by the server, but is not available.", errorText); break; case KIO::ERR_POST_DENIED: result = i18n("Access to restricted port in POST denied."); break; case KIO::ERR_POST_NO_SIZE: result = i18n("The required content size information was not provided for a POST operation."); break; case KIO::ERR_DROP_ON_ITSELF: result = i18n("A file or folder cannot be dropped onto itself"); break; case KIO::ERR_CANNOT_MOVE_INTO_ITSELF: result = i18n("A folder cannot be moved into itself"); break; case KIO::ERR_PASSWD_SERVER: result = i18n("Communication with the local password server failed"); break; case KIO::ERR_CANNOT_CREATE_SLAVE: result = i18n("Unable to create io-slave. %1", errorText); break; case KIO::ERR_FILE_TOO_LARGE_FOR_FAT32: result = xi18nc("@info", "Cannot transfer %1 because it is too large. The destination filesystem only supports files up to 4GiB", errorText); break; + case KIO::ERR_PRIVILEGE_NOT_REQUIRED: + result = i18n("Privilege escalation is not necessary because \n'%1' is owned by the current user.\nPlease retry after changing permissions.", errorText); + break; default: result = i18n("Unknown error code %1\n%2\nPlease send a full bug report at https://bugs.kde.org.", errorCode, errorText); break; } return result; } QStringList KIO::Job::detailedErrorStrings(const QUrl *reqUrl /*= 0*/, int method /*= -1*/) const { QString errorName, techName, description, ret2; QStringList causes, solutions, ret; QByteArray raw = rawErrorDetail(error(), errorText(), reqUrl, method); QDataStream stream(raw); stream >> errorName >> techName >> description >> causes >> solutions; QString url, protocol, datetime; if (reqUrl) { QString prettyUrl; prettyUrl = reqUrl->toDisplayString(); url = prettyUrl.toHtmlEscaped(); protocol = reqUrl->scheme(); } else { url = i18nc("@info url", "(unknown)"); } datetime = QDateTime::currentDateTime().toString(Qt::DefaultLocaleLongDate); ret << errorName; ret << i18nc("@info %1 error name, %2 description", "

%1

%2

", errorName, description); ret2 = QStringLiteral(""); if (!techName.isEmpty()) ret2 += QLatin1String("

") + i18n("Technical reason: ") + techName + QLatin1String("

"); ret2 += QLatin1String("

") + i18n("Details of the request:") + QLatin1String("

    ") + i18n("
  • URL: %1
  • ", url); if (!protocol.isEmpty()) { ret2 += i18n("
  • Protocol: %1
  • ", protocol); } ret2 += i18n("
  • Date and time: %1
  • ", datetime) + i18n("
  • Additional information: %1
  • ", errorText()) + QLatin1String("
"); if (!causes.isEmpty()) { ret2 += QLatin1String("

") + i18n("Possible causes:") + QLatin1String("

  • ") + causes.join(QStringLiteral("
  • ")) + QLatin1String("
"); } if (!solutions.isEmpty()) { ret2 += QLatin1String("

") + i18n("Possible solutions:") + QLatin1String("

  • ") + solutions.join(QStringLiteral("
  • ")) + QLatin1String("
"); } ret2 += QLatin1String("
"); ret << ret2; return ret; } KIOCORE_EXPORT QByteArray KIO::rawErrorDetail(int errorCode, const QString &errorText, const QUrl *reqUrl /*= 0*/, int /*method = -1*/) { QString url, host, protocol, datetime, domain, path, filename; bool isSlaveNetwork = false; if (reqUrl) { url = reqUrl->toDisplayString(); host = reqUrl->host(); protocol = reqUrl->scheme(); if (host.startsWith(QLatin1String("www."))) { domain = host.mid(4); } else { domain = host; } filename = reqUrl->fileName(); path = reqUrl->path(); // detect if protocol is a network protocol... isSlaveNetwork = KProtocolInfo::protocolClass(protocol) == QLatin1String(":internet"); } else { // assume that the errorText has the location we are interested in url = host = domain = path = filename = errorText; protocol = i18nc("@info protocol", "(unknown)"); } datetime = QDateTime::currentDateTime().toString(Qt::DefaultLocaleLongDate); QString errorName, techName, description; QStringList causes, solutions; // c == cause, s == solution QString sSysadmin = i18n("Contact your appropriate computer support system, " "whether the system administrator, or technical support group for further " "assistance."); QString sServeradmin = i18n("Contact the administrator of the server " "for further assistance."); // FIXME active link to permissions dialog QString sAccess = i18n("Check your access permissions on this resource."); QString cAccess = i18n("Your access permissions may be inadequate to " "perform the requested operation on this resource."); QString cLocked = i18n("The file may be in use (and thus locked) by " "another user or application."); QString sQuerylock = i18n("Check to make sure that no other " "application or user is using the file or has locked the file."); QString cHardware = i18n("Although unlikely, a hardware error may have " "occurred."); QString cBug = i18n("You may have encountered a bug in the program."); QString cBuglikely = i18n("This is most likely to be caused by a bug in the " "program. Please consider submitting a full bug report as detailed below."); QString sUpdate = i18n("Update your software to the latest version. " "Your distribution should provide tools to update your software."); QString sBugreport = i18n("When all else fails, please consider helping the " "KDE team or the third party maintainer of this software by submitting a " "high quality bug report. If the software is provided by a third party, " "please contact them directly. Otherwise, first look to see if " "the same bug has been submitted by someone else by searching at the " "
KDE bug reporting website. If not, take " "note of the details given above, and include them in your bug report, along " "with as many other details as you think might help."); QString cNetwork = i18n("There may have been a problem with your network " "connection."); // FIXME netconf kcontrol link QString cNetconf = i18n("There may have been a problem with your network " "configuration. If you have been accessing the Internet with no problems " "recently, this is unlikely."); QString cNetpath = i18n("There may have been a problem at some point along " "the network path between the server and this computer."); QString sTryagain = i18n("Try again, either now or at a later time."); QString cProtocol = i18n("A protocol error or incompatibility may have occurred."); QString sExists = i18n("Ensure that the resource exists, and try again."); QString cExists = i18n("The specified resource may not exist."); QString sTypo = i18n("Double-check that you have entered the correct location " "and try again."); QString sNetwork = i18n("Check your network connection status."); switch (errorCode) { case KIO::ERR_CANNOT_OPEN_FOR_READING: errorName = i18n("Cannot Open Resource For Reading"); description = i18n("This means that the contents of the requested file " "or folder %1 could not be retrieved, as read " "access could not be obtained.", path); causes << i18n("You may not have permissions to read the file or open " "the folder.") << cLocked << cHardware; solutions << sAccess << sQuerylock << sSysadmin; break; case KIO::ERR_CANNOT_OPEN_FOR_WRITING: errorName = i18n("Cannot Open Resource For Writing"); description = i18n("This means that the file, %1, could " "not be written to as requested, because access with permission to " "write could not be obtained.", KStringHandler::csqueeze(filename, s_maxFilePathLength)); causes << cAccess << cLocked << cHardware; solutions << sAccess << sQuerylock << sSysadmin; break; case KIO::ERR_CANNOT_LAUNCH_PROCESS: errorName = i18n("Cannot Launch Process required by the %1 Protocol", protocol); techName = i18n("Unable to Launch Process"); description = i18n("The program on your computer which provides access " "to the %1 protocol could not be found or started. This is " "usually due to technical reasons.", protocol); causes << i18n("The program which provides compatibility with this " "protocol may not have been updated with your last update of KDE. " "This can cause the program to be incompatible with the current version " "and thus not start.") << cBug; solutions << sUpdate << sSysadmin; break; case KIO::ERR_INTERNAL: errorName = i18n("Internal Error"); description = i18n("The program on your computer which provides access " "to the %1 protocol has reported an internal error.", protocol); causes << cBuglikely; solutions << sUpdate << sBugreport; break; case KIO::ERR_MALFORMED_URL: errorName = i18n("Improperly Formatted URL"); description = i18n("The Uniform Resource " "Locator (URL) that you entered was not properly " "formatted. The format of a URL is generally as follows:" "
protocol://user:password@www.example.org:port/folder/" "filename.extension?query=value
"); solutions << sTypo; break; case KIO::ERR_UNSUPPORTED_PROTOCOL: errorName = i18n("Unsupported Protocol %1", protocol); description = i18n("The protocol %1 is not supported " "by the KDE programs currently installed on this computer.", protocol); causes << i18n("The requested protocol may not be supported.") << i18n("The versions of the %1 protocol supported by this computer and " "the server may be incompatible.", protocol); solutions << i18n("You may perform a search on the Internet for a KDE " "program (called a kioslave or ioslave) which supports this protocol. " "Places to search include " "https://kde-apps.org/ and " "http://freshmeat.net/.") << sUpdate << sSysadmin; break; case KIO::ERR_NO_SOURCE_PROTOCOL: errorName = i18n("URL Does Not Refer to a Resource."); techName = i18n("Protocol is a Filter Protocol"); description = i18n("The Uniform Resource " "Locator (URL) that you entered did not refer to a " "specific resource."); causes << i18n("KDE is able to communicate through a protocol within a " "protocol; the protocol specified is only for use in such situations, " "however this is not one of these situations. This is a rare event, and " "is likely to indicate a programming error."); solutions << sTypo; break; case KIO::ERR_UNSUPPORTED_ACTION: errorName = i18n("Unsupported Action: %1", errorText); description = i18n("The requested action is not supported by the KDE " "program which is implementing the %1 protocol.", protocol); causes << i18n("This error is very much dependent on the KDE program. The " "additional information should give you more information than is available " "to the KDE input/output architecture."); solutions << i18n("Attempt to find another way to accomplish the same " "outcome."); break; case KIO::ERR_IS_DIRECTORY: errorName = i18n("File Expected"); description = i18n("The request expected a file, however the " "folder %1 was found instead.", path); causes << i18n("This may be an error on the server side.") << cBug; solutions << sUpdate << sSysadmin; break; case KIO::ERR_IS_FILE: errorName = i18n("Folder Expected"); description = i18n("The request expected a folder, however " "the file %1 was found instead.", filename); causes << cBug; solutions << sUpdate << sSysadmin; break; case KIO::ERR_DOES_NOT_EXIST: errorName = i18n("File or Folder Does Not Exist"); description = i18n("The specified file or folder %1 " "does not exist.", path); causes << cExists; solutions << sExists; break; case KIO::ERR_FILE_ALREADY_EXIST: errorName = i18n("File Already Exists"); description = i18n("The requested file could not be created because a " "file with the same name already exists."); solutions << i18n("Try moving the current file out of the way first, " "and then try again.") << i18n("Delete the current file and try again.") << i18n("Choose an alternate filename for the new file."); break; case KIO::ERR_DIR_ALREADY_EXIST: errorName = i18n("Folder Already Exists"); description = i18n("The requested folder could not be created because " "a folder with the same name already exists."); solutions << i18n("Try moving the current folder out of the way first, " "and then try again.") << i18n("Delete the current folder and try again.") << i18n("Choose an alternate name for the new folder."); break; case KIO::ERR_UNKNOWN_HOST: errorName = i18n("Unknown Host"); description = i18n("An unknown host error indicates that the server with " "the requested name, %1, could not be " "located on the Internet.", host); causes << i18n("The name that you typed, %1, may not exist: it may be " "incorrectly typed.", host) << cNetwork << cNetconf; solutions << sNetwork << sSysadmin; break; case KIO::ERR_ACCESS_DENIED: errorName = i18n("Access Denied"); description = i18n("Access was denied to the specified resource, " "%1.", url); causes << i18n("You may have supplied incorrect authentication details or " "none at all.") << i18n("Your account may not have permission to access the " "specified resource."); solutions << i18n("Retry the request and ensure your authentication details " "are entered correctly.") << sSysadmin; if (!isSlaveNetwork) { solutions << sServeradmin; } break; case KIO::ERR_WRITE_ACCESS_DENIED: errorName = i18n("Write Access Denied"); description = i18n("This means that an attempt to write to the file " "%1 was rejected.", filename); causes << cAccess << cLocked << cHardware; solutions << sAccess << sQuerylock << sSysadmin; break; case KIO::ERR_CANNOT_ENTER_DIRECTORY: errorName = i18n("Unable to Enter Folder"); description = i18n("This means that an attempt to enter (in other words, " "to open) the requested folder %1 was rejected.", path); causes << cAccess << cLocked; solutions << sAccess << sQuerylock << sSysadmin; break; case KIO::ERR_PROTOCOL_IS_NOT_A_FILESYSTEM: errorName = i18n("Folder Listing Unavailable"); techName = i18n("Protocol %1 is not a Filesystem", protocol); description = i18n("This means that a request was made which requires " "determining the contents of the folder, and the KDE program supporting " "this protocol is unable to do so."); causes << cBug; solutions << sUpdate << sBugreport; break; case KIO::ERR_CYCLIC_LINK: errorName = i18n("Cyclic Link Detected"); description = i18n("UNIX environments are commonly able to link a file or " "folder to a separate name and/or location. KDE detected a link or " "series of links that results in an infinite loop - i.e. the file was " "(perhaps in a roundabout way) linked to itself."); solutions << i18n("Delete one part of the loop in order that it does not " "cause an infinite loop, and try again.") << sSysadmin; break; case KIO::ERR_USER_CANCELED: // Do nothing in this case. The user doesn't need to be told what he just did. // rodda: However, if we have been called, an application is about to display // this information anyway. If we don't return sensible information, the // user sees a blank dialog (I have seen this myself) errorName = i18n("Request Aborted By User"); description = i18n("The request was not completed because it was " "aborted."); solutions << i18n("Retry the request."); break; case KIO::ERR_CYCLIC_COPY: errorName = i18n("Cyclic Link Detected During Copy"); description = i18n("UNIX environments are commonly able to link a file or " "folder to a separate name and/or location. During the requested copy " "operation, KDE detected a link or series of links that results in an " "infinite loop - i.e. the file was (perhaps in a roundabout way) linked " "to itself."); solutions << i18n("Delete one part of the loop in order that it does not " "cause an infinite loop, and try again.") << sSysadmin; break; case KIO::ERR_CANNOT_CREATE_SOCKET: errorName = i18n("Could Not Create Network Connection"); techName = i18n("Could Not Create Socket"); description = i18n("This is a fairly technical error in which a required " "device for network communications (a socket) could not be created."); causes << i18n("The network connection may be incorrectly configured, or " "the network interface may not be enabled."); solutions << sNetwork << sSysadmin; break; case KIO::ERR_CANNOT_CONNECT: errorName = i18n("Connection to Server Refused"); description = i18n("The server %1 refused to allow this " "computer to make a connection.", host); causes << i18n("The server, while currently connected to the Internet, " "may not be configured to allow requests.") << i18n("The server, while currently connected to the Internet, " "may not be running the requested service (%1).", protocol) << i18n("A network firewall (a device which restricts Internet " "requests), either protecting your network or the network of the server, " "may have intervened, preventing this request."); solutions << sTryagain << sServeradmin << sSysadmin; break; case KIO::ERR_CONNECTION_BROKEN: errorName = i18n("Connection to Server Closed Unexpectedly"); description = i18n("Although a connection was established to " "%1, the connection was closed at an unexpected point " "in the communication.", host); causes << cNetwork << cNetpath << i18n("A protocol error may have occurred, " "causing the server to close the connection as a response to the error."); solutions << sTryagain << sServeradmin << sSysadmin; break; case KIO::ERR_NOT_FILTER_PROTOCOL: errorName = i18n("URL Resource Invalid"); techName = i18n("Protocol %1 is not a Filter Protocol", protocol); description = i18n("The Uniform Resource " "Locator (URL) that you entered did not refer to " "a valid mechanism of accessing the specific resource, " "%1%2.", !host.isNull() ? host + QLatin1Char('/') : QString(), path); causes << i18n("KDE is able to communicate through a protocol within a " "protocol. This request specified a protocol be used as such, however " "this protocol is not capable of such an action. This is a rare event, " "and is likely to indicate a programming error."); solutions << sTypo << sSysadmin; break; case KIO::ERR_CANNOT_MOUNT: errorName = i18n("Unable to Initialize Input/Output Device"); techName = i18n("Could Not Mount Device"); description = i18n("The requested device could not be initialized " "(\"mounted\"). The reported error was: %1", errorText); causes << i18n("The device may not be ready, for example there may be " "no media in a removable media device (i.e. no CD-ROM in a CD drive), " "or in the case of a peripheral/portable device, the device may not " "be correctly connected.") << i18n("You may not have permissions to initialize (\"mount\") the " "device. On UNIX systems, often system administrator privileges are " "required to initialize a device.") << cHardware; solutions << i18n("Check that the device is ready; removable drives " "must contain media, and portable devices must be connected and powered " "on.; and try again.") << sAccess << sSysadmin; break; case KIO::ERR_CANNOT_UNMOUNT: errorName = i18n("Unable to Uninitialize Input/Output Device"); techName = i18n("Could Not Unmount Device"); description = i18n("The requested device could not be uninitialized " "(\"unmounted\"). The reported error was: %1", errorText); causes << i18n("The device may be busy, that is, still in use by " "another application or user. Even such things as having an open " "browser window on a location on this device may cause the device to " "remain in use.") << i18n("You may not have permissions to uninitialize (\"unmount\") " "the device. On UNIX systems, system administrator privileges are " "often required to uninitialize a device.") << cHardware; solutions << i18n("Check that no applications are accessing the device, " "and try again.") << sAccess << sSysadmin; break; case KIO::ERR_CANNOT_READ: errorName = i18n("Cannot Read From Resource"); description = i18n("This means that although the resource, " "%1, was able to be opened, an error occurred while " "reading the contents of the resource.", url); causes << i18n("You may not have permissions to read from the resource."); if (!isSlaveNetwork) { causes << cNetwork; } causes << cHardware; solutions << sAccess; if (!isSlaveNetwork) { solutions << sNetwork; } solutions << sSysadmin; break; case KIO::ERR_CANNOT_WRITE: errorName = i18n("Cannot Write to Resource"); description = i18n("This means that although the resource, %1" ", was able to be opened, an error occurred while writing to the resource.", url); causes << i18n("You may not have permissions to write to the resource."); if (!isSlaveNetwork) { causes << cNetwork; } causes << cHardware; solutions << sAccess; if (!isSlaveNetwork) { solutions << sNetwork; } solutions << sSysadmin; break; case KIO::ERR_CANNOT_BIND: errorName = i18n("Could Not Listen for Network Connections"); techName = i18n("Could Not Bind"); description = i18n("This is a fairly technical error in which a required " "device for network communications (a socket) could not be established " "to listen for incoming network connections."); causes << i18n("The network connection may be incorrectly configured, or " "the network interface may not be enabled."); solutions << sNetwork << sSysadmin; break; case KIO::ERR_CANNOT_LISTEN: errorName = i18n("Could Not Listen for Network Connections"); techName = i18n("Could Not Listen"); description = i18n("This is a fairly technical error in which a required " "device for network communications (a socket) could not be established " "to listen for incoming network connections."); causes << i18n("The network connection may be incorrectly configured, or " "the network interface may not be enabled."); solutions << sNetwork << sSysadmin; break; case KIO::ERR_CANNOT_ACCEPT: errorName = i18n("Could Not Accept Network Connection"); description = i18n("This is a fairly technical error in which an error " "occurred while attempting to accept an incoming network connection."); causes << i18n("The network connection may be incorrectly configured, or " "the network interface may not be enabled.") << i18n("You may not have permissions to accept the connection."); solutions << sNetwork << sSysadmin; break; case KIO::ERR_CANNOT_LOGIN: errorName = i18n("Could Not Login: %1", errorText); description = i18n("An attempt to login to perform the requested " "operation was unsuccessful."); causes << i18n("You may have supplied incorrect authentication details or " "none at all.") << i18n("Your account may not have permission to access the " "specified resource.") << cProtocol; solutions << i18n("Retry the request and ensure your authentication details " "are entered correctly.") << sServeradmin << sSysadmin; break; case KIO::ERR_CANNOT_STAT: errorName = i18n("Could Not Determine Resource Status"); techName = i18n("Could Not Stat Resource"); description = i18n("An attempt to determine information about the status " "of the resource %1, such as the resource name, type, " "size, etc., was unsuccessful.", url); causes << i18n("The specified resource may not have existed or may " "not be accessible.") << cProtocol << cHardware; solutions << i18n("Retry the request and ensure your authentication details " "are entered correctly.") << sSysadmin; break; case KIO::ERR_CANNOT_CLOSEDIR: //result = i18n( "Could not terminate listing %1" ).arg( errorText ); errorName = i18n("Could Not Cancel Listing"); techName = i18n("FIXME: Document this"); break; case KIO::ERR_CANNOT_MKDIR: errorName = i18n("Could Not Create Folder"); description = i18n("An attempt to create the requested folder failed."); causes << cAccess << i18n("The location where the folder was to be created " "may not exist."); if (!isSlaveNetwork) { causes << cProtocol; } solutions << i18n("Retry the request.") << sAccess; break; case KIO::ERR_CANNOT_RMDIR: errorName = i18n("Could Not Remove Folder"); description = i18n("An attempt to remove the specified folder, " "%1, failed.", path); causes << i18n("The specified folder may not exist.") << i18n("The specified folder may not be empty.") << cAccess; if (!isSlaveNetwork) { causes << cProtocol; } solutions << i18n("Ensure that the folder exists and is empty, and try " "again.") << sAccess; break; case KIO::ERR_CANNOT_RESUME: errorName = i18n("Could Not Resume File Transfer"); description = i18n("The specified request asked that the transfer of " "file %1 be resumed at a certain point of the " "transfer. This was not possible.", filename); causes << i18n("The protocol, or the server, may not support file " "resuming."); solutions << i18n("Retry the request without attempting to resume " "transfer."); break; case KIO::ERR_CANNOT_RENAME: errorName = i18n("Could Not Rename Resource"); description = i18n("An attempt to rename the specified resource " "%1 failed.", KStringHandler::csqueeze(url, s_maxFilePathLength)); causes << cAccess << cExists; if (!isSlaveNetwork) { causes << cProtocol; } solutions << sAccess << sExists; break; case KIO::ERR_CANNOT_CHMOD: errorName = i18n("Could Not Alter Permissions of Resource"); description = i18n("An attempt to alter the permissions on the specified " "resource %1 failed.", url); causes << cAccess << cExists; solutions << sAccess << sExists; break; case KIO::ERR_CANNOT_CHOWN: errorName = i18n("Could Not Change Ownership of Resource"); description = i18n("An attempt to change the ownership of the specified " "resource %1 failed.", url); causes << cAccess << cExists; solutions << sAccess << sExists; break; case KIO::ERR_CANNOT_DELETE: errorName = i18n("Could Not Delete Resource"); description = i18n("An attempt to delete the specified resource " "%1 failed.", url); causes << cAccess << cExists; solutions << sAccess << sExists; break; case KIO::ERR_SLAVE_DIED: errorName = i18n("Unexpected Program Termination"); description = i18n("The program on your computer which provides access " "to the %1 protocol has unexpectedly terminated.", url); causes << cBuglikely; solutions << sUpdate << sBugreport; break; case KIO::ERR_OUT_OF_MEMORY: errorName = i18n("Out of Memory"); description = i18n("The program on your computer which provides access " "to the %1 protocol could not obtain the memory " "required to continue.", protocol); causes << cBuglikely; solutions << sUpdate << sBugreport; break; case KIO::ERR_UNKNOWN_PROXY_HOST: errorName = i18n("Unknown Proxy Host"); description = i18n("While retrieving information about the specified " "proxy host, %1, an Unknown Host error was encountered. " "An unknown host error indicates that the requested name could not be " "located on the Internet.", errorText); causes << i18n("There may have been a problem with your network " "configuration, specifically your proxy's hostname. If you have been " "accessing the Internet with no problems recently, this is unlikely.") << cNetwork; solutions << i18n("Double-check your proxy settings and try again.") << sSysadmin; break; case KIO::ERR_CANNOT_AUTHENTICATE: errorName = i18n("Authentication Failed: Method %1 Not Supported", errorText); description = i18n("Although you may have supplied the correct " "authentication details, the authentication failed because the " "method that the server is using is not supported by the KDE " "program implementing the protocol %1.", protocol); solutions << i18n("Please file a bug at " "https://bugs.kde.org/ to inform the KDE team of the unsupported " "authentication method.") << sSysadmin; break; case KIO::ERR_ABORTED: errorName = i18n("Request Aborted"); description = i18n("The request was not completed because it was " "aborted."); solutions << i18n("Retry the request."); break; case KIO::ERR_INTERNAL_SERVER: errorName = i18n("Internal Error in Server"); description = i18n("The program on the server which provides access " "to the %1 protocol has reported an internal error: " "%2.", protocol, errorText); causes << i18n("This is most likely to be caused by a bug in the " "server program. Please consider submitting a full bug report as " "detailed below."); solutions << i18n("Contact the administrator of the server " "to advise them of the problem.") << i18n("If you know who the authors of the server software are, " "submit the bug report directly to them."); break; case KIO::ERR_SERVER_TIMEOUT: errorName = i18n("Timeout Error"); description = i18n("Although contact was made with the server, a " "response was not received within the amount of time allocated for " "the request as follows:
    " "
  • Timeout for establishing a connection: %1 seconds
  • " "
  • Timeout for receiving a response: %2 seconds
  • " "
  • Timeout for accessing proxy servers: %3 seconds
" "Please note that you can alter these timeout settings in the KDE " "System Settings, by selecting Network Settings -> Connection Preferences.", KProtocolManager::connectTimeout(), KProtocolManager::responseTimeout(), KProtocolManager::proxyConnectTimeout()); causes << cNetpath << i18n("The server was too busy responding to other " "requests to respond."); solutions << sTryagain << sServeradmin; break; case KIO::ERR_UNKNOWN: errorName = i18n("Unknown Error"); description = i18n("The program on your computer which provides access " "to the %1 protocol has reported an unknown error: " "%2.", protocol, errorText); causes << cBug; solutions << sUpdate << sBugreport; break; case KIO::ERR_UNKNOWN_INTERRUPT: errorName = i18n("Unknown Interruption"); description = i18n("The program on your computer which provides access " "to the %1 protocol has reported an interruption of " "an unknown type: %2.", protocol, errorText); causes << cBug; solutions << sUpdate << sBugreport; break; case KIO::ERR_CANNOT_DELETE_ORIGINAL: errorName = i18n("Could Not Delete Original File"); description = i18n("The requested operation required the deleting of " "the original file, most likely at the end of a file move operation. " "The original file %1 could not be deleted.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_CANNOT_DELETE_PARTIAL: errorName = i18n("Could Not Delete Temporary File"); description = i18n("The requested operation required the creation of " "a temporary file in which to save the new file while being " "downloaded. This temporary file %1 could not be " "deleted.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_CANNOT_RENAME_ORIGINAL: errorName = i18n("Could Not Rename Original File"); description = i18n("The requested operation required the renaming of " "the original file %1, however it could not be " "renamed.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_CANNOT_RENAME_PARTIAL: errorName = i18n("Could Not Rename Temporary File"); description = i18n("The requested operation required the creation of " "a temporary file %1, however it could not be " "created.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_CANNOT_SYMLINK: errorName = i18n("Could Not Create Link"); techName = i18n("Could Not Create Symbolic Link"); description = i18n("The requested symbolic link %1 could not be created.", errorText); causes << cAccess; solutions << sAccess; break; case KIO::ERR_NO_CONTENT: errorName = i18n("No Content"); description = errorText; break; case KIO::ERR_DISK_FULL: errorName = i18n("Disk Full"); description = i18n("The requested file %1 could not be " "written to as there is inadequate disk space.", errorText); solutions << i18n("Free up enough disk space by 1) deleting unwanted and " "temporary files; 2) archiving files to removable media storage such as " "CD-Recordable discs; or 3) obtain more storage capacity.") << sSysadmin; break; case KIO::ERR_IDENTICAL_FILES: errorName = i18n("Source and Destination Files Identical"); description = i18n("The operation could not be completed because the " "source and destination files are the same file."); solutions << i18n("Choose a different filename for the destination file."); break; case KIO::ERR_DROP_ON_ITSELF: errorName = i18n("File or Folder dropped onto itself"); description = i18n("The operation could not be completed because the " "source and destination file or folder are the same."); solutions << i18n("Drop the item into a different file or folder."); break; // We assume that the slave has all the details case KIO::ERR_SLAVE_DEFINED: errorName.clear(); description = errorText; break; case KIO::ERR_CANNOT_MOVE_INTO_ITSELF: errorName = i18n("Folder moved into itself"); description = i18n("The operation could not be completed because the " "source can not be moved into itself."); solutions << i18n("Move the item into a different folder."); break; case KIO::ERR_PASSWD_SERVER: errorName = i18n("Could not communicate with password server"); description = i18n("The operation could not be completed because the " "service for requesting passwords (kpasswdserver) couldn't be contacted"); solutions << i18n("Try restarting your session, or look in the logs for errors from kiod."); break; case KIO::ERR_CANNOT_CREATE_SLAVE: errorName = i18n("Cannot Initiate the %1 Protocol", protocol); techName = i18n("Unable to Create io-slave"); description = i18n("The io-slave which provides access " "to the %1 protocol could not be started. This is " "usually due to technical reasons.", protocol); causes << i18n("klauncher could not find or start the plugin which provides the protocol." "This means you may have an outdated version of the plugin."); solutions << sUpdate << sSysadmin; break; case KIO::ERR_FILE_TOO_LARGE_FOR_FAT32: errorName = xi18nc("@info", "Cannot transfer %1", errorText); description = xi18nc("@info", "The file %1 cannot be transferred," " because the destination filesystem does not support files that large", errorText); solutions << i18n("Reformat the destination drive to use a filesystem that supports files that large."); break; default: // fall back to the plain error... errorName = i18n("Undocumented Error"); description = buildErrorString(errorCode, errorText); } QByteArray ret; QDataStream stream(&ret, QIODevice::WriteOnly); stream << errorName << techName << description << causes << solutions; return ret; } QFile::Permissions KIO::convertPermissions(int permissions) { QFile::Permissions qPermissions; if (permissions > 0) { if (permissions & S_IRUSR) { qPermissions |= QFile::ReadOwner; } if (permissions & S_IWUSR) { qPermissions |= QFile::WriteOwner; } if (permissions & S_IXUSR) { qPermissions |= QFile::ExeOwner; } if (permissions & S_IRGRP) { qPermissions |= QFile::ReadGroup; } if (permissions & S_IWGRP) { qPermissions |= QFile::WriteGroup; } if (permissions & S_IXGRP) { qPermissions |= QFile::ExeGroup; } if (permissions & S_IROTH) { qPermissions |= QFile::ReadOther; } if (permissions & S_IWOTH) { qPermissions |= QFile::WriteOther; } if (permissions & S_IXOTH) { qPermissions |= QFile::ExeOther; } } return qPermissions; } diff --git a/src/ioslaves/file/file_unix.cpp b/src/ioslaves/file/file_unix.cpp index c35aa64b..b0c327e1 100644 --- a/src/ioslaves/file/file_unix.cpp +++ b/src/ioslaves/file/file_unix.cpp @@ -1,911 +1,924 @@ /* 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 #if HAVE_SYS_XATTR_H #include #endif #include #include #include #include "fdreceiver.h" //sendfile has different semantics in different platforms #if 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() { const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); return QStringLiteral("%1/filehelper%2%3").arg(runtimeDir, KRandom::randomString(6)).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, int errcode) { const QString sockPath = socketPath(); FdReceiver fdRecv(QFile::encodeName(sockPath).toStdString()); if (!fdRecv.isListening()) { return PrivilegeOperationReturnValue::failure(errcode); } 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, sockPath}, errcode)) { return err; } else { int fd = fdRecv.fileDescriptor(); if (fd < 3 || !f.open(fd, openMode, QFileDevice::AutoCloseHandle)) { return PrivilegeOperationReturnValue::failure(errcode); } } return PrivilegeOperationReturnValue::success(); } PrivilegeOperationReturnValue FileProtocol::tryChangeFileAttr(ActionType action, const QVariantList &args, int errcode) { 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, args, errcode); } return PrivilegeOperationReturnValue::failure(errcode); } 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(); QString dest = destUrl.toLocalFile(); QByteArray _src(QFile::encodeName(src)); QByteArray _dest(QFile::encodeName(dest)); QByteArray _dest_backup; 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) { // 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 ((buff_dest.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { //qDebug() << "copy(): LINK DESTINATION"; if (!QFile::remove(dest)) { if (auto err = execWithElevatedPrivilege(DEL, {_dest}, errno)) { if (!err.wasCanceled()) { error(KIO::ERR_CANNOT_DELETE_ORIGINAL, dest); } return; } } } else if ((buff_dest.st_mode & QT_STAT_MASK) == QT_STAT_REG) { _dest_backup = _dest; dest.append(QStringLiteral(".part")); _dest = QFile::encodeName(dest); } } else { error(KIO::ERR_FILE_ALREADY_EXIST, dest); return; } } QFile src_file(src); if (!src_file.open(QIODevice::ReadOnly)) { if (auto err = tryOpen(src_file, _src, O_RDONLY, S_IRUSR, errno)) { 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)) { if (auto err = tryOpen(dest_file, _dest, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR, errno)) { if (!err.wasCanceled()) { // qDebug() << "###### COULD NOT WRITE " << dest; if (err == EACCES) { error(KIO::ERR_WRITE_ACCESS_DENIED, dest); } else { error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest); } } src_file.close(); return; } } // nobody shall be allowed to peek into the file during creation // Note that error handling is omitted for this call, we don't want to error on e.g. VFAT dest_file.setPermissions(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 ]; ssize_t n = 0; #ifdef USE_SENDFILE bool use_sendfile = buff_src.st_size < 0x7FFFFFFF; #endif bool existing_dest_delete_attempted = false; 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 if (!_dest_backup.isEmpty() && !existing_dest_delete_attempted) { ::unlink(_dest_backup.constData()); existing_dest_delete_attempted = true; continue; } 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 if (!QFile::remove(dest)) { // don't keep partly copied file execWithElevatedPrivilege(DEL, {_dest}, errno); } 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 if (!_dest_backup.isEmpty() && !existing_dest_delete_attempted) { ::unlink(_dest_backup.constData()); existing_dest_delete_attempted = true; continue; } 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 if (!QFile::remove(dest)) { // don't keep partly copied file execWithElevatedPrivilege(DEL, {_dest}, errno); } 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 if (!QFile::remove(dest)) { // don't keep partly copied file execWithElevatedPrivilege(DEL, {_dest}, errno); } 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 ) { const int errCode = errno; 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}, errCode)) { 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 { if (tryChangeFileAttr(CHOWN, {_dest, buff_src.st_uid, buff_src.st_gid}, errno)) { qCWarning(KIO_FILE) << "Couldn't preserve group for" << 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) { if (tryChangeFileAttr(UTIME, {_dest, qint64(ut.actime), qint64(ut.modtime)}, errno)) { qCWarning(KIO_FILE) << "Couldn't preserve access and modification time for" << dest; } } if (!_dest_backup.isEmpty()) { if (::unlink(_dest_backup.constData()) == -1) { qCWarning(KIO_FILE) << "Couldn't remove original dest" << _dest_backup << "(" << strerror(errno) << ")"; } if (::rename(_dest.constData(), _dest_backup.constData()) == -1) { qCWarning(KIO_FILE) << "Couldn't rename" << _dest << "to" << _dest_backup << "(" << strerror(errno) << ")"; } } 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); } #if HAVE_SYS_XATTR_H static bool isNtfsHidden(const QString &filename) { constexpr auto attrName = "system.ntfs_attrib_be"; const auto filenameEncoded = QFile::encodeName(filename); #ifdef Q_OS_MACOS auto length = getxattr(filenameEncoded.data(), attrName, nullptr, 0, 0, XATTR_NOFOLLOW); #else auto length = getxattr(filenameEncoded.data(), attrName, nullptr, 0); #endif if (length <= 0) { return false; } constexpr size_t xattr_size = 1024; char strAttr[xattr_size]; #ifdef Q_OS_MACOS length = getxattr(filenameEncoded.data(), attrName, strAttr, xattr_size, 0, XATTR_NOFOLLOW); #else length = getxattr(filenameEncoded.data(), attrName, strAttr, xattr_size); #endif if (length <= 0) { return false; } // Decode result to hex string static constexpr auto digits = "0123456789abcdef"; QVarLengthArray hexAttr(static_cast(length) * 2 + 4); char *c = strAttr; char *e = hexAttr.data(); *e++ ='0'; *e++ = 'x'; for (auto n = 0; n < length; n++, c++) { *e++ = digits[(static_cast(*c) >> 4)]; *e++ = digits[(static_cast(*c) & 0x0F)]; } *e = '\0'; // Decode hex string to int auto intAttr = static_cast(strtol(hexAttr.data(), nullptr, 16)); constexpr auto FILE_ATTRIBUTE_HIDDEN = 0x2u; return static_cast(intAttr & FILE_ATTRIBUTE_HIDDEN); } #endif 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.fastInsert(KIO::UDSEntry::UDS_NAME, filename); #ifdef HAVE_DIRENT_D_TYPE entry.fastInsert(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.fastInsert(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.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, QStringLiteral("Dummy Link Target")); } listEntry(entry); } else { if (createUDSEntry(filename, QByteArray(ep->d_name), entry, details)) { #if HAVE_SYS_XATTR_H if (isNtfsHidden(filename)) { bool ntfsHidden = true; // Bug 392913: NTFS root volume is always "hidden", ignore this if (ep->d_type == DT_DIR || ep->d_type == DT_UNKNOWN || ep->d_type == DT_LNK) { const QString fullFilePath = QDir(filename).canonicalPath(); auto mountPoint = KMountPoint::currentMountPoints().findByPath(fullFilePath); if (mountPoint && mountPoint->mountPoint() == fullFilePath) { ntfsHidden = false; } } if (ntfsHidden) { entry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1); } } #endif 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 (auto err = execWithElevatedPrivilege(RENAME, {_src, _dest}, errno)) { if (!err.wasCanceled()) { if ((err == EACCES) || (err == EPERM)) { error(KIO::ERR_ACCESS_DENIED, dest); } else if (err == EXDEV) { error(KIO::ERR_UNSUPPORTED_ACTION, QStringLiteral("rename")); } else if (err == EROFS) { // The file is on a read-only filesystem error(KIO::ERR_CANNOT_DELETE, src); } else { error(KIO::ERR_CANNOT_RENAME, src); } } 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) { if (auto err = execWithElevatedPrivilege(DEL, {dest}, errno)) { 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 { if (auto err = execWithElevatedPrivilege(SYMLINK, {dest, target}, errno)) { 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 (auto err = execWithElevatedPrivilege(DEL, {_path}, errno)) { if (!err.wasCanceled()) { if ((err == EACCES) || (err == EPERM)) { error(KIO::ERR_ACCESS_DENIED, path); } else if (err == EISDIR) { error(KIO::ERR_IS_DIRECTORY, path); } else { error(KIO::ERR_CANNOT_DELETE, path); } } 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 (auto err = execWithElevatedPrivilege(RMDIR, {_path}, errno)) { if (!err.wasCanceled()) { if ((err == EACCES) || (err == 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) { if (auto err = execWithElevatedPrivilege(CHOWN, {_path, uid, gid}, errno)) { if (!err.wasCanceled()) { switch (err) { 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)) { redirect(url); 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 QVariantList &args, int errcode) { if (privilegeOperationUnitTestMode()) { return PrivilegeOperationReturnValue::success(); } // temporarily disable privilege execution if (true) { return PrivilegeOperationReturnValue::failure(errcode); } if (!(errcode == EACCES || errcode == EPERM)) { return PrivilegeOperationReturnValue::failure(errcode); } KIO::PrivilegeOperationStatus opStatus = requestPrivilegeOperation(); if (opStatus != KIO::OperationAllowed) { if (opStatus == KIO::OperationCanceled) { error(KIO::ERR_USER_CANCELED, QString()); return PrivilegeOperationReturnValue::canceled(); } return PrivilegeOperationReturnValue::failure(errcode); } + const QUrl targetUrl = QUrl::fromLocalFile(args.first().toString()); // target is always the first item. + const bool useParent = action != CHOWN && action != CHMOD && action != UTIME; + const QString targetPath = useParent ? targetUrl.adjusted(QUrl::RemoveFilename).toLocalFile() : targetUrl.toLocalFile(); + bool userIsOwner = QFileInfo(targetPath).ownerId() == getuid(); + if (action == RENAME) { // for rename check src and dest owner + QString dest = QUrl(args[1].toString()).toLocalFile(); + userIsOwner = userIsOwner && QFileInfo(dest).ownerId() == getuid(); + } + if (userIsOwner) { + error(KIO::ERR_PRIVILEGE_NOT_REQUIRED, targetPath); + return PrivilegeOperationReturnValue::canceled(); + } + QByteArray helperArgs; QDataStream out(&helperArgs, QIODevice::WriteOnly); out << action; for (const QVariant &arg : args) { out << arg; } const QString actionId = QStringLiteral("org.kde.kio.file.exec"); KAuth::Action execAction(actionId); execAction.setHelperId(QStringLiteral("org.kde.kio.file")); QVariantMap argv; argv.insert(QStringLiteral("arguments"), helperArgs); execAction.setArguments(argv); auto reply = execAction.execute(); if (reply->exec()) { addTemporaryAuthorization(actionId); return PrivilegeOperationReturnValue::success(); } return PrivilegeOperationReturnValue::failure(KIO::ERR_ACCESS_DENIED); }