diff --git a/src/core/job.cpp b/src/core/job.cpp index 85fee01c..a4e79cfd 100644 --- a/src/core/job.cpp +++ b/src/core/job.cpp @@ -1,324 +1,418 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 David Faure 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 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 "job.h" #include "job_p.h" #include #include #include #include #include #include #include "slave.h" #include "scheduler.h" +#include "slavebase.h" using namespace KIO; Job::Job() : KCompositeJob(nullptr) , d_ptr(new JobPrivate) { d_ptr->q_ptr = this; setCapabilities(KJob::Killable | KJob::Suspendable); } Job::Job(JobPrivate &dd) : KCompositeJob(nullptr) , d_ptr(&dd) { d_ptr->q_ptr = this; setCapabilities(KJob::Killable | KJob::Suspendable); } Job::~Job() { delete d_ptr; } // Exists for historical reasons only KJobUiDelegate *Job::ui() const { return uiDelegate(); } JobUiDelegateExtension *Job::uiDelegateExtension() const { Q_D(const Job); return d->m_uiDelegateExtension; } void Job::setUiDelegateExtension(JobUiDelegateExtension *extension) { Q_D(Job); d->m_uiDelegateExtension = extension; } bool Job::addSubjob(KJob *jobBase) { //qDebug() << "addSubjob(" << jobBase << ") this=" << this; bool ok = KCompositeJob::addSubjob(jobBase); KIO::Job *job = dynamic_cast(jobBase); if (ok && job) { // Copy metadata into subjob (e.g. window-id, user-timestamp etc.) Q_D(Job); job->mergeMetaData(d->m_outgoingMetaData); // Forward information from that subjob. connect(job, SIGNAL(speed(KJob*,ulong)), SLOT(slotSpeed(KJob*,ulong))); job->setProperty("window", property("window")); // see KJobWidgets job->setProperty("userTimestamp", property("userTimestamp")); // see KJobWidgets job->setUiDelegateExtension(d->m_uiDelegateExtension); } return ok; } bool Job::removeSubjob(KJob *jobBase) { //qDebug() << "removeSubjob(" << jobBase << ") this=" << this << "subjobs=" << subjobs().count(); return KCompositeJob::removeSubjob(jobBase); } static QString url_description_string(const QUrl& url) { return url.scheme() == "data" ? QStringLiteral("data:[...]") : KStringHandler::csqueeze(url.toDisplayString(QUrl::PreferLocalFile), 100); } KIO::JobPrivate::~JobPrivate() { } void JobPrivate::emitMoving(KIO::Job *job, const QUrl &src, const QUrl &dest) { emit job->description(job, i18nc("@title job", "Moving"), qMakePair(i18nc("The source of a file operation", "Source"), url_description_string(src)), qMakePair(i18nc("The destination of a file operation", "Destination"), url_description_string(dest))); } void JobPrivate::emitCopying(KIO::Job *job, const QUrl &src, const QUrl &dest) { emit job->description(job, i18nc("@title job", "Copying"), qMakePair(i18nc("The source of a file operation", "Source"), url_description_string(src)), qMakePair(i18nc("The destination of a file operation", "Destination"), url_description_string(dest))); } void JobPrivate::emitCreatingDir(KIO::Job *job, const QUrl &dir) { emit job->description(job, i18nc("@title job", "Creating directory"), qMakePair(i18n("Directory"), url_description_string(dir))); } void JobPrivate::emitDeleting(KIO::Job *job, const QUrl &url) { emit job->description(job, i18nc("@title job", "Deleting"), qMakePair(i18n("File"), url_description_string(url))); } void JobPrivate::emitStating(KIO::Job *job, const QUrl &url) { emit job->description(job, i18nc("@title job", "Examining"), qMakePair(i18n("File"), url_description_string(url))); } void JobPrivate::emitTransferring(KIO::Job *job, const QUrl &url) { emit job->description(job, i18nc("@title job", "Transferring"), qMakePair(i18nc("The source of a file operation", "Source"), url_description_string(url))); } void JobPrivate::emitMounting(KIO::Job *job, const QString &dev, const QString &point) { emit job->description(job, i18nc("@title job", "Mounting"), qMakePair(i18n("Device"), dev), qMakePair(i18n("Mountpoint"), point)); } void JobPrivate::emitUnmounting(KIO::Job *job, const QString &point) { emit job->description(job, i18nc("@title job", "Unmounting"), qMakePair(i18n("Mountpoint"), point)); } bool Job::doKill() { // kill all subjobs, without triggering their result slot Q_FOREACH (KJob *it, subjobs()) { it->kill(KJob::Quietly); } clearSubjobs(); return true; } bool Job::doSuspend() { Q_FOREACH (KJob *it, subjobs()) { if (!it->suspend()) { return false; } } return true; } bool Job::doResume() { Q_FOREACH (KJob *it, subjobs()) { if (!it->resume()) { return false; } } return true; } void JobPrivate::slotSpeed(KJob *, unsigned long speed) { //qDebug() << speed; q_func()->emitSpeed(speed); } //Job::errorString is implemented in job_error.cpp void Job::setParentJob(Job *job) { Q_D(Job); Q_ASSERT(d->m_parentJob == nullptr); Q_ASSERT(job); d->m_parentJob = job; } Job *Job::parentJob() const { return d_func()->m_parentJob; } MetaData Job::metaData() const { return d_func()->m_incomingMetaData; } QString Job::queryMetaData(const QString &key) { return d_func()->m_incomingMetaData.value(key, QString()); } void Job::setMetaData(const KIO::MetaData &_metaData) { Q_D(Job); d->m_outgoingMetaData = _metaData; } void Job::addMetaData(const QString &key, const QString &value) { d_func()->m_outgoingMetaData.insert(key, value); } void Job::addMetaData(const QMap &values) { Q_D(Job); QMap::const_iterator it = values.begin(); for (; it != values.end(); ++it) { d->m_outgoingMetaData.insert(it.key(), it.value()); } } void Job::mergeMetaData(const QMap &values) { Q_D(Job); QMap::const_iterator it = values.begin(); for (; it != values.end(); ++it) // there's probably a faster way if (!d->m_outgoingMetaData.contains(it.key())) { d->m_outgoingMetaData.insert(it.key(), it.value()); } } MetaData Job::outgoingMetaData() const { return d_func()->m_outgoingMetaData; } +PrivilegeOperationStatus JobPrivate::tryAskPrivilegeOpConfirmation() +{ + if (m_confirmationAsked) { + return OperationAllowed; + } + + if (m_parentJob) { + if (!m_parentJob->d_func()->m_privilegeExecutionEnabled) { + return OperationNotAllowed; + } + + if (!m_parentJob->d_func()->m_confirmationAsked) { + PrivilegeOperationStatus opStatus = m_parentJob->d_func()->tryAskPrivilegeOpConfirmation(); + if (opStatus == OperationAllowed) { + // Copy meta-data from parent job + m_incomingMetaData.insert(QStringLiteral("TestData"), m_parentJob->queryMetaData(QStringLiteral("TestData"))); + m_confirmationAsked = true; + } + return opStatus; + } else { + return OperationAllowed; + } + } else { + // In case of SimpleJob like chmod, chown, etc. which don't accept JobFlags + if (!m_privilegeExecutionEnabled) { + return OperationNotAllowed; + } + } + + switch (m_operationType) { + case ChangeAttr: + m_caption = i18n("Change Attribute"); + m_message = i18n("Root privileges are required to change file attributes. " + "Do you want to continue?"); + break; + case Copy: + m_caption = i18n("Copy Files"); + m_message = i18n("Root privileges are required to complete the copy operation. " + "Do you want to continue?"); + break; + case Delete: + m_caption = i18n("Delete Files"); + m_message = i18n("Root privileges are required to complete the delete operation." + "However, doing so may damage your system. Do you want to continue?"); + break; + case MkDir: + m_caption = i18n("Create Folder"); + m_message = i18n("Root privileges are required to create this folder. " + "Do you want to continue?"); + break; + case Move: + m_caption = i18n("Move Items"); + m_message = i18n("Root privileges are required to complete the move operation. " + "Do you want to continue?"); + break; + case Rename: + m_caption = i18n("Rename"); + m_message = i18n("Root privileges are required to complete renaming. " + "Do you want to continue?"); + break; + case Symlink: + m_caption = i18n("Create Symlink"); + m_message = i18n("Root privileges are required to create a symlink. " + "Do you want to continue?"); + break; + case Transfer: + m_caption = i18n("Transfer data"); + m_message = i18n("Root privileges are required to complete transferring data. " + "Do you want to continue?"); + default: + break; + } + + if (m_outgoingMetaData.value(QStringLiteral("UnitTesting")) == QLatin1String("true")) { + // Set meta-data for the top-level job + m_incomingMetaData.insert(QStringLiteral("TestData"), QStringLiteral("PrivilegeOperationAllowed")); + return OperationAllowed; + } + + if (!m_uiDelegateExtension) { + return OperationNotAllowed; + } + + int status = m_uiDelegateExtension->requestMessageBox(JobUiDelegateExtension::WarningContinueCancel, + m_message, m_caption, i18n("Continue"), i18n("Cancel")); + m_confirmationAsked = true; + + if (status == SlaveBase::Cancel) { + return OperationCanceled; + } + return OperationAllowed; +} + ////////////////////////// class KIO::DirectCopyJobPrivate: public KIO::SimpleJobPrivate { public: DirectCopyJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs) : SimpleJobPrivate(url, command, packedArgs) {} /** * @internal * Called by the scheduler when a @p slave gets to * work on this job. * @param slave the slave that starts working on this job */ void start(Slave *slave) Q_DECL_OVERRIDE; Q_DECLARE_PUBLIC(DirectCopyJob) }; DirectCopyJob::DirectCopyJob(const QUrl &url, const QByteArray &packedArgs) : SimpleJob(*new DirectCopyJobPrivate(url, CMD_COPY, packedArgs)) { setUiDelegate(KIO::createDefaultJobUiDelegate()); } DirectCopyJob::~DirectCopyJob() { } void DirectCopyJobPrivate::start(Slave *slave) { Q_Q(DirectCopyJob); q->connect(slave, SIGNAL(canResume(KIO::filesize_t)), SLOT(slotCanResume(KIO::filesize_t))); SimpleJobPrivate::start(slave); } void DirectCopyJob::slotCanResume(KIO::filesize_t offset) { emit canResume(this, offset); } ////////////////////////// SimpleJob *KIO::file_delete(const QUrl &src, JobFlags flags) { KIO_ARGS << src << qint8(true); // isFile SimpleJob *job = SimpleJobPrivate::newJob(src, CMD_DEL, packedArgs, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent); } return job; } ////////// //// #include "moc_job_base.cpp" #include "moc_job_p.cpp" diff --git a/src/core/job_base.h b/src/core/job_base.h index b641cd52..b835fd14 100644 --- a/src/core/job_base.h +++ b/src/core/job_base.h @@ -1,313 +1,332 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 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 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 KIO_JOB_BASE_H #define KIO_JOB_BASE_H #include #include namespace KIO { class JobUiDelegateExtension; class JobPrivate; /** * @class KIO::Job job_base.h * * The base class for all jobs. * For all jobs created in an application, the code looks like * * \code * KIO::Job * job = KIO::someoperation( some parameters ); * connect( job, SIGNAL( result( KJob * ) ), * this, SLOT( slotResult( KJob * ) ) ); * \endcode * (other connects, specific to the job) * * And slotResult is usually at least: * * \code * if ( job->error() ) * job->uiDelegate()->showErrorMessage(); * \endcode * @see KIO::Scheduler */ class KIOCORE_EXPORT Job : public KCompositeJob { Q_OBJECT protected: Job(); Job(JobPrivate &dd); public: virtual ~Job(); void start() Q_DECL_OVERRIDE {} // Since KIO autostarts its jobs /** * Retrieves the UI delegate of this job. * * @deprecated since 5.0, can now be replaced with uiDelegate() * * @return the delegate used by the job to communicate with the UI */ #ifndef KIOCORE_NO_DEPRECATED KIOCORE_DEPRECATED KJobUiDelegate *ui() const; #endif /** * Retrieves the UI delegate extension used by this job. * @since 5.0 */ JobUiDelegateExtension *uiDelegateExtension() const; /** * Sets the UI delegate extension to be used by this job. * The default UI delegate extension is KIO::defaultJobUiDelegateExtension() */ void setUiDelegateExtension(JobUiDelegateExtension *extension); protected: /** * Abort this job. * This kills all subjobs and deletes the job. * */ bool doKill() Q_DECL_OVERRIDE; /** * Suspend this job * @see resume */ bool doSuspend() Q_DECL_OVERRIDE; /** * Resume this job * @see suspend */ bool doResume() Q_DECL_OVERRIDE; public: /** * Converts an error code and a non-i18n error message into an * error message in the current language. The low level (non-i18n) * error message (usually a url) is put into the translated error * message using %1. * * Example for errid == ERR_CANNOT_OPEN_FOR_READING: * \code * i18n( "Could not read\n%1" ).arg( errortext ); * \endcode * Use this to display the error yourself, but for a dialog box * use uiDelegate()->showErrorMessage(). Do not call it if error() * is not 0. * @return the error message and if there is no error, a message * telling the user that the app is broken, so check with * error() whether there is an error */ QString errorString() const Q_DECL_OVERRIDE; /** * Converts an error code and a non-i18n error message into i18n * strings suitable for presentation in a detailed error message box. * * @param reqUrl the request URL that generated this error message * @param method the method that generated this error message * (unimplemented) * @return the following strings: caption, error + description, * causes+solutions */ QStringList detailedErrorStrings(const QUrl *reqUrl = nullptr, int method = -1) const; /** * Set the parent Job. * One example use of this is when FileCopyJob calls RenameDialog::open, * it must pass the correct progress ID of the parent CopyJob * (to hide the progress dialog). * You can set the parent job only once. By default a job does not * have a parent job. * @param parentJob the new parent job */ void setParentJob(Job *parentJob); /** * Returns the parent job, if there is one. * @return the parent job, or 0 if there is none * @see setParentJob */ Job *parentJob() const; /** * Set meta data to be sent to the slave, replacing existing * meta data. * @param metaData the meta data to set * @see addMetaData() * @see mergeMetaData() */ void setMetaData(const KIO::MetaData &metaData); /** * Add key/value pair to the meta data that is sent to the slave. * @param key the key of the meta data * @param value the value of the meta data * @see setMetaData() * @see mergeMetaData() */ void addMetaData(const QString &key, const QString &value); /** * Add key/value pairs to the meta data that is sent to the slave. * If a certain key already existed, it will be overridden. * @param values the meta data to add * @see setMetaData() * @see mergeMetaData() */ void addMetaData(const QMap &values); /** * Add key/value pairs to the meta data that is sent to the slave. * If a certain key already existed, it will remain unchanged. * @param values the meta data to merge * @see setMetaData() * @see addMetaData() */ void mergeMetaData(const QMap &values); /** * @internal. For the scheduler. Do not use. */ MetaData outgoingMetaData() const; /** * Get meta data received from the slave. * (Valid when first data is received and/or slave is finished) * @return the job's meta data */ MetaData metaData() const; /** * Query meta data received from the slave. * (Valid when first data is received and/or slave is finished) * @param key the key of the meta data to retrieve * @return the value of the meta data, or QString() if the * @p key does not exist */ QString queryMetaData(const QString &key); protected: Q_SIGNALS: /** * @deprecated. Don't use ! * Emitted when the job is canceled. * Signal result() is emitted as well, and error() is, * in this case, ERR_USER_CANCELED. * @param job the job that emitted this signal */ #ifndef KIOCORE_NO_DEPRECATED KIOCORE_DEPRECATED void canceled(KJob *job); #endif /** * Emitted when the slave successfully connected to the host. * There is no guarantee the slave will send this, and this is * currently unused (in the applications). * @param job the job that emitted this signal */ void connected(KIO::Job *job); protected: /** * Add a job that has to be finished before a result * is emitted. This has obviously to be called before * the finish signal is emitted by the slave. * * @param job the subjob to add */ bool addSubjob(KJob *job) Q_DECL_OVERRIDE; /** * Mark a sub job as being done. * * Note that this does not terminate the parent job, even if @p job * is the last subjob. emitResult must be called to indicate that * the job is complete. * * @param job the subjob to remove */ bool removeSubjob(KJob *job) Q_DECL_OVERRIDE; protected: JobPrivate *const d_ptr; private: /** * Forward signal from subjob. * @param job the subjob * @param speed the speed in bytes/s * @see speed() */ Q_PRIVATE_SLOT(d_func(), void slotSpeed(KJob *job, unsigned long speed)) Q_DECLARE_PRIVATE(Job) }; /** * Flags for the job properties. * Not all flags are supported in all cases. Please see documentation of * the calling function! */ enum JobFlag { /** * Show the progress info GUI, no Resume and no Overwrite */ DefaultFlags = 0, /** * Hide progress information dialog, i.e. don't show a GUI. */ HideProgressInfo = 1, /** * When set, automatically append to the destination file if it exists already. * WARNING: this is NOT the builtin support for offering the user to resume a previous * partial download. The Resume option is much less used, it allows to append * to an existing file. * This is used by KIO::put(), KIO::file_copy(), KIO::file_move(). */ Resume = 2, /** * When set, automatically overwrite the destination if it exists already. * This is used by KIO::rename(), KIO::put(), KIO::file_copy(), KIO::file_move(), KIO::symlink(). * Otherwise the operation will fail with ERR_FILE_ALREADY_EXIST or ERR_DIR_ALREADY_EXIST. */ - Overwrite = 4 + Overwrite = 4, + + //TODO: Only one of these flags is required. Remove the one less stable. + /** + * When set, notifies the slave that application/job does not want privilege execution. + * So in case of failure due to insufficient privileges show an error without attempting + * to run the operation as root first. + * + * @since 5.43 + */ + NoPrivilegeExecution = 8, + + /** + * When set, notifies the slave that the application(job) wants the file operation to be + * performed as root user if there occurs a failure due to insufficient privilege. + * + * @since 5.43 + */ + PrivilegeExecution = 16 + }; Q_DECLARE_FLAGS(JobFlags, JobFlag) Q_DECLARE_OPERATORS_FOR_FLAGS(JobFlags) enum LoadType { Reload, NoReload }; } #endif diff --git a/src/core/job_p.h b/src/core/job_p.h index e930c9eb..a9308942 100644 --- a/src/core/job_p.h +++ b/src/core/job_p.h @@ -1,349 +1,374 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 David Faure Waldo Bastian Copyright (C) 2007 Thiago Macieira Copyright (C) 2013 Dawit Alemayehu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 KIO_JOB_P_H #define KIO_JOB_P_H #include "simplejob.h" #include "transferjob.h" #include "commands_p.h" #include "kjobtrackerinterface.h" #include #include #include #include #include #include "kiocoredebug.h" +#include "global.h" #define KIO_ARGS QByteArray packedArgs; QDataStream stream( &packedArgs, QIODevice::WriteOnly ); stream namespace KIO { class Slave; // Exported for KIOWidgets jobs class KIOCORE_EXPORT JobPrivate { public: JobPrivate() : m_parentJob(nullptr), m_extraFlags(0), - m_uiDelegateExtension(KIO::defaultJobUiDelegateExtension()) + m_uiDelegateExtension(KIO::defaultJobUiDelegateExtension()), + m_privilegeExecutionEnabled(false), m_confirmationAsked(false) { } virtual ~JobPrivate(); /** * Some extra storage space for jobs that don't have their own * private d pointer. */ enum { EF_TransferJobAsync = (1 << 0), EF_TransferJobNeedData = (1 << 1), EF_TransferJobDataSent = (1 << 2), EF_ListJobUnrestricted = (1 << 3), EF_KillCalled = (1 << 4) }; + enum FileOperationType { + ChangeAttr, // chmod(), chown(), setModificationTime() + Copy, + Delete, + MkDir, + Move, + Rename, + Symlink, + Transfer, // put() and get() + Other // if other file operation set message, caption inside the job. + }; + // Maybe we could use the QObject parent/child mechanism instead // (requires a new ctor, and moving the ctor code to some init()). Job *m_parentJob; int m_extraFlags; MetaData m_incomingMetaData; MetaData m_internalMetaData; MetaData m_outgoingMetaData; JobUiDelegateExtension *m_uiDelegateExtension; Job *q_ptr; + // For privilege operation + bool m_privilegeExecutionEnabled; + bool m_confirmationAsked; + QString m_caption, m_message; + FileOperationType m_operationType; + PrivilegeOperationStatus tryAskPrivilegeOpConfirmation(); void slotSpeed(KJob *job, unsigned long speed); static void emitMoving(KIO::Job *, const QUrl &src, const QUrl &dest); static void emitCopying(KIO::Job *, const QUrl &src, const QUrl &dest); static void emitCreatingDir(KIO::Job *, const QUrl &dir); static void emitDeleting(KIO::Job *, const QUrl &url); static void emitStating(KIO::Job *, const QUrl &url); static void emitTransferring(KIO::Job *, const QUrl &url); static void emitMounting(KIO::Job *, const QString &dev, const QString &point); static void emitUnmounting(KIO::Job *, const QString &point); Q_DECLARE_PUBLIC(Job) }; class SimpleJobPrivate: public JobPrivate { public: /** * Creates a new simple job. * @param url the url of the job * @param command the command of the job * @param packedArgs the arguments */ SimpleJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs) : m_slave(nullptr), m_packedArgs(packedArgs), m_url(url), m_command(command), m_checkOnHold(false), m_schedSerial(0), m_redirectionHandlingEnabled(true) { #if 0 if (m_url.hasSubUrl()) { QList list = KUrl::split(m_url); list.removeLast(); m_subUrl = KUrl::join(list); //qDebug() << "New URL = " << m_url.url(); //qDebug() << "Sub URL = " << m_subUrl.url(); } #endif } Slave *m_slave; QByteArray m_packedArgs; QUrl m_url; QUrl m_subUrl; int m_command; // for use in KIO::Scheduler // // There are two kinds of protocol: // (1) The protocol of the url // (2) The actual protocol that the io-slave uses. // // These two often match, but not necessarily. Most notably, they don't // match when doing ftp via a proxy. // In that case (1) is ftp, but (2) is http. // // JobData::protocol stores (2) while Job::url().protocol() returns (1). // The ProtocolInfoDict is indexed with (2). // // We schedule slaves based on (2) but tell the slave about (1) via // Slave::setProtocol(). QString m_protocol; QStringList m_proxyList; bool m_checkOnHold; int m_schedSerial; bool m_redirectionHandlingEnabled; void simpleJobInit(); /** * Called on a slave's connected signal. * @see connected() */ void slotConnected(); /** * Forward signal from the slave. * @param data_size the processed size in bytes * @see processedSize() */ void slotProcessedSize(KIO::filesize_t data_size); /** * Forward signal from the slave. * @param speed the speed in bytes/s * @see speed() */ void slotSpeed(unsigned long speed); /** * Forward signal from the slave * Can also be called by the parent job, when it knows the size. * @param data_size the total size */ void slotTotalSize(KIO::filesize_t data_size); /** * Called on a slave's info message. * @param s the info message * @see infoMessage() */ void _k_slotSlaveInfoMessage(const QString &s); + /** + * Called when privilegeOperationRequested() is emitted by slave. + */ + void slotPrivilegeOperationRequested(); + /** * @internal * Called by the scheduler when a slave gets to * work on this job. **/ virtual void start(KIO::Slave *slave); /** * @internal * Called to detach a slave from a job. **/ void slaveDone(); /** * Called by subclasses to restart the job after a redirection was signalled. * The m_redirectionURL data member can appear in several subclasses, so we have it * passed in. The regular URL will be set to the redirection URL which is then cleared. */ void restartAfterRedirection(QUrl *redirectionUrl); /** * Request the ui delegate to show a message box. * @internal */ int requestMessageBox(int type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &iconYes = QString(), const QString &iconNo = QString(), const QString &dontAskAgainName = QString(), const KIO::MetaData &sslMetaData = KIO::MetaData()); Q_DECLARE_PUBLIC(SimpleJob) static inline SimpleJobPrivate *get(KIO::SimpleJob *job) { return job->d_func(); } static inline SimpleJob *newJobNoUi(const QUrl &url, int command, const QByteArray &packedArgs) { SimpleJob *job = new SimpleJob(*new SimpleJobPrivate(url, command, packedArgs)); return job; } static inline SimpleJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, JobFlags flags = HideProgressInfo) { SimpleJob *job = new SimpleJob(*new SimpleJobPrivate(url, command, packedArgs)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } return job; } }; class TransferJobPrivate: public SimpleJobPrivate { public: inline TransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData) : SimpleJobPrivate(url, command, packedArgs), m_internalSuspended(false), m_errorPage(false), staticData(_staticData), m_isMimetypeEmitted(false), m_closedBeforeStart(false), m_subJob(nullptr) { } inline TransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice) : SimpleJobPrivate(url, command, packedArgs), m_internalSuspended(false), m_errorPage(false), m_isMimetypeEmitted(false), m_closedBeforeStart(false), m_subJob(nullptr), m_outgoingDataSource(QPointer(ioDevice)) { } bool m_internalSuspended; bool m_errorPage; QByteArray staticData; QUrl m_redirectionURL; QList m_redirectionList; QString m_mimetype; bool m_isMimetypeEmitted; bool m_closedBeforeStart; TransferJob *m_subJob; QPointer m_outgoingDataSource; /** * Flow control. Suspend data processing from the slave. */ void internalSuspend(); /** * Flow control. Resume data processing from the slave. */ void internalResume(); /** * @internal * Called by the scheduler when a slave gets to * work on this job. * @param slave the slave that works on the job */ void start(KIO::Slave *slave) Q_DECL_OVERRIDE; /** * @internal * Called when the ioslave needs the data to send the server. This slot * is invoked when the data is to be sent is read from a QIODevice rather * instead of a QByteArray buffer. */ virtual void slotDataReqFromDevice(); void slotIODeviceClosed(); void slotIODeviceClosedBeforeStart(); void slotErrorPage(); void slotCanResume(KIO::filesize_t offset); void slotPostRedirection(); void slotNeedSubUrlData(); void slotSubUrlData(KIO::Job *, const QByteArray &); Q_DECLARE_PUBLIC(TransferJob) static inline TransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData, JobFlags flags) { TransferJob *job = new TransferJob(*new TransferJobPrivate(url, command, packedArgs, _staticData)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } return job; } static inline TransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice, JobFlags flags) { TransferJob *job = new TransferJob(*new TransferJobPrivate(url, command, packedArgs, ioDevice)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } return job; } }; class DirectCopyJobPrivate; /** * @internal * Used for direct copy from or to the local filesystem (i.e. SlaveBase::copy()) */ class DirectCopyJob : public SimpleJob { Q_OBJECT public: DirectCopyJob(const QUrl &url, const QByteArray &packedArgs); ~DirectCopyJob(); public Q_SLOTS: void slotCanResume(KIO::filesize_t offset); Q_SIGNALS: /** * @internal * Emitted if the job found an existing partial file * and supports resuming. Used by FileCopyJob. */ void canResume(KIO::Job *job, KIO::filesize_t offset); private: Q_DECLARE_PRIVATE(DirectCopyJob) }; } #endif diff --git a/src/core/simplejob.cpp b/src/core/simplejob.cpp index 9783c907..37ff44ad 100644 --- a/src/core/simplejob.cpp +++ b/src/core/simplejob.cpp @@ -1,421 +1,429 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2013 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 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 "simplejob.h" #include "job_p.h" #include "scheduler.h" #include "slave.h" #include "kprotocolinfo.h" #include #include #include using namespace KIO; SimpleJob::SimpleJob(SimpleJobPrivate &dd) : Job(dd) { d_func()->simpleJobInit(); } void SimpleJobPrivate::simpleJobInit() { Q_Q(SimpleJob); if (!m_url.isValid() || m_url.scheme().isEmpty()) { qCWarning(KIO_CORE) << "Invalid URL:" << m_url; q->setError(ERR_MALFORMED_URL); q->setErrorText(m_url.toString()); QTimer::singleShot(0, q, SLOT(slotFinished())); return; } Scheduler::doJob(q); } bool SimpleJob::doKill() { Q_D(SimpleJob); if ((d->m_extraFlags & JobPrivate::EF_KillCalled) == 0) { d->m_extraFlags |= JobPrivate::EF_KillCalled; Scheduler::cancelJob(this); // deletes the slave if not 0 } else { qCWarning(KIO_CORE) << this << "This is overkill."; } return Job::doKill(); } bool SimpleJob::doSuspend() { Q_D(SimpleJob); if (d->m_slave) { d->m_slave->suspend(); } return Job::doSuspend(); } bool SimpleJob::doResume() { Q_D(SimpleJob); if (d->m_slave) { d->m_slave->resume(); } return Job::doResume(); } const QUrl &SimpleJob::url() const { return d_func()->m_url; } void SimpleJob::putOnHold() { Q_D(SimpleJob); Q_ASSERT(d->m_slave); if (d->m_slave) { Scheduler::putSlaveOnHold(this, d->m_url); } // we should now be disassociated from the slave Q_ASSERT(!d->m_slave); kill(Quietly); } void SimpleJob::removeOnHold() { Scheduler::removeSlaveOnHold(); } bool SimpleJob::isRedirectionHandlingEnabled() const { return d_func()->m_redirectionHandlingEnabled; } void SimpleJob::setRedirectionHandlingEnabled(bool handle) { Q_D(SimpleJob); d->m_redirectionHandlingEnabled = handle; } SimpleJob::~SimpleJob() { Q_D(SimpleJob); // last chance to remove this job from the scheduler! if (d->m_schedSerial) { //qDebug() << "Killing job" << this << "in destructor!"/* << qBacktrace()*/; Scheduler::cancelJob(this); } } void SimpleJobPrivate::start(Slave *slave) { Q_Q(SimpleJob); m_slave = slave; // Slave::setJob can send us SSL metadata if there is a persistent connection q->connect(slave, SIGNAL(metaData(KIO::MetaData)), SLOT(slotMetaData(KIO::MetaData))); slave->setJob(q); q->connect(slave, SIGNAL(error(int,QString)), SLOT(slotError(int,QString))); q->connect(slave, SIGNAL(warning(QString)), SLOT(slotWarning(QString))); q->connect(slave, SIGNAL(infoMessage(QString)), SLOT(_k_slotSlaveInfoMessage(QString))); q->connect(slave, SIGNAL(connected()), SLOT(slotConnected())); q->connect(slave, SIGNAL(finished()), SLOT(slotFinished())); + q->connect(slave, SIGNAL(privilegeOperationRequested()), + SLOT(slotPrivilegeOperationRequested())); + if ((m_extraFlags & EF_TransferJobDataSent) == 0) { // this is a "get" job q->connect(slave, SIGNAL(totalSize(KIO::filesize_t)), SLOT(slotTotalSize(KIO::filesize_t))); q->connect(slave, SIGNAL(processedSize(KIO::filesize_t)), SLOT(slotProcessedSize(KIO::filesize_t))); q->connect(slave, SIGNAL(speed(ulong)), SLOT(slotSpeed(ulong))); } const QVariant windowIdProp = q->property("window-id"); // see KJobWidgets::setWindow if (windowIdProp.isValid()) { m_outgoingMetaData.insert(QStringLiteral("window-id"), QString::number(windowIdProp.toULongLong())); } const QVariant userTimestampProp = q->property("userTimestamp"); // see KJobWidgets::updateUserTimestamp if (userTimestampProp.isValid()) { m_outgoingMetaData.insert(QStringLiteral("user-timestamp"), QString::number(userTimestampProp.toULongLong())); } if (q->uiDelegate() == nullptr) { // not interactive m_outgoingMetaData.insert(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); } if (!m_outgoingMetaData.isEmpty()) { KIO_ARGS << m_outgoingMetaData; slave->send(CMD_META_DATA, packedArgs); } if (!m_subUrl.isEmpty()) { KIO_ARGS << m_subUrl; slave->send(CMD_SUBURL, packedArgs); } slave->send(m_command, m_packedArgs); if (q->isSuspended()) { slave->suspend(); } } void SimpleJobPrivate::slaveDone() { Q_Q(SimpleJob); if (m_slave) { if (m_command == CMD_OPEN) { m_slave->send(CMD_CLOSE); } q->disconnect(m_slave); // Remove all signals between slave and job } // only finish a job once; Scheduler::jobFinished() resets schedSerial to zero. if (m_schedSerial) { Scheduler::jobFinished(q, m_slave); } } void SimpleJob::slotFinished() { Q_D(SimpleJob); // Return slave to the scheduler d->slaveDone(); if (!hasSubjobs()) { if (!error() && (d->m_command == CMD_MKDIR || d->m_command == CMD_RENAME)) { if (d->m_command == CMD_MKDIR) { const QUrl urlDir = url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); org::kde::KDirNotify::emitFilesAdded(urlDir); } else { /*if ( m_command == CMD_RENAME )*/ QUrl src, dst; QDataStream str(d->m_packedArgs); str >> src >> dst; if (src.adjusted(QUrl::RemoveFilename) == dst.adjusted(QUrl::RemoveFilename) // For the user, moving isn't renaming. Only renaming is. && !KProtocolInfo::slaveHandlesNotify(dst.scheme()).contains(QLatin1String("Rename"))) { org::kde::KDirNotify::emitFileRenamed(src, dst); } org::kde::KDirNotify::emitFileMoved(src, dst); if (d->m_uiDelegateExtension) { d->m_uiDelegateExtension->updateUrlInClipboard(src, dst); } } } emitResult(); } } void SimpleJob::slotError(int err, const QString &errorText) { Q_D(SimpleJob); setError(err); setErrorText(errorText); if ((error() == ERR_UNKNOWN_HOST) && d->m_url.host().isEmpty()) { setErrorText(QString()); } // error terminates the job slotFinished(); } void SimpleJob::slotWarning(const QString &errorText) { emit warning(this, errorText); } void SimpleJobPrivate::_k_slotSlaveInfoMessage(const QString &msg) { emit q_func()->infoMessage(q_func(), msg); } void SimpleJobPrivate::slotConnected() { emit q_func()->connected(q_func()); } void SimpleJobPrivate::slotTotalSize(KIO::filesize_t size) { Q_Q(SimpleJob); if (size != q->totalAmount(KJob::Bytes)) { q->setTotalAmount(KJob::Bytes, size); } } void SimpleJobPrivate::slotProcessedSize(KIO::filesize_t size) { Q_Q(SimpleJob); //qDebug() << KIO::number(size); q->setProcessedAmount(KJob::Bytes, size); } void SimpleJobPrivate::slotSpeed(unsigned long speed) { //qDebug() << speed; q_func()->emitSpeed(speed); } void SimpleJobPrivate::restartAfterRedirection(QUrl *redirectionUrl) { Q_Q(SimpleJob); // Return slave to the scheduler while we still have the old URL in place; the scheduler // requires a job URL to stay invariant while the job is running. slaveDone(); m_url = *redirectionUrl; redirectionUrl->clear(); if ((m_extraFlags & EF_KillCalled) == 0) { Scheduler::doJob(q); } } int SimpleJobPrivate::requestMessageBox(int _type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &iconYes, const QString &iconNo, const QString &dontAskAgainName, const KIO::MetaData &sslMetaData) { if (m_uiDelegateExtension) { const JobUiDelegateExtension::MessageBoxType type = static_cast(_type); return m_uiDelegateExtension->requestMessageBox(type, text, caption, buttonYes, buttonNo, iconYes, iconNo, dontAskAgainName, sslMetaData); } qCWarning(KIO_CORE) << "JobUiDelegate not set! Returing -1"; return -1; } void SimpleJob::slotMetaData(const KIO::MetaData &_metaData) { Q_D(SimpleJob); QMapIterator it(_metaData); while (it.hasNext()) { it.next(); if (it.key().startsWith(QLatin1String("{internal~"), Qt::CaseInsensitive)) { d->m_internalMetaData.insert(it.key(), it.value()); } else { d->m_incomingMetaData.insert(it.key(), it.value()); } } // Update the internal meta-data values as soon as possible. Waiting until // the ioslave is finished has unintended consequences if the client starts // a new connection without waiting for the ioslave to finish. if (!d->m_internalMetaData.isEmpty()) { Scheduler::updateInternalMetaData(this); } } void SimpleJob::storeSSLSessionFromJob(const QUrl &redirectionURL) { Q_UNUSED(redirectionURL); } +void SimpleJobPrivate::slotPrivilegeOperationRequested() +{ + m_slave->send(MSG_PRIVILEGE_EXEC, QByteArray::number(tryAskPrivilegeOpConfirmation())); +} + ////////// SimpleJob *KIO::rmdir(const QUrl &url) { //qDebug() << "rmdir " << url; KIO_ARGS << url << qint8(false); // isFile is false return SimpleJobPrivate::newJob(url, CMD_DEL, packedArgs); } SimpleJob *KIO::chmod(const QUrl &url, int permissions) { //qDebug() << "chmod " << url; KIO_ARGS << url << permissions; return SimpleJobPrivate::newJob(url, CMD_CHMOD, packedArgs); } SimpleJob *KIO::chown(const QUrl &url, const QString &owner, const QString &group) { KIO_ARGS << url << owner << group; return SimpleJobPrivate::newJob(url, CMD_CHOWN, packedArgs); } SimpleJob *KIO::setModificationTime(const QUrl &url, const QDateTime &mtime) { //qDebug() << "setModificationTime " << url << " " << mtime; KIO_ARGS << url << mtime; return SimpleJobPrivate::newJobNoUi(url, CMD_SETMODIFICATIONTIME, packedArgs); } SimpleJob *KIO::rename(const QUrl &src, const QUrl &dest, JobFlags flags) { //qDebug() << "rename " << src << " " << dest; KIO_ARGS << src << dest << (qint8)(flags & Overwrite); return SimpleJobPrivate::newJob(src, CMD_RENAME, packedArgs); } SimpleJob *KIO::symlink(const QString &target, const QUrl &dest, JobFlags flags) { //qDebug() << "symlink target=" << target << " " << dest; KIO_ARGS << target << dest << (qint8)(flags & Overwrite); return SimpleJobPrivate::newJob(dest, CMD_SYMLINK, packedArgs, flags); } SimpleJob *KIO::special(const QUrl &url, const QByteArray &data, JobFlags flags) { //qDebug() << "special " << url; return SimpleJobPrivate::newJob(url, CMD_SPECIAL, data, flags); } SimpleJob *KIO::mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags) { KIO_ARGS << int(1) << qint8(ro ? 1 : 0) << QString::fromLatin1(fstype) << dev << point; SimpleJob *job = special(QUrl(QStringLiteral("file:///")), packedArgs, flags); if (!(flags & HideProgressInfo)) { KIO::JobPrivate::emitMounting(job, dev, point); } return job; } SimpleJob *KIO::unmount(const QString &point, JobFlags flags) { KIO_ARGS << int(2) << point; SimpleJob *job = special(QUrl(QStringLiteral("file:///")), packedArgs, flags); if (!(flags & HideProgressInfo)) { KIO::JobPrivate::emitUnmounting(job, point); } return job; } ////////// SimpleJob *KIO::http_update_cache(const QUrl &url, bool no_cache, const QDateTime &expireDate) { Q_ASSERT(url.scheme() == QLatin1String("http") || url.scheme() == QLatin1String("https")); // Send http update_cache command (2) KIO_ARGS << (int)2 << url << no_cache << qlonglong(expireDate.toMSecsSinceEpoch() / 1000); SimpleJob *job = SimpleJobPrivate::newJob(url, CMD_SPECIAL, packedArgs); Scheduler::setJobPriority(job, 1); return job; } #include "moc_simplejob.cpp" diff --git a/src/core/simplejob.h b/src/core/simplejob.h index 09d6b1f3..f4ce323f 100644 --- a/src/core/simplejob.h +++ b/src/core/simplejob.h @@ -1,281 +1,282 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2013 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 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 KIO_SIMPLEJOB_H #define KIO_SIMPLEJOB_H #include "job_base.h" #include // filesize_t namespace KIO { class SimpleJobPrivate; /** * @class KIO::SimpleJob simplejob.h * * A simple job (one url and one command). * This is the base class for all jobs that are scheduled. * Other jobs are high-level jobs (CopyJob, DeleteJob, FileCopyJob...) * that manage subjobs but aren't scheduled directly. */ class KIOCORE_EXPORT SimpleJob : public KIO::Job { Q_OBJECT public: ~SimpleJob(); protected: /** * Suspend this job * @see resume */ bool doSuspend() Q_DECL_OVERRIDE; /** * Resume this job * @see suspend */ bool doResume() Q_DECL_OVERRIDE; /** * Abort job. * This kills all subjobs and deletes the job. */ bool doKill() Q_DECL_OVERRIDE; public: /** * Returns the SimpleJob's URL * @return the url */ const QUrl &url() const; /** * Abort job. * Suspends slave to be reused by another job for the same request. */ virtual void putOnHold(); /** * Discard suspended slave. */ static void removeOnHold(); /** * Returns true when redirections are handled internally, the default. * * @since 4.4 */ bool isRedirectionHandlingEnabled() const; /** * Set @p handle to false to prevent the internal handling of redirections. * * When this flag is set, redirection requests are simply forwarded to the * caller instead of being handled internally. * * @since 4.4 */ void setRedirectionHandlingEnabled(bool handle); public Q_SLOTS: /** * @internal * Called on a slave's error. * Made public for the scheduler. */ void slotError(int, const QString &); protected Q_SLOTS: /** * Called when the slave marks the job * as finished. */ virtual void slotFinished(); /** * @internal * Called on a slave's warning. */ virtual void slotWarning(const QString &); /** * MetaData from the slave is received. * @param _metaData the meta data * @see metaData() */ virtual void slotMetaData(const KIO::MetaData &_metaData); protected: /** * Allow jobs that inherit SimpleJob and are aware * of redirections to store the SSL session used. * Retrieval is handled by SimpleJob::start * @param m_redirectionURL Reference to redirection URL, * used instead of m_url if not empty */ void storeSSLSessionFromJob(const QUrl &m_redirectionURL); /** * Creates a new simple job. You don't need to use this constructor, * unless you create a new job that inherits from SimpleJob. */ SimpleJob(SimpleJobPrivate &dd); private: Q_PRIVATE_SLOT(d_func(), void slotConnected()) Q_PRIVATE_SLOT(d_func(), void slotProcessedSize(KIO::filesize_t data_size)) Q_PRIVATE_SLOT(d_func(), void slotSpeed(unsigned long speed)) Q_PRIVATE_SLOT(d_func(), void slotTotalSize(KIO::filesize_t data_size)) Q_PRIVATE_SLOT(d_func(), void _k_slotSlaveInfoMessage(const QString &)) + Q_PRIVATE_SLOT(d_func(), void slotPrivilegeOperationRequested()) Q_DECLARE_PRIVATE(SimpleJob) }; /** * Removes a single directory. * * The directory is assumed to be empty. * The job will fail if the directory is not empty. * Use KIO::del() (DeleteJob) to delete non-empty directories. * * @param url The URL of the directory to remove. * @return A pointer to the job handling the operation. */ KIOCORE_EXPORT SimpleJob *rmdir(const QUrl &url); /** * Changes permissions on a file or directory. * See the other chmod in chmodjob.h for changing many files * or directories. * * @param url The URL of file or directory. * @param permissions The permissions to set. * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *chmod(const QUrl &url, int permissions); /** * Changes ownership and group of a file or directory. * * @param url The URL of file or directory. * @param owner the new owner * @param group the new group * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *chown(const QUrl &url, const QString &owner, const QString &group); /** * Changes the modification time on a file or directory. * * @param url The URL of file or directory. * @param mtime The modification time to set. * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *setModificationTime(const QUrl &url, const QDateTime &mtime); /** * Rename a file or directory. * Warning: this operation fails if a direct renaming is not * possible (like with files or dirs on separate partitions) * Use move or file_move in this case. * * @param src The original URL * @param dest The final URL * @param flags Can be Overwrite here * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *rename(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Create or move a symlink. * This is the lowlevel operation, similar to file_copy and file_move. * It doesn't do any check (other than those the slave does) * and it doesn't show rename and skip dialogs - use KIO::link for that. * @param target The string that will become the "target" of the link (can be relative) * @param dest The symlink to create. * @param flags Can be Overwrite and HideProgressInfo * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *symlink(const QString &target, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Execute any command that is specific to one slave (protocol). * * Examples are : HTTP POST, mount and unmount (kio_file) * * @param url The URL isn't passed to the slave, but is used to know * which slave to send it to :-) * @param data Packed data. The meaning is completely dependent on the * slave, but usually starts with an int for the command number. * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *special(const QUrl &url, const QByteArray &data, JobFlags flags = DefaultFlags); /** * Mount filesystem. * * Special job for @p kio_file. * * @param ro Mount read-only if @p true. * @param fstype File system type (e.g. "ext2", can be empty). * @param dev Device (e.g. /dev/sda0). * @param point Mount point, can be @p null. * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags = DefaultFlags); /** * Unmount filesystem. * * Special job for @p kio_file. * * @param point Point to unmount. * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *unmount(const QString &point, JobFlags flags = DefaultFlags); /** * HTTP cache update * * @param url Url to update, protocol must be "http". * @param no_cache If true, cache entry for @p url is deleted. * @param expireDate Local machine time indicating when the entry is * supposed to expire. * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *http_update_cache(const QUrl &url, bool no_cache, const QDateTime &expireDate); /** * Delete a single file. * * @param src File to delete. * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT SimpleJob *file_delete(const QUrl &src, JobFlags flags = DefaultFlags); } #endif diff --git a/src/ioslaves/file/file.cpp b/src/ioslaves/file/file.cpp index cffe93e7..d255690b 100644 --- a/src/ioslaves/file/file.cpp +++ b/src/ioslaves/file/file.cpp @@ -1,1450 +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)) { 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; } } } 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) { 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")) { if (!QFile::remove(path)) { execWithElevatedPrivilege(DEL, path); } } QT_STATBUF buff; if (QT_LSTAT(QFile::encodeName(path).constData(), &buff) == -1) { - bool dirCreated; - if (!(dirCreated = QDir().mkdir(path))) { + bool dirCreated = QDir().mkdir(path); + if (!dirCreated) { 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)) { 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()) { int oflags = 0; int filemode = _mode; if ((_flags & KIO::Resume)) { oflags = O_RDWR | O_APPEND; } else { 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 { +#ifndef Q_OS_WIN if ((_flags & KIO::Resume)) { execWithElevatedPrivilege(CHOWN, dest, getuid(), getgid()); QFile::setPermissions(dest, modeToQFilePermissions(filemode)); } +#endif } } } 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) { if (!QFile::remove(dest_orig)) { execWithElevatedPrivilege(DEL, dest_orig); } } if (!QFile::rename(dest, dest_orig)) { 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. 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(); 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)) { 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)) { 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"