diff --git a/autotests/jobremotetest.cpp b/autotests/jobremotetest.cpp index 767096d2..74690277 100644 --- a/autotests/jobremotetest.cpp +++ b/autotests/jobremotetest.cpp @@ -1,387 +1,469 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2008 Norbert Frese 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 "jobremotetest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //#include "kiotesthelper.h" // createTestFile etc. QTEST_MAIN(JobRemoteTest) QDateTime s_referenceTimeStamp; // The code comes partly from jobtest.cpp static QUrl remoteTmpUrl() { QString customDir(qgetenv("KIO_JOBREMOTETEST_REMOTETMP")); if (customDir.isEmpty()) { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/'); } else { // Could be a path or a URL return QUrl::fromUserInput(customDir + '/'); } } static QString localTmpDir() { #ifdef Q_OS_WIN return QDir::tempPath() + "/jobremotetest/"; #else // This one needs to be on another partition return QStringLiteral("/tmp/jobremotetest/"); #endif } static bool myExists(const QUrl &url) { KIO::Job *job = KIO::stat(url, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); job->setUiDelegate(nullptr); return job->exec(); } static bool myMkdir(const QUrl &url) { KIO::Job *job = KIO::mkdir(url, -1); job->setUiDelegate(nullptr); return job->exec(); } void JobRemoteTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago // Start with a clean base dir cleanupTestCase(); QUrl url = remoteTmpUrl(); if (!myExists(url)) { const bool ok = url.isLocalFile() ? QDir().mkpath(url.toLocalFile()) : myMkdir(url); if (!ok) { qFatal("couldn't create %s", qPrintable(url.toString())); } } const bool ok = QDir().mkpath(localTmpDir()); if (!ok) { qFatal("couldn't create %s", qPrintable(localTmpDir())); } } static void delDir(const QUrl &pathOrUrl) { KIO::Job *job = KIO::del(pathOrUrl, KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->exec(); } void JobRemoteTest::cleanupTestCase() { delDir(remoteTmpUrl()); delDir(QUrl::fromLocalFile(localTmpDir())); } void JobRemoteTest::enterLoop() { QEventLoop eventLoop; connect(this, &JobRemoteTest::exitLoop, &eventLoop, &QEventLoop::quit); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); } ///// void JobRemoteTest::putAndGet() { QUrl u(remoteTmpUrl()); u.setPath(u.path() + "putAndGetFile"); KIO::TransferJob *job = KIO::put(u, 0600, KIO::Overwrite | KIO::HideProgressInfo); quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago job->setModificationTime(mtime); job->setUiDelegate(nullptr); connect(job, &KJob::result, this, &JobRemoteTest::slotResult); connect(job, &KIO::TransferJob::dataReq, this, &JobRemoteTest::slotDataReq); m_result = -1; m_dataReqCount = 0; enterLoop(); QVERIFY(m_result == 0); // no error m_result = -1; KIO::StoredTransferJob *getJob = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); getJob->setUiDelegate(nullptr); connect(getJob, &KJob::result, this, &JobRemoteTest::slotGetResult); enterLoop(); QCOMPARE(m_result, 0); // no error QCOMPARE(m_data, QByteArray("This is a test for KIO::put()\n")); //QCOMPARE( m_data.size(), 11 ); } void JobRemoteTest::slotGetResult(KJob *job) { m_result = job->error(); m_data = static_cast(job)->data(); emit exitLoop(); } void JobRemoteTest::slotDataReq(KIO::Job *, QByteArray &data) { // Really not the way you'd write a slotDataReq usually :) switch (m_dataReqCount++) { case 0: data = "This is a test for "; break; case 1: data = "KIO::put()\n"; break; case 2: data = QByteArray(); break; } } void JobRemoteTest::slotResult(KJob *job) { m_result = job->error(); emit exitLoop(); } //// void JobRemoteTest::openFileWriting() { m_rwCount = 0; QUrl u(remoteTmpUrl()); u.setPath(u.path() + "openFileWriting"); fileJob = KIO::open(u, QIODevice::WriteOnly); fileJob->setUiDelegate(nullptr); connect(fileJob, &KJob::result, this, &JobRemoteTest::slotResult); connect(fileJob, &KIO::FileJob::data, this, &JobRemoteTest::slotFileJobData); connect(fileJob, &KIO::FileJob::open, this, &JobRemoteTest::slotFileJobOpen); connect(fileJob, &KIO::FileJob::written, this, &JobRemoteTest::slotFileJobWritten); connect(fileJob, &KIO::FileJob::position, this, &JobRemoteTest::slotFileJobPosition); - connect(fileJob, SIGNAL(close(KIO::Job*)), - this, SLOT(slotFileJobClose(KIO::Job*))); + connect(fileJob, QOverload::of(&KIO::FileJob::close), + this, &JobRemoteTest::slotFileJobClose); m_result = -1; + m_closeSignalCalled = false; enterLoop(); QEXPECT_FAIL("", "Needs fixing in kio_file", Abort); QVERIFY(m_result == 0); // no error KIO::StoredTransferJob *getJob = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); getJob->setUiDelegate(nullptr); connect(getJob, &KJob::result, this, &JobRemoteTest::slotGetResult); enterLoop(); QCOMPARE(m_result, 0); // no error + QVERIFY(m_closeSignalCalled); // close signal called. qDebug() << "m_data: " << m_data; QCOMPARE(m_data, QByteArray("test....test....test....test....test....test....end")); } void JobRemoteTest::slotFileJobData(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job); Q_UNUSED(data); } void JobRemoteTest::slotFileJobRedirection(KIO::Job *job, const QUrl &url) { Q_UNUSED(job); Q_UNUSED(url); } void JobRemoteTest::slotFileJobMimetype(KIO::Job *job, const QString &type) { Q_UNUSED(job); Q_UNUSED(type); } void JobRemoteTest::slotFileJobOpen(KIO::Job *job) { Q_UNUSED(job); fileJob->seek(0); } void JobRemoteTest::slotFileJobWritten(KIO::Job *job, KIO::filesize_t written) { Q_UNUSED(job); Q_UNUSED(written); if (m_rwCount > 5) { fileJob->close(); } else { fileJob->seek(m_rwCount * 8); m_rwCount++; } } void JobRemoteTest::slotFileJobPosition(KIO::Job *job, KIO::filesize_t offset) { Q_UNUSED(job); Q_UNUSED(offset); const QByteArray data("test....end"); fileJob->write(data); } void JobRemoteTest::slotFileJobClose(KIO::Job *job) { Q_UNUSED(job); + m_closeSignalCalled = true; qDebug() << "+++++++++ closed"; } //// void JobRemoteTest::openFileReading() { QUrl u(remoteTmpUrl()); u.setPath(u.path() + "openFileReading"); const QByteArray putData("test1test2test3test4test5"); KIO::StoredTransferJob *putJob = KIO::storedPut(putData, u, 0600, KIO::Overwrite | KIO::HideProgressInfo ); quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago putJob->setModificationTime(mtime); putJob->setUiDelegate(nullptr); connect(putJob, &KJob::result, this, &JobRemoteTest::slotResult); m_result = -1; enterLoop(); QVERIFY(m_result == 0); // no error m_rwCount = 4; m_data = QByteArray(); fileJob = KIO::open(u, QIODevice::ReadOnly); fileJob->setUiDelegate(nullptr); connect(fileJob, &KJob::result, this, &JobRemoteTest::slotResult); connect(fileJob, &KIO::FileJob::data, this, &JobRemoteTest::slotFileJob2Data); connect(fileJob, &KIO::FileJob::open, this, &JobRemoteTest::slotFileJob2Open); connect(fileJob, &KIO::FileJob::written, this, &JobRemoteTest::slotFileJob2Written); connect(fileJob, &KIO::FileJob::position, this, &JobRemoteTest::slotFileJob2Position); - connect(fileJob, SIGNAL(close(KIO::Job*)), - this, SLOT(slotFileJob2Close(KIO::Job*))); + connect(fileJob, QOverload::of(&KIO::FileJob::close), + this, &JobRemoteTest::slotFileJob2Close); m_result = -1; + m_closeSignalCalled = false; enterLoop(); QVERIFY(m_result == 0); // no error + QVERIFY(m_closeSignalCalled); // close signal called. qDebug() << "resulting m_data: " << QString(m_data); QCOMPARE(m_data, QByteArray("test5test4test3test2test1")); } void JobRemoteTest::slotFileJob2Data(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job); qDebug() << "m_rwCount = " << m_rwCount << " data: " << data; m_data.append(data); if (m_rwCount < 0) { fileJob->close(); } else { fileJob->seek(m_rwCount-- * 5); } } void JobRemoteTest::slotFileJob2Redirection(KIO::Job *job, const QUrl &url) { Q_UNUSED(job); Q_UNUSED(url); } void JobRemoteTest::slotFileJob2Mimetype(KIO::Job *job, const QString &type) { Q_UNUSED(job); qDebug() << "mimetype: " << type; } void JobRemoteTest::slotFileJob2Open(KIO::Job *job) { Q_UNUSED(job); fileJob->seek(m_rwCount-- * 5); } void JobRemoteTest::slotFileJob2Written(KIO::Job *job, KIO::filesize_t written) { Q_UNUSED(job); Q_UNUSED(written); } void JobRemoteTest::slotFileJob2Position(KIO::Job *job, KIO::filesize_t offset) { Q_UNUSED(job); qDebug() << "position : " << offset << " -> read (5)"; fileJob->read(5); } void JobRemoteTest::slotFileJob2Close(KIO::Job *job) { Q_UNUSED(job); + m_closeSignalCalled = true; qDebug() << "+++++++++ job2 closed"; } //// void JobRemoteTest::slotMimetype(KIO::Job *job, const QString &type) { QVERIFY(job != nullptr); m_mimetype = type; } +void JobRemoteTest::openFileRead0Bytes() +{ + QUrl u(remoteTmpUrl()); + u.setPath(u.path() + "openFileReading"); + + const QByteArray putData("Doesn't matter"); + + KIO::StoredTransferJob *putJob = KIO::storedPut(putData, + u, + 0600, KIO::Overwrite | KIO::HideProgressInfo + ); + + quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems + QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago + putJob->setModificationTime(mtime); + putJob->setUiDelegate(nullptr); + connect(putJob, &KJob::result, + this, &JobRemoteTest::slotResult); + m_result = -1; + enterLoop(); + QVERIFY(m_result == 0); // no error + + m_data = QByteArray(); + + fileJob = KIO::open(u, QIODevice::ReadOnly); + + fileJob->setUiDelegate(nullptr); + connect(fileJob, &KJob::result, + this, &JobRemoteTest::slotResult); + connect(fileJob, &KIO::FileJob::data, + this, &JobRemoteTest::slotFileJob3Data); + connect(fileJob, &KIO::FileJob::open, + this, &JobRemoteTest::slotFileJob3Open); + // Can reuse this slot (it's a noop). + connect(fileJob, &KIO::FileJob::written, + this, &JobRemoteTest::slotFileJob2Written); + connect(fileJob, &KIO::FileJob::position, + this, &JobRemoteTest::slotFileJob3Position); + // Can reuse this as well. + connect(fileJob, QOverload::of(&KIO::FileJob::close), + this, &JobRemoteTest::slotFileJob3Close); + m_result = -1; + m_closeSignalCalled = false; + + enterLoop(); + // Previously reading 0 bytes would cause both data() and error() being emitted... + QVERIFY(m_result == 0); // no error + QVERIFY(m_closeSignalCalled); // close signal called. +} + +void JobRemoteTest::slotFileJob3Open(KIO::Job *job) +{ + Q_UNUSED(job); + fileJob->seek(0); +} + +void JobRemoteTest::slotFileJob3Position(KIO::Job *job, KIO::filesize_t offset) +{ + Q_UNUSED(job); + qDebug() << "position : " << offset << " -> read (0)"; + fileJob->read(0); +} + +void JobRemoteTest::slotFileJob3Data(KIO::Job *job, const QByteArray& data) +{ + Q_UNUSED(job); + QVERIFY(data.isEmpty()); + fileJob->close(); +} + +void JobRemoteTest::slotFileJob3Close(KIO::Job *job) +{ + Q_UNUSED(job); + m_closeSignalCalled = true; + qDebug() << "+++++++++ job2 closed"; +} diff --git a/autotests/jobremotetest.h b/autotests/jobremotetest.h index c367409c..5d24d346 100644 --- a/autotests/jobremotetest.h +++ b/autotests/jobremotetest.h @@ -1,92 +1,99 @@ /* This file is part of the KDE project Copyright (C) 2004 David Faure Copyright (C) 2008 Norbert Frese 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. */ /* Please set KIO_JOBREMOTETEST_REMOTETMP to test other protocols than kio_file. Don't forget the trailing slash! */ #ifndef JOBREMOTETEST_H #define JOBREMOTETEST_H #include #include #include class JobRemoteTest : public QObject { Q_OBJECT public: JobRemoteTest() {} private Q_SLOTS: void initTestCase(); void cleanupTestCase(); // Remote tests void putAndGet(); void openFileWriting(); void openFileReading(); + void openFileRead0Bytes(); //void calculateRemainingSeconds(); Q_SIGNALS: void exitLoop(); protected Q_SLOTS: //void slotEntries( KIO::Job*, const KIO::UDSEntryList& lst ); void slotGetResult(KJob *); void slotDataReq(KIO::Job *, QByteArray &); void slotResult(KJob *); void slotMimetype(KIO::Job *, const QString &); void slotFileJobData(KIO::Job *job, const QByteArray &data); void slotFileJobRedirection(KIO::Job *job, const QUrl &url); void slotFileJobMimetype(KIO::Job *job, const QString &type); void slotFileJobOpen(KIO::Job *job); void slotFileJobWritten(KIO::Job *job, KIO::filesize_t written); void slotFileJobPosition(KIO::Job *job, KIO::filesize_t offset); void slotFileJobClose(KIO::Job *job); void slotFileJob2Data(KIO::Job *job, const QByteArray &data); void slotFileJob2Redirection(KIO::Job *job, const QUrl &url); void slotFileJob2Mimetype(KIO::Job *job, const QString &type); void slotFileJob2Open(KIO::Job *job); void slotFileJob2Written(KIO::Job *job, KIO::filesize_t written); void slotFileJob2Position(KIO::Job *job, KIO::filesize_t offset); void slotFileJob2Close(KIO::Job *job); + void slotFileJob3Open(KIO::Job *job); + void slotFileJob3Position(KIO::Job *job, KIO::filesize_t offset); + void slotFileJob3Data(KIO::Job *job, const QByteArray &data); + void slotFileJob3Close(KIO::Job *job); + private: void enterLoop(); enum { AlreadyExists = 1 }; int m_result; + bool m_closeSignalCalled; QByteArray m_data; QStringList m_names; int m_dataReqCount; QString m_mimetype; // openReadWrite test KIO::FileJob *fileJob; int m_rwCount; }; #endif diff --git a/src/core/filejob.cpp b/src/core/filejob.cpp index fbd96fab..b02ad33e 100644 --- a/src/core/filejob.cpp +++ b/src/core/filejob.cpp @@ -1,225 +1,229 @@ /* * This file is part of the KDE libraries * Copyright (c) 2006 Allan Sandfeld Jensen * * 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 "filejob.h" #include "slavebase.h" #include "scheduler.h" #include "slave.h" #include "job_p.h" class KIO::FileJobPrivate: public KIO::SimpleJobPrivate { public: FileJobPrivate(const QUrl &url, const QByteArray &packedArgs) : SimpleJobPrivate(url, CMD_OPEN, packedArgs), m_open(false), m_size(0) {} bool m_open; QString m_mimetype; KIO::filesize_t m_size; void slotRedirection(const QUrl &url); void slotData(const QByteArray &data); void slotMimetype(const QString &mimetype); void slotOpen(); void slotWritten(KIO::filesize_t); void slotFinished(); void slotPosition(KIO::filesize_t); void slotTotalSize(KIO::filesize_t); /** * @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) override; Q_DECLARE_PUBLIC(FileJob) static inline FileJob *newJob(const QUrl &url, const QByteArray &packedArgs) { FileJob *job = new FileJob(*new FileJobPrivate(url, packedArgs)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); return job; } }; using namespace KIO; FileJob::FileJob(FileJobPrivate &dd) : SimpleJob(dd) { } FileJob::~FileJob() { } void FileJob::read(KIO::filesize_t size) { Q_D(FileJob); if (!d->m_open) { return; } KIO_ARGS << size; d->m_slave->send(CMD_READ, packedArgs); } void FileJob::write(const QByteArray &_data) { Q_D(FileJob); if (!d->m_open) { return; } d->m_slave->send(CMD_WRITE, _data); } void FileJob::seek(KIO::filesize_t offset) { Q_D(FileJob); if (!d->m_open) { return; } KIO_ARGS << KIO::filesize_t(offset); d->m_slave->send(CMD_SEEK, packedArgs); } void FileJob::close() { Q_D(FileJob); if (!d->m_open) { return; } d->m_slave->send(CMD_CLOSE); // ### close? } KIO::filesize_t FileJob::size() { Q_D(FileJob); if (!d->m_open) { return 0; } return d->m_size; } // Slave sends data void FileJobPrivate::slotData(const QByteArray &_data) { Q_Q(FileJob); emit q_func()->data(q, _data); } void FileJobPrivate::slotRedirection(const QUrl &url) { Q_Q(FileJob); //qDebug() << url; emit q->redirection(q, url); } void FileJobPrivate::slotMimetype(const QString &type) { Q_Q(FileJob); m_mimetype = type; emit q->mimetype(q, m_mimetype); } void FileJobPrivate::slotPosition(KIO::filesize_t pos) { Q_Q(FileJob); emit q->position(q, pos); } void FileJobPrivate::slotTotalSize(KIO::filesize_t t_size) { m_size = t_size; Q_Q(FileJob); q->setTotalAmount(KJob::Bytes, m_size); } void FileJobPrivate::slotOpen() { Q_Q(FileJob); m_open = true; emit q->open(q); } void FileJobPrivate::slotWritten(KIO::filesize_t t_written) { Q_Q(FileJob); emit q->written(q, t_written); } void FileJobPrivate::slotFinished() { Q_Q(FileJob); //qDebug() << this << m_url; + m_open = false; emit q->close(q); // Return slave to the scheduler slaveDone(); -// Scheduler::doJob(this); + // Scheduler::doJob(this); q->emitResult(); } void FileJobPrivate::start(Slave *slave) { Q_Q(FileJob); q->connect(slave, SIGNAL(data(QByteArray)), SLOT(slotData(QByteArray))); q->connect(slave, SIGNAL(redirection(QUrl)), SLOT(slotRedirection(QUrl))); q->connect(slave, SIGNAL(mimeType(QString)), SLOT(slotMimetype(QString))); q->connect(slave, SIGNAL(open()), SLOT(slotOpen())); + q->connect(slave, SIGNAL(finished()), + SLOT(slotFinished())); + q->connect(slave, SIGNAL(position(KIO::filesize_t)), SLOT(slotPosition(KIO::filesize_t))); q->connect(slave, SIGNAL(written(KIO::filesize_t)), SLOT(slotWritten(KIO::filesize_t))); q->connect(slave, SIGNAL(totalSize(KIO::filesize_t)), SLOT(slotTotalSize(KIO::filesize_t))); SimpleJobPrivate::start(slave); } FileJob *KIO::open(const QUrl &url, QIODevice::OpenMode mode) { // Send decoded path and encoded query KIO_ARGS << url << mode; return FileJobPrivate::newJob(url, packedArgs); } #include "moc_filejob.cpp" diff --git a/src/core/filejob.h b/src/core/filejob.h index 890ebdba..01418199 100644 --- a/src/core/filejob.h +++ b/src/core/filejob.h @@ -1,164 +1,197 @@ /* * This file is part of the KDE libraries * Copyright (c) 2006 Allan Sandfeld Jensen * * 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_FILEJOB_H #define KIO_FILEJOB_H #include "kiocore_export.h" #include "simplejob.h" namespace KIO { class FileJobPrivate; /** * @class KIO::FileJob filejob.h * * The file-job is an asynchronous version of normal file handling. * It allows block-wise reading and writing, and allows seeking. Results are returned through signals. * * Should always be created using KIO::open(QUrl) */ class KIOCORE_EXPORT FileJob : public SimpleJob { Q_OBJECT public: ~FileJob(); /** - * Read block + * This function attempts to read up to \p size bytes from the URL passed to + * KIO::open() and returns the bytes received via the data() signal. + * + * The read operation commences at the current file offset, and the file + * offset is incremented by the number of bytes read, but this change in the + * offset does not result in the position() signal being emitted. + * + * If the current file offset is at or past the end of file (i.e. EOD), no + * bytes are read, and the data() signal returns an empty QByteArray. + * + * On error the data() signal is not emitted. To catch errors please connect + * to the result() signal. + * + * @param size the requested amount of data to read * - * The slave emits the data through data(). - * @param size the requested amount of data */ void read(KIO::filesize_t size); /** - * Write block + * This function attempts to write all the bytes in \p data to the URL + * passed to KIO::open() and returns the bytes written received via the + * written() signal. + * + * The write operation commences at the current file offset, and the file + * offset is incremented by the number of bytes read, but this change in the + * offset does not result in the position() being emitted. + * + * On error the written() signal is not emitted. To catch errors please + * connect to the result() signal. * * @param data the data to write */ void write(const QByteArray &data); /** - * Close - * * Closes the file-slave + * + * The slave emits close() and result(). */ void close(); /** * Seek * - * The slave emits position() + * The slave emits position() on successful seek to the specified \p offset. + * + * On error the position() signal is not emitted. To catch errors please + * connect to the result() signal. + * * @param offset the position from start to go to */ void seek(KIO::filesize_t offset); /** * Size * * @return the file size */ KIO::filesize_t size(); Q_SIGNALS: /** - * Data from the slave has arrived. + * Data from the slave has arrived. Emitted after read(). + * + * Unless a read() request was sent for 0 bytes, End of data (EOD) has been + * reached if data.size() == 0 + * * @param job the job that emitted this signal * @param data data received from the slave. + * */ void data(KIO::Job *job, const QByteArray &data); /** * Signals the file is a redirection. * Follow this url manually to reach data * @param job the job that emitted this signal * @param url the new URL */ void redirection(KIO::Job *job, const QUrl &url); /** * Mimetype determined. * @param job the job that emitted this signal * @param type the mime type */ void mimetype(KIO::Job *job, const QString &type); /** * File is open, metadata has been determined and the * file-slave is ready to receive commands. * @param job the job that emitted this signal */ void open(KIO::Job *job); /** - * Bytes written to the file. + * \p written bytes were written to the file. Emitted after write(). * @param job the job that emitted this signal * @param written bytes written. */ void written(KIO::Job *job, KIO::filesize_t written); /** * File is closed and will accept no more commands * @param job the job that emitted this signal */ void close(KIO::Job *job); /** - * The file has reached this position. Emitted after seek. + * The file has reached this position. Emitted after seek(). * @param job the job that emitted this signal * @param offset the new position */ void position(KIO::Job *job, KIO::filesize_t offset); protected: FileJob(FileJobPrivate &dd); private: Q_PRIVATE_SLOT(d_func(), void slotRedirection(const QUrl &)) Q_PRIVATE_SLOT(d_func(), void slotData(const QByteArray &data)) Q_PRIVATE_SLOT(d_func(), void slotMimetype(const QString &mimetype)) Q_PRIVATE_SLOT(d_func(), void slotOpen()) Q_PRIVATE_SLOT(d_func(), void slotWritten(KIO::filesize_t)) Q_PRIVATE_SLOT(d_func(), void slotFinished()) Q_PRIVATE_SLOT(d_func(), void slotPosition(KIO::filesize_t)) Q_PRIVATE_SLOT(d_func(), void slotTotalSize(KIO::filesize_t)) Q_DECLARE_PRIVATE(FileJob) }; /** * Open ( random access I/O ) * * The file-job emits open() when opened + * + * On error the open() signal is not emitted. To catch errors please + * connect to the result() signal. + * * @param url the URL of the file * @param mode the access privileges: see \ref OpenMode * * @return The file-handling job. It will never return 0. Errors are handled asynchronously * (emitted as signals). */ KIOCORE_EXPORT FileJob *open(const QUrl &url, QIODevice::OpenMode mode); } // namespace #endif diff --git a/src/core/slavebase.h b/src/core/slavebase.h index 87099f0d..4e6e6faf 100644 --- a/src/core/slavebase.h +++ b/src/core/slavebase.h @@ -1,988 +1,1007 @@ /* Copyright (C) 2000 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 SLAVEBASE_H #define SLAVEBASE_H #include #include #include #include "job_base.h" // for KIO::JobFlags #include #include class KConfigGroup; class KRemoteEncoding; class QUrl; namespace KIO { class Connection; class SlaveBasePrivate; /** * @class KIO::SlaveBase slavebase.h * * There are two classes that specifies the protocol between application (job) * and kioslave. SlaveInterface is the class to use on the application end, * SlaveBase is the one to use on the slave end. * * Slave implementations should simply inherit SlaveBase * * A call to foo() results in a call to slotFoo() on the other end. * * Note that a kioslave doesn't have a Qt event loop. When idle, it's waiting for a command * on the socket that connects it to the application. So don't expect a kioslave to react * to D-Bus signals for instance. KIOSlaves are short-lived anyway, so any kind of watching * or listening for notifications should be done elsewhere, for instance in a kded module * (see kio_desktop's desktopnotifier.cpp for an example). * * If a kioslave needs a Qt event loop within the implementation of one method, e.g. to * wait for an asynchronous operation to finish, that is possible, using QEventLoop. */ class KIOCORE_EXPORT SlaveBase { public: SlaveBase(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket); virtual ~SlaveBase(); /** * @internal * Terminate the slave by calling the destructor and then ::exit() */ Q_NORETURN void exit(); /** * @internal */ void dispatchLoop(); /////////// // Message Signals to send to the job /////////// /** * Sends data in the slave to the job (i.e. in get). * * To signal end of data, simply send an empty * QByteArray(). * * @param data the data read by the slave */ void data(const QByteArray &data); /** * Asks for data from the job. * @see readData */ void dataReq(); /** * open succeeds - * @see open + * @see open() */ void opened(); /** * Call to signal an error. * This also finishes the job, so you must not call * finished() after calling this. * * If the error code is KIO::ERR_SLAVE_DEFINED then the * _text should contain the complete translated text of * of the error message. * * For all other error codes, _text should match the corresponding * error code. Usually, _text is a file or host name, or the error which * was passed from the server.
* For example, for KIO::ERR_DOES_NOT_EXIST, _text may only * be the file or folder which does not exist, nothing else. Otherwise, * this would break error strings generated by KIO::buildErrorString().
* If you have to add more details than what the standard error codes * provide, you'll need to use KIO::ERR_SLAVE_DEFINED. * For a complete list of what _text should contain for each error code, * look at the source of KIO::buildErrorString(). * * You can add rich text markup to the message, the places where the * error message will be displayed are rich text aware. * * @see KIO::Error * @see KIO::buildErrorString * @param _errid the error code from KIO::Error * @param _text the rich text error message */ void error(int _errid, const QString &_text); /** * Call in openConnection, if you reimplement it, when you're done. */ void connected(); /** * Call to signal successful completion of any command * besides openConnection and closeConnection. Do not * call this after calling error(). */ void finished(); /** * Call to signal that data from the sub-URL is needed */ void needSubUrlData(); /** * Used to report the status of the slave. * @param host the slave is currently connected to. (Should be * empty if not connected) * @param connected Whether an actual network connection exists. **/ void slaveStatus(const QString &host, bool connected); /** * Call this from stat() to express details about an object, the * UDSEntry customarily contains the atoms describing file name, size, * mimetype, etc. * @param _entry The UDSEntry containing all of the object attributes. */ void statEntry(const UDSEntry &_entry); /** * Call this in listDir, each time you have a bunch of entries * to report. * @param _entry The UDSEntry containing all of the object attributes. */ void listEntries(const UDSEntryList &_entry); /** * Call this at the beginning of put(), to give the size of the existing * partial file, if there is one. The @p offset argument notifies the * other job (the one that gets the data) about the offset to use. * In this case, the boolean returns whether we can indeed resume or not * (we can't if the protocol doing the get() doesn't support setting an offset) */ bool canResume(KIO::filesize_t offset); /** * Call this at the beginning of get(), if the "range-start" metadata was set * and returning byte ranges is implemented by this protocol. */ void canResume(); /////////// // Info Signals to send to the job /////////// /** * Call this in get and copy, to give the total size * of the file. */ void totalSize(KIO::filesize_t _bytes); /** * Call this during get and copy, once in a while, * to give some info about the current state. * Don't emit it in listDir, listEntries speaks for itself. */ void processedSize(KIO::filesize_t _bytes); void position(KIO::filesize_t _pos); void written(KIO::filesize_t _bytes); /** * Only use this if you can't know in advance the size of the * copied data. For example, if you're doing variable bitrate * compression of the source. * * STUB ! Currently unimplemented. Here now for binary compatibility. * * Call this during get and copy, once in a while, * to give some info about the current state. * Don't emit it in listDir, listEntries speaks for itself. */ void processedPercent(float percent); /** * Call this in get and copy, to give the current transfer * speed, but only if it can't be calculated out of the size you * passed to processedSize (in most cases you don't want to call it) */ void speed(unsigned long _bytes_per_second); /** * Call this to signal a redirection * The job will take care of going to that url. */ void redirection(const QUrl &_url); /** * Tell that we will only get an error page here. * This means: the data you'll get isn't the data you requested, * but an error page (usually HTML) that describes an error. */ void errorPage(); /** * Call this in mimetype() and in get(), when you know the mimetype. * See mimetype about other ways to implement it. */ void mimeType(const QString &_type); /** * Call to signal a warning, to be displayed in a dialog box. */ void warning(const QString &msg); /** * Call to signal a message, to be displayed if the application wants to, * for instance in a status bar. Usual examples are "connecting to host xyz", etc. */ void infoMessage(const QString &msg); /** * Type of message box. Should be kept in sync with KMessageBox::ButtonCode. */ enum MessageBoxType { QuestionYesNo = 1, WarningYesNo = 2, WarningContinueCancel = 3, WarningYesNoCancel = 4, Information = 5, SSLMessageBox = 6 }; /** * Button codes. Should be kept in sync with KMessageBox::ButtonCode */ enum ButtonCode { Ok = 1, Cancel = 2, Yes = 3, No = 4, Continue = 5 }; /** * Call this to show a message box from the slave * @param type type of message box: QuestionYesNo, WarningYesNo, WarningContinueCancel... * @param text Message string. May contain newlines. * @param caption Message box title. * @param buttonYes The text for the first button. * The default is i18n("&Yes"). * @param buttonNo The text for the second button. * The default is i18n("&No"). * Note: for ContinueCancel, buttonYes is the continue button and buttonNo is unused. * and for Information, none is used. * @return a button code, as defined in ButtonCode, or 0 on communication error. */ int messageBox(MessageBoxType type, const QString &text, const QString &caption = QString(), const QString &buttonYes = QString(), const QString &buttonNo = QString()); /** * Call this to show a message box from the slave * @param text Message string. May contain newlines. * @param type type of message box: QuestionYesNo, WarningYesNo, WarningContinueCancel... * @param caption Message box title. * @param buttonYes The text for the first button. * The default is i18n("&Yes"). * @param buttonNo The text for the second button. * The default is i18n("&No"). * Note: for ContinueCancel, buttonYes is the continue button and buttonNo is unused. * and for Information, none is used. * @param dontAskAgainName the name used to store result from 'Do not ask again' checkbox. * @return a button code, as defined in ButtonCode, or 0 on communication error. */ int messageBox(const QString &text, MessageBoxType type, const QString &caption = QString(), const QString &buttonYes = QString(), const QString &buttonNo = QString(), const QString &dontAskAgainName = QString()); /** * Sets meta-data to be send to the application before the first * data() or finished() signal. */ void setMetaData(const QString &key, const QString &value); /** * Queries for the existence of a certain config/meta-data entry * send by the application to the slave. */ bool hasMetaData(const QString &key) const; /** * Queries for config/meta-data send by the application to the slave. */ QString metaData(const QString &key) const; /** * @internal for ForwardingSlaveBase * Contains all metadata (but no config) sent by the application to the slave. */ MetaData allMetaData() const; /** * Returns a configuration object to query config/meta-data information * from. * * The application provides the slave with all configuration information * relevant for the current protocol and host. */ KConfigGroup *config(); /** * Returns an object that can translate remote filenames into proper * Unicode forms. This encoding can be set by the user. */ KRemoteEncoding *remoteEncoding(); /////////// // Commands sent by the job, the slave has to // override what it wants to implement /////////// /** * Set the host * * Called directly by createSlave, this is why there is no equivalent in * SlaveInterface, unlike the other methods. * * This method is called whenever a change in host, port or user occurs. */ virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass); /** * Prepare slave for streaming operation */ virtual void setSubUrl(const QUrl &url); /** * Opens the connection (forced) * When this function gets called the slave is operating in * connection-oriented mode. * When a connection gets lost while the slave operates in * connection oriented mode, the slave should report * ERR_CONNECTION_BROKEN instead of reconnecting. The user is * expected to disconnect the slave in the error handler. */ virtual void openConnection(); /** * Closes the connection (forced) * Called when the application disconnects the slave to close * any open network connections. * * When the slave was operating in connection-oriented mode, * it should reset itself to connectionless (default) mode. */ virtual void closeConnection(); /** * get, aka read. * @param url the full url for this request. Host, port and user of the URL * can be assumed to be the same as in the last setHost() call. * * The slave should first "emit" the mimetype by calling mimeType(), * and then "emit" the data using the data() method. * * The reason why we need get() to emit the mimetype is: * when pasting a URL in krunner, or konqueror's location bar, * we have to find out what is the mimetype of that URL. * Rather than doing it with a call to mimetype(), then the app or part * would have to do a second request to the same server, this is done * like this: get() is called, and when it emits the mimetype, the job * is put on hold and the right app or part is launched. When that app * or part calls get(), the slave is magically reused, and the download * can now happen. All with a single call to get() in the slave. * This mechanism is also described in KIO::get(). */ virtual void get(const QUrl &url); /** * open. * @param url the full url for this request. Host, port and user of the URL * can be assumed to be the same as in the last setHost() call. * @param mode see \ref QIODevice::OpenMode */ virtual void open(const QUrl &url, QIODevice::OpenMode mode); + /** + * read. + * @param size the requested amount of data to read + * @see KIO::FileJob::read() + */ virtual void read(KIO::filesize_t size); + /** + * write. + * @param data the data to write + * @see KIO::FileJob::write() + */ virtual void write(const QByteArray &data); + /** + * seek. + * @param offset the requested amount of data to read + * @see KIO::FileJob::read() + */ virtual void seek(KIO::filesize_t offset); + /** + * close. + * @see KIO::FileJob::close() + */ virtual void close(); /** * put, i.e. write data into a file. * * @param url where to write the file * @param permissions may be -1. In this case no special permission mode is set. * @param flags We support Overwrite here. Hopefully, we're going to * support Resume in the future, too. * If the file indeed already exists, the slave should NOT apply the * permissions change to it. * The support for resuming using .part files is done by calling canResume(). * * IMPORTANT: Use the "modified" metadata in order to set the modification time of the file. * * @see canResume() */ virtual void put(const QUrl &url, int permissions, JobFlags flags); /** * Finds all details for one file or directory. * The information returned is the same as what listDir returns, * but only for one file or directory. * Call statEntry() after creating the appropriate UDSEntry for this * url. * * You can use the "details" metadata to optimize this method to only * do as much work as needed by the application. * By default details is 2 (all details wanted, including modification time, size, etc.), * details==1 is used when deleting: we don't need all the information if it takes * too much time, no need to follow symlinks etc. * details==0 is used for very simple probing: we'll only get the answer * "it's a file or a directory (or a symlink), or it doesn't exist". */ virtual void stat(const QUrl &url); /** * Finds mimetype for one file or directory. * * This method should either emit 'mimeType' or it * should send a block of data big enough to be able * to determine the mimetype. * * If the slave doesn't reimplement it, a get will * be issued, i.e. the whole file will be downloaded before * determining the mimetype on it - this is obviously not a * good thing in most cases. */ virtual void mimetype(const QUrl &url); /** * Lists the contents of @p url. * The slave should emit ERR_CANNOT_ENTER_DIRECTORY if it doesn't exist, * if we don't have enough permissions. * It should also emit totalFiles as soon as it knows how many * files it will list. * You should not list files if the path in @p url is empty, but redirect * to a non-empty path instead. */ virtual void listDir(const QUrl &url); /** * Create a directory * @param url path to the directory to create * @param permissions the permissions to set after creating the directory * (-1 if no permissions to be set) * The slave emits ERR_CANNOT_MKDIR if failure. */ virtual void mkdir(const QUrl &url, int permissions); /** * Rename @p oldname into @p newname. * If the slave returns an error ERR_UNSUPPORTED_ACTION, the job will * ask for copy + del instead. * * Important: the slave must implement the logic "if the destination already * exists, error ERR_DIR_ALREADY_EXIST or ERR_FILE_ALREADY_EXIST". * For performance reasons no stat is done in the destination before hand, * the slave must do it. * * By default, rename() is only called when renaming (moving) from * yourproto://host/path to yourproto://host/otherpath. * * If you set renameFromFile=true then rename() will also be called when * moving a file from file:///path to yourproto://host/otherpath. * Otherwise such a move would have to be done the slow way (copy+delete). * See KProtocolManager::canRenameFromFile() for more details. * * If you set renameToFile=true then rename() will also be called when * moving a file from yourproto: to file:. * See KProtocolManager::canRenameToFile() for more details. * * @param src where to move the file from * @param dest where to move the file to * @param flags We support Overwrite here */ virtual void rename(const QUrl &src, const QUrl &dest, JobFlags flags); /** * Creates a symbolic link named @p dest, pointing to @p target, which * may be a relative or an absolute path. * @param target The string that will become the "target" of the link (can be relative) * @param dest The symlink to create. * @param flags We support Overwrite here */ virtual void symlink(const QString &target, const QUrl &dest, JobFlags flags); /** * Change permissions on @p url * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_CHMOD */ virtual void chmod(const QUrl &url, int permissions); /** * Change ownership of @p url * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_CHOWN */ virtual void chown(const QUrl &url, const QString &owner, const QString &group); /** * Sets the modification time for @url * For instance this is what CopyJob uses to set mtime on dirs at the end of a copy. * It could also be used to set the mtime on any file, in theory. * The usual implementation on unix is to call utime(path, &myutimbuf). * The slave emits ERR_DOES_NOT_EXIST or ERR_CANNOT_SETTIME */ virtual void setModificationTime(const QUrl &url, const QDateTime &mtime); /** * Copy @p src into @p dest. * * By default, copy() is only called when copying a file from * yourproto://host/path to yourproto://host/otherpath. * * If you set copyFromFile=true then copy() will also be called when * moving a file from file:///path to yourproto://host/otherpath. * Otherwise such a copy would have to be done the slow way (get+put). * See also KProtocolManager::canCopyFromFile(). * * If you set copyToFile=true then copy() will also be called when * moving a file from yourproto: to file:. * See also KProtocolManager::canCopyToFile(). * * If the slave returns an error ERR_UNSUPPORTED_ACTION, the job will * ask for get + put instead. * @param src where to copy the file from (decoded) * @param dest where to copy the file to (decoded) * @param permissions may be -1. In this case no special permission mode is set. * @param flags We support Overwrite here * * Don't forget to set the modification time of @p dest to be the modification time of @p src. */ virtual void copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags); /** * Delete a file or directory. * @param url file/directory to delete * @param isfile if true, a file should be deleted. * if false, a directory should be deleted. * * By default, del() on a directory should FAIL if the directory is not empty. * However, if metadata("recurse") == "true", then the slave can do a recursive deletion. * This behavior is only invoked if the slave specifies deleteRecursive=true in its protocol file. */ virtual void del(const QUrl &url, bool isfile); /** * Change the destination of a symlink * @param url the url of the symlink to modify * @param target the new destination (target) of the symlink */ virtual void setLinkDest(const QUrl &url, const QString &target); /** * Used for any command that is specific to this slave (protocol) * Examples are : HTTP POST, mount and unmount (kio_file) * * @param data packed data; the meaning is completely dependent on the * slave, but usually starts with an int for the command number. * Document your slave's commands, at least in its header file. */ virtual void special(const QByteArray &data); /** * Used for multiple get. Currently only used foir HTTP pielining * support. * * @param data packed data; Contains number of URLs to fetch, and for * each URL the URL itself and its associated MetaData. */ virtual void multiGet(const QByteArray &data); /** * Called to get the status of the slave. Slave should respond * by calling slaveStatus(...) */ virtual void slave_status(); /** * Called by the scheduler to tell the slave that the configuration * changed (i.e. proxy settings) . */ virtual void reparseConfiguration(); /** * @return timeout value for connecting to remote host. */ int connectTimeout(); /** * @return timeout value for connecting to proxy in secs. */ int proxyConnectTimeout(); /** * @return timeout value for read from first data from * remote host in seconds. */ int responseTimeout(); /** * @return timeout value for read from subsequent data from * remote host in secs. */ int readTimeout(); /** * This function sets a timeout of @p timeout seconds and calls * special(data) when the timeout occurs as if it was called by the * application. * * A timeout can only occur when the slave is waiting for a command * from the application. * * Specifying a negative timeout cancels a pending timeout. * * Only one timeout at a time is supported, setting a timeout * cancels any pending timeout. */ void setTimeoutSpecialCommand(int timeout, const QByteArray &data = QByteArray()); ///////////////// // Dispatching (internal) //////////////// /** * @internal */ virtual void dispatch(int command, const QByteArray &data); /** * @internal */ virtual void dispatchOpenCommand(int command, const QByteArray &data); /** * Read data sent by the job, after a dataReq * * @param buffer buffer where data is stored * @return 0 on end of data, * > 0 bytes read * < 0 error **/ int readData(QByteArray &buffer); /** * It collects entries and emits them via listEntries * when enough of them are there or a certain time * frame exceeded (to make sure the app gets some * items in time but not too many items one by one * as this will cause a drastic performance penalty). * @param _entry The UDSEntry containing all of the object attributes. * @param ready set to true after emitting all items. @p _entry is not * used in this case * @deprecated since 5.0. the listEntry(entry, true) indicated * that the entry listing was completed. However, each slave should * already call finished() to also tell us that we're done listing. * You should make sure that finished() is called when the entry * listing is completed and simply remove the call to listEntry(entry, true). */ #ifndef KIOCORE_NO_DEPRECATED KIOCORE_DEPRECATED void listEntry(const UDSEntry &_entry, bool ready); #endif /** * It collects entries and emits them via listEntries * when enough of them are there or a certain time * frame exceeded (to make sure the app gets some * items in time but not too many items one by one * as this will cause a drastic performance penalty). * @param entry The UDSEntry containing all of the object attributes. * @since 5.0 */ void listEntry(const UDSEntry &entry); /** * internal function to connect a slave to/ disconnect from * either the slave pool or the application */ void connectSlave(const QString &path); void disconnectSlave(); /** * Prompt the user for Authorization info (login & password). * * Use this function to request authorization information from * the end user. You can also pass an error message which explains * why a previous authorization attempt failed. Here is a very * simple example: * * \code * KIO::AuthInfo authInfo; * int errorCode = openPasswordDialogV2(authInfo); * if (!errorCode) { * qDebug() << QLatin1String("User: ") << authInfo.username; * qDebug() << QLatin1String("Password: not displayed here!"); * } else { * error(errorCode, QString()); * } * \endcode * * You can also preset some values like the username, caption or * comment as follows: * * \code * KIO::AuthInfo authInfo; * authInfo.caption = i18n("Acme Password Dialog"); * authInfo.username = "Wile E. Coyote"; * QString errorMsg = i18n("You entered an incorrect password."); * int errorCode = openPasswordDialogV2(authInfo, errorMsg); * [...] * \endcode * * \note You should consider using checkCachedAuthentication() to * see if the password is available in kpasswdserver before calling * this function. * * \note A call to this function can fail and return @p false, * if the password server could not be started for whatever reason. * * \note This function does not store the password information * automatically (and has not since kdelibs 4.7). If you want to * store the password information in a persistent storage like * KWallet, then you MUST call @ref cacheAuthentication. * * @see checkCachedAuthentication * @param info See AuthInfo. * @param errorMsg Error message to show * @return a KIO error code: NoError (0), KIO::USER_CANCELED, or other error codes. */ int openPasswordDialogV2(KIO::AuthInfo &info, const QString &errorMsg = QString()); /** * @deprecated since KF 5.24, use openPasswordDialogV2. * The return value works differently: * instead of * if (!openPasswordDialog()) { error(USER_CANCELED); } * store and pass the return value to error(), when NOT zero, * as shown documentation for openPasswordDialogV2(). */ KIOCORE_DEPRECATED bool openPasswordDialog(KIO::AuthInfo &info, const QString &errorMsg = QString()); /** * Checks for cached authentication based on parameters * given by @p info. * * Use this function to check if any cached password exists * for the URL given by @p info. If @p AuthInfo::realmValue * and/or @p AuthInfo::verifyPath flag is specified, then * they will also be factored in determining the presence * of a cached password. Note that @p Auth::url is a required * parameter when attempting to check for cached authorization * info. Here is a simple example: * * \code * AuthInfo info; * info.url = QUrl("http://www.foobar.org/foo/bar"); * info.username = "somename"; * info.verifyPath = true; * if ( !checkCachedAuthentication( info ) ) * { * int errorCode = openPasswordDialogV2(info); * .... * } * \endcode * * @param info See AuthInfo. * @return @p true if cached Authorization is found, false otherwise. */ bool checkCachedAuthentication(AuthInfo &info); /** * Caches @p info in a persistent storage like KWallet. * * Note that calling openPasswordDialogV2 does not store passwords * automatically for you (and has not since kdelibs 4.7). * * Here is a simple example of how to use cacheAuthentication: * * \code * AuthInfo info; * info.url = QUrl("http://www.foobar.org/foo/bar"); * info.username = "somename"; * info.verifyPath = true; * if ( !checkCachedAuthentication( info ) ) { * int errorCode = openPasswordDialogV2(info); * if (!errorCode) { * if (info.keepPassword) { // user asked password be save/remembered * cacheAuthentication(info); * } * } * } * \endcode * * @param info See AuthInfo. * @return @p true if @p info was successfully cached. */ bool cacheAuthentication(const AuthInfo &info); /** * @deprecated for a very very long time, not implemented anymore * Probably dates back to model dialup times. * * Used by the slave to check if it can connect * to a given host. This should be called where the slave is ready * to do a ::connect() on a socket. For each call to * requestNetwork must exist a matching call to * dropNetwork, or the system will stay online until * KNetMgr gets closed (or the SlaveBase gets destructed)! * * If KNetMgr is not running, then this is a no-op and returns true * * @param host tells the netmgr the host the slave wants to connect * to. As this could also be a proxy, we can't just take * the host currently connected to (but that's the default * value) * * @return true in theory, the host is reachable * false the system is offline and the host is in a remote network. */ #ifndef KIOCORE_NO_DEPRECATED bool requestNetwork(const QString &host = QString()); #endif /** * @deprecated for a very very long time, not implemented anymore * Probably dates back to model dialup times. * * Used by the slave to withdraw a connection requested by * requestNetwork. This function cancels the last call to * requestNetwork. If a client uses more than one internet * connection, it must use dropNetwork(host) to * stop each request. * * If KNetMgr is not running, then this is a no-op. * * @param host the host passed to requestNetwork * * A slave should call this function every time it disconnect from a host. * */ #ifndef KIOCORE_NO_DEPRECATED void dropNetwork(const QString &host = QString()); #endif /** * Wait for an answer to our request, until we get @p expected1 or @p expected2 * @return the result from readData, as well as the cmd in *pCmd if set, and the data in @p data */ int waitForAnswer(int expected1, int expected2, QByteArray &data, int *pCmd = nullptr); /** * Internal function to transmit meta data to the application. * m_outgoingMetaData will be cleared; this means that if the slave is for * example put on hold and picked up by a different KIO::Job later the new * job will not see the metadata sent before. * See kio/DESIGN.krun for an overview of the state * progression of a job/slave. * @warning calling this method may seriously interfere with the operation * of KIO which relies on the presence of some metadata at some points in time. * You should not use it if you are not familiar with KIO and not before * the slave is connected to the last job before returning to idle state. */ void sendMetaData(); /** * Internal function to transmit meta data to the application. * Like sendMetaData() but m_outgoingMetaData will not be cleared. * This method is mainly useful in code that runs before the slave is connected * to its final job. */ void sendAndKeepMetaData(); /** If your ioslave was killed by a signal, wasKilled() returns true. Check it regularly in lengthy functions (e.g. in get();) and return as fast as possible from this function if wasKilled() returns true. This will ensure that your slave destructor will be called correctly. */ bool wasKilled() const; /** Internally used. * @internal */ void setKillFlag(); /** Internally used * @internal */ void lookupHost(const QString &host); /** Internally used * @internal */ int waitForHostInfo(QHostInfo &info); /** * Checks with job if privilege operation is allowed. * @return privilege operation status. * @see PrivilegeOperationStatus * @since 5.43 */ PrivilegeOperationStatus requestPrivilegeOperation(); /** * Adds @p action to the list of PolicyKit actions which the * slave is authorized to perform. * * @param action the PolicyKit action * @since 5.45 */ void addTemporaryAuthorization(const QString &action); protected: /** * Name of the protocol supported by this slave */ QByteArray mProtocol; //Often used by TcpSlaveBase and unlikely to change MetaData mOutgoingMetaData; MetaData mIncomingMetaData; enum VirtualFunctionId { AppConnectionMade = 0, GetFileSystemFreeSpace = 1 // KF6 TODO: Turn into a virtual method }; virtual void virtual_hook(int id, void *data); private: // This helps catching missing tr()/i18n() calls in error(). void error(int _errid, const QByteArray &_text); void send(int cmd, const QByteArray &arr = QByteArray()); SlaveBasePrivate *const d; friend class SlaveBasePrivate; }; /** * Returns an appropriate error message if the given command @p cmd * is an unsupported action (ERR_UNSUPPORTED_ACTION). * @param protocol name of the protocol * @param cmd given command * @see enum Command */ KIOCORE_EXPORT QString unsupportedActionErrorString(const QString &protocol, int cmd); } #endif diff --git a/src/core/slaveinterface.h b/src/core/slaveinterface.h index 2cc4cd4a..ae8cd1d5 100644 --- a/src/core/slaveinterface.h +++ b/src/core/slaveinterface.h @@ -1,198 +1,199 @@ /* This file is part of the KDE project Copyright (C) 2000 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_slaveinterface_h #define __kio_slaveinterface_h #include #include #include #include #include #include class QUrl; namespace KIO { class Connection; // better there is one ... class SlaveInterfacePrivate; // Definition of enum Command has been moved to global.h /** * Identifiers for KIO informational messages. */ enum Info { INF_TOTAL_SIZE = 10, INF_PROCESSED_SIZE = 11, INF_SPEED, INF_REDIRECTION = 20, INF_MIME_TYPE = 21, INF_ERROR_PAGE = 22, INF_WARNING = 23, INF_GETTING_FILE, ///< @deprecated INF_UNUSED = 25, ///< now unused INF_INFOMESSAGE, INF_META_DATA, INF_NETWORK_STATUS, INF_MESSAGEBOX, INF_POSITION // add new ones here once a release is done, to avoid breaking binary compatibility }; /** * Identifiers for KIO data messages. */ enum Message { MSG_DATA = 100, MSG_DATA_REQ, MSG_ERROR, MSG_CONNECTED, MSG_FINISHED, MSG_STAT_ENTRY, // 105 MSG_LIST_ENTRIES, MSG_RENAMED, ///< unused MSG_RESUME, MSG_SLAVE_STATUS, ///< only for compatibility, use V2 for KF >= 5.45. TODO KF6: remove MSG_SLAVE_ACK, // 110 MSG_NET_REQUEST, MSG_NET_DROP, MSG_NEED_SUBURL_DATA, MSG_CANRESUME, MSG_AUTH_KEY, ///< @deprecated MSG_DEL_AUTH_KEY, ///< @deprecated MSG_OPENED, MSG_WRITTEN, MSG_HOST_INFO_REQ, MSG_PRIVILEGE_EXEC, MSG_SLAVE_STATUS_V2 // add new ones here once a release is done, to avoid breaking binary compatibility }; /** * @class KIO::SlaveInterface slaveinterface.h * * There are two classes that specifies the protocol between application * ( KIO::Job) and kioslave. SlaveInterface is the class to use on the application * end, SlaveBase is the one to use on the slave end. * * A call to foo() results in a call to slotFoo() on the other end. */ class KIOCORE_EXPORT SlaveInterface : public QObject { Q_OBJECT protected: SlaveInterface(SlaveInterfacePrivate &dd, QObject *parent = nullptr); public: virtual ~SlaveInterface(); // TODO KF6: remove these methods, Connection isn't an exported class KIOCORE_DEPRECATED void setConnection(Connection *connection); KIOCORE_DEPRECATED Connection *connection() const; // Send our answer to the MSG_RESUME (canResume) request // (to tell the "put" job whether to resume or not) void sendResumeAnswer(bool resume); /** * Sends our answer for the INF_MESSAGEBOX request. * * @since 4.11 */ void sendMessageBoxAnswer(int result); void setOffset(KIO::filesize_t offset); KIO::filesize_t offset() const; Q_SIGNALS: /////////// // Messages sent by the slave /////////// void data(const QByteArray &); void dataReq(); void error(int, const QString &); void connected(); void finished(); void slaveStatus(qint64, const QByteArray &, const QString &, bool); void listEntries(const KIO::UDSEntryList &); void statEntry(const KIO::UDSEntry &); void needSubUrlData(); void canResume(KIO::filesize_t); void open(); void written(KIO::filesize_t); + void close(); void privilegeOperationRequested(); /////////// // Info sent by the slave ////////// void metaData(const KIO::MetaData &); void totalSize(KIO::filesize_t); void processedSize(KIO::filesize_t); void redirection(const QUrl &); void position(KIO::filesize_t); void speed(unsigned long); void errorPage(); void mimeType(const QString &); void warning(const QString &); void infoMessage(const QString &); //void connectFinished(); //it does not get emitted anywhere protected: ///////////////// // Dispatching //////////////// virtual bool dispatch(); virtual bool dispatch(int _cmd, const QByteArray &data); void messageBox(int type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo); void messageBox(int type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &dontAskAgainName); // I need to identify the slaves void requestNetwork(const QString &, const QString &); void dropNetwork(const QString &, const QString &); protected Q_SLOTS: void calcSpeed(); protected: SlaveInterfacePrivate *const d_ptr; Q_DECLARE_PRIVATE(SlaveInterface) private: Q_PRIVATE_SLOT(d_func(), void slotHostInfo(QHostInfo)) }; } #endif diff --git a/src/ioslaves/file/file.cpp b/src/ioslaves/file/file.cpp index 428c35d4..67c3b122 100644 --- a/src/ioslaves/file/file.cpp +++ b/src/ioslaves/file/file.cpp @@ -1,1555 +1,1554 @@ /* 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. */ #include "file.h" #include #include #include #include #include "kioglobal_p.h" #ifdef Q_OS_UNIX #include "legacycodec.h" #endif #include #include #ifdef Q_OS_WIN #include #include #include //struct timeval #else #include #endif #include #include #include #include #include #ifdef Q_OS_WIN #include #include #endif #include #include #include #include #include #include #include #include #if HAVE_STATX #include #endif #if HAVE_VOLMGT #include #include #endif #include #include 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); } #ifdef Q_OS_UNIX LegacyCodec codec; #endif 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().constData()); 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().constData()); 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}, errno)) { if (!err.wasCanceled()) { switch (err) { 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.toSecsSinceEpoch(); // modification time if (::utime(QFile::encodeName(path).constData(), &utbuf) != 0) { if (auto err = execWithElevatedPrivilege(UTIME, {path, qint64(utbuf.actime), qint64(utbuf.modtime)}, errno)) { 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}, errno); } } QT_STATBUF buff; if (QT_LSTAT(QFile::encodeName(path).constData(), &buff) == -1) { bool dirCreated = QDir().mkdir(path); if (!dirCreated) { if (auto err = execWithElevatedPrivilege(MKDIR, {path}, errno)) { 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::redirect(const QUrl &url) { QUrl redir(url); redir.setScheme(config()->readEntry("DefaultRemoteProtocol", "smb")); // if we would redirect into the Windows world, let's also check for the // DavWWWRoot "token" which in the Windows world tells win explorer to access // a webdav url // https://www.webdavsystem.com/server/access/windows if ((redir.scheme() == QLatin1String("smb")) && redir.path().startsWith(QLatin1String("/DavWWWRoot/"))) { redir.setPath(redir.path().mid(11)); // remove /DavWWWRoot redir.setScheme(QStringLiteral("webdav")); } redirection(redir); finished(); } void FileProtocol::get(const QUrl &url) { if (!url.isLocalFile()) { redirect(url); 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, errno)) { 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()); 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()); mimeType(mt.name()); } totalSize(buff.st_size); position(0); opened(); } void FileProtocol::read(KIO::filesize_t bytes) { // qDebug() << "File::open -- read"; Q_ASSERT(mFile && mFile->isOpen()); - while (true) { - QByteArray res = mFile->read(bytes); + QVarLengthArray buffer(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; - } + qint64 bytesRead = mFile->read(buffer.data(), bytes); + + if (bytesRead == -1) { + qCWarning(KIO_FILE) << "Couldn't read. Error:" << mFile->errorString(); + error(KIO::ERR_CANNOT_READ, mFile->fileName()); + closeWithoutFinish(); + } else { + const QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); + data(fileData); } } void FileProtocol::write(const QByteArray &data) { // qDebug() << "File::open -- write"; Q_ASSERT(mFile && mFile->isWritable()); - if (mFile->write(data) != data.size()) { + qint64 bytesWritten = mFile->write(data); + + if (bytesWritten == -1) { if (mFile->error() == QFileDevice::ResourceError) { // disk full error(KIO::ERR_DISK_FULL, mFile->fileName()); - close(); + closeWithoutFinish(); } else { qCWarning(KIO_FILE) << "Couldn't write. Error:" << mFile->errorString(); error(KIO::ERR_CANNOT_WRITE, mFile->fileName()); - close(); + closeWithoutFinish(); } } else { - written(data.size()); + written(bytesWritten); } } 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(); + closeWithoutFinish(); } } -void FileProtocol::close() +void FileProtocol::closeWithoutFinish() { - // qDebug() << "File::open -- close "; Q_ASSERT(mFile); delete mFile; mFile = nullptr; +} +void FileProtocol::close() +{ + // qDebug() << "File::open -- close "; + closeWithoutFinish(); 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; 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, errno)) { 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()}, errno); 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}, errno); } } if (!QFile::rename(dest, dest_orig)) { if (auto err = execWithElevatedPrivilege(RENAME, {dest, dest_orig}, errno)) { if (!err.wasCanceled()) { qCWarning(KIO_FILE) << " Couldn't rename " << dest << " to " << dest_orig; error(KIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig); } return; } } org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(dest), QUrl::fromLocalFile(dest_orig)); } // set final permissions if (_mode != -1 && !(_flags & KIO::Resume)) { if (!QFile::setPermissions(dest_orig, modeToQFilePermissions(_mode))) { // couldn't chmod. Eat the error if the filesystem apparently doesn't support it. KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(dest_orig); if (mp && mp->testFileSystemFlag(KMountPoint::SupportsChmod)) { if (tryChangeFileAttr(CHMOD, {dest_orig, _mode}, errno)) { 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.toSecsSinceEpoch(); 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.toSecsSinceEpoch(); if (utime(QFile::encodeName(dest_orig).constData(), &utbuf) != 0) { tryChangeFileAttr(UTIME, {dest_orig, qint64(utbuf.actime), qint64(utbuf.modtime)}, errno); } #endif } } } // We have done our job => finish finished(); } QString FileProtocol::getUserName(KUserId uid) const { if (Q_UNLIKELY(!uid.isValid())) { return QString(); } auto it = mUsercache.find(uid); if (it == mUsercache.end()) { KUser user(uid); QString name = user.loginName(); if (name.isEmpty()) { name = uid.toString(); } it = mUsercache.insert(uid, name); } return *it; } QString FileProtocol::getGroupName(KGroupId gid) const { if (Q_UNLIKELY(!gid.isValid())) { return QString(); } auto it = mGroupcache.find(gid); if (it == mGroupcache.end()) { KUserGroup group(gid); QString name = group.name(); if (name.isEmpty()) { name = gid.toString(); } it = mGroupcache.insert(gid, name); } return *it; } #if HAVE_STATX // statx syscall is available inline int LSTAT(const char* path, struct statx * buff) { return statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS | STATX_BTIME, buff); } inline int STAT(const char* path, struct statx * buff) { return statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT, STATX_BASIC_STATS | STATX_BTIME, buff); } inline static uint16_t stat_mode(struct statx &buf) { return buf.stx_mode; } inline static uint32_t stat_dev(struct statx &buf) { return buf.stx_dev_major; } inline static uint64_t stat_ino(struct statx &buf) { return buf.stx_ino; } inline static uint64_t stat_size(struct statx &buf) { return buf.stx_size; } inline static uint32_t stat_uid(struct statx &buf) { return buf.stx_uid; } inline static uint32_t stat_gid(struct statx &buf) { return buf.stx_gid; } inline static int64_t stat_atime(struct statx &buf) { return buf.stx_atime.tv_sec; } inline static int64_t stat_mtime(struct statx &buf) { return buf.stx_mtime.tv_sec; } #else // regular stat struct inline int LSTAT(const char* path, QT_STATBUF * buff) { return QT_LSTAT(path, buff); } inline int STAT(const char* path, QT_STATBUF * buff) { return QT_STAT(path, buff); } inline static mode_t stat_mode(QT_STATBUF &buf) { return buf.st_mode; } inline static dev_t stat_dev(QT_STATBUF &buf) { return buf.st_dev; } inline static ino_t stat_ino(QT_STATBUF &buf) { return buf.st_ino; } inline static off_t stat_size(QT_STATBUF &buf) { return buf.st_size; } #ifndef Q_OS_WIN inline static uid_t stat_uid(QT_STATBUF &buf) { return buf.st_uid; } inline static gid_t stat_gid(QT_STATBUF &buf) { return buf.st_gid; } #endif inline static time_t stat_atime(QT_STATBUF &buf) { return buf.st_atime; } inline static time_t stat_mtime(QT_STATBUF &buf) { return buf.st_mtime; } #endif bool FileProtocol::createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, short int details) { assert(entry.count() == 0); // by contract :-) switch (details) { case 0: // filename, access, type, size, linkdest entry.reserve(5); break; case 1: // uid, gid, atime, mtime, btime entry.reserve(10); break; case 2: // acl data entry.reserve(13); break; default: // case details > 2 // dev, inode entry.reserve(15); break; } entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); mode_t type; mode_t access; bool isBrokenSymLink = false; signed long long size = 0LL; #if HAVE_POSIX_ACL QByteArray targetPath = path; #endif #if HAVE_STATX // statx syscall is available struct statx buff; #else QT_STATBUF buff; #endif if (LSTAT(path.data(), &buff) == 0) { if (details > 2) { entry.fastInsert(KIO::UDSEntry::UDS_DEVICE_ID, stat_dev(buff)); entry.fastInsert(KIO::UDSEntry::UDS_INODE, stat_ino(buff)); } if ((stat_mode(buff) & 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) #if HAVE_STATX size_t lowerBound = 256; size_t higherBound = 1024; uint64_t s = stat_size(buff); if (s > SIZE_MAX) { qCWarning(KIO_FILE) << "file size bigger than SIZE_MAX, too big for readlink use!" << path; return false; } size_t size = static_cast(s); using SizeType = size_t; #else off_t lowerBound = 256; off_t higherBound = 1024; off_t size = stat_size(buff); using SizeType = off_t; #endif SizeType bufferSize = qBound(lowerBound, size +1, higherBound); 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) { // the buffer was not filled in the last iteration // we are finished reading, break the loop linkTargetBuffer.truncate(n); break; } bufferSize *= 2; linkTargetBuffer.resize(bufferSize); } const QString linkTarget = QFile::decodeName(linkTargetBuffer); #endif entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkTarget); // A symlink -> follow it only if details>1 if (details > 1) { if (STAT(path.constData(), &buff) == -1) { isBrokenSymLink = true; } else { #if HAVE_POSIX_ACL // valid symlink, will get the ACLs of the destination targetPath = linkTargetBuffer; #endif } } } } else { // qCWarning(KIO_FILE) << "lstat didn't work on " << path.data(); return false; } if (isBrokenSymLink) { // It is a link pointing to nowhere type = S_IFMT - 1; access = S_IRWXU | S_IRWXG | S_IRWXO; size = 0LL; } else { type = stat_mode(buff) & S_IFMT; // extract file type access = stat_mode(buff) & 07777; // extract permissions size = stat_size(buff); } entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 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(targetPath, entry, type); } #endif if (details > 0) { entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, stat_mtime(buff)); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, stat_atime(buff)); #ifndef Q_OS_WIN entry.fastInsert(KIO::UDSEntry::UDS_USER, getUserName(KUserId(stat_uid(buff)))); entry.fastInsert(KIO::UDSEntry::UDS_GROUP, getGroupName(KGroupId(stat_gid(buff)))); #else #pragma message("TODO: st_uid and st_gid are always zero, use GetSecurityInfo to find the owner") #endif #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.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.st_birthtime); } #elif defined __st_birthtime /* As above, but OpenBSD calls it slightly differently. */ if (buff.__st_birthtime > 0) { entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.__st_birthtime); } #elif HAVE_STATX /* And linux version using statx syscall */ if (buff.stx_mask & STATX_BTIME) { entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.stx_btime.tv_sec); } #endif } 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 " + 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 " + 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")) == nullptr) { 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 = nullptr; rewind(mnttab); while (getmntent(mnttab, &mnt) == nullptr) { if (strcmp(_point.toLocal8Bit(), mnt.mnt_mountp) == 0) { devname = mnt.mnt_special; break; } } fclose(mnttab); if (devname == nullptr) { 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; } const QByteArray buffer = QFile::encodeName(pumountProg) + ' ' + 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.fastInsert(KIO::UDSEntry::UDS_EXTENDED_ACL, 1); if (acl) { const QString str = aclToText(acl); entry.fastInsert(KIO::UDSEntry::UDS_ACL_STRING, str); // qDebug() << path.constData() << "ACL:" << str; acl_free(acl); } if (defaultAcl) { const QString str = aclToText(defaultAcl); entry.fastInsert(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}, errno)) { if (!err.wasCanceled()) { error(KIO::ERR_CANNOT_DELETE, itemPath); } return false; } } } } QDir dir; for (const QString &itemPath : qAsConst(dirsToDelete)) { //qDebug() << "QDir::rmdir" << itemPath; if (!dir.rmdir(itemPath)) { if (auto err = execWithElevatedPrivilege(RMDIR, {itemPath}, errno)) { if (!err.wasCanceled()) { error(KIO::ERR_CANNOT_DELETE, itemPath); } return false; } } } return true; } void FileProtocol::fileSystemFreeSpace(const QUrl &url) { if (url.isLocalFile()) { const KDiskFreeSpaceInfo spaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo(url.toLocalFile()); if (spaceInfo.isValid()) { setMetaData(QStringLiteral("total"), QString::number(spaceInfo.size())); setMetaData(QStringLiteral("available"), QString::number(spaceInfo.available())); finished(); } else { error(KIO::ERR_COULD_NOT_STAT, url.url()); } } else { error(KIO::ERR_UNSUPPORTED_PROTOCOL, url.url()); } } void FileProtocol::virtual_hook(int id, void *data) { switch(id) { case SlaveBase::GetFileSystemFreeSpace: { QUrl *url = static_cast(data); fileSystemFreeSpace(*url); } break; default: { SlaveBase::virtual_hook(id, data); } break; } } // needed for JSON file embedding #include "file.moc" diff --git a/src/ioslaves/file/file.h b/src/ioslaves/file/file.h index 72e4a783..b8ae5c0e 100644 --- a/src/ioslaves/file/file.h +++ b/src/ioslaves/file/file.h @@ -1,122 +1,125 @@ /* Copyright (C) 2000-2002 Stephan Kulow Copyright (C) 2000-2002 David Faure Copyright (C) 2000-2002 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __file_h__ #define __file_h__ #include #include #include #include #include #include #include // mode_t #include #if HAVE_POSIX_ACL #include #include #endif #include "file_p.h" #include Q_DECLARE_LOGGING_CATEGORY(KIO_FILE) class FileProtocol : public QObject, public KIO::SlaveBase { Q_OBJECT public: FileProtocol(const QByteArray &pool, const QByteArray &app); virtual ~FileProtocol(); void get(const QUrl &url) override; virtual void put(const QUrl &url, int _mode, KIO::JobFlags _flags) override; virtual void copy(const QUrl &src, const QUrl &dest, int mode, KIO::JobFlags flags) override; virtual void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) override; virtual void symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags) override; void stat(const QUrl &url) override; void listDir(const QUrl &url) override; void mkdir(const QUrl &url, int permissions) override; void chmod(const QUrl &url, int permissions) override; void chown(const QUrl &url, const QString &owner, const QString &group) override; void setModificationTime(const QUrl &url, const QDateTime &mtime) override; void del(const QUrl &url, bool isfile) override; void open(const QUrl &url, QIODevice::OpenMode mode) override; void read(KIO::filesize_t size) override; void write(const QByteArray &data) override; void seek(KIO::filesize_t offset) override; void close() override; /** * Special commands supported by this slave: * 1 - mount * 2 - unmount */ void special(const QByteArray &data) override; void unmount(const QString &point); void mount(bool _ro, const char *_fstype, const QString &dev, const QString &point); bool pumount(const QString &point); bool pmount(const QString &dev); #if HAVE_POSIX_ACL static bool isExtendedACL(acl_t acl); #endif protected: void virtual_hook(int id, void *data) override; private: bool createUDSEntry(const QString &filename, const QByteArray &path, KIO::UDSEntry &entry, short int details); int setACL(const char *path, mode_t perm, bool _directoryDefault); QString getUserName(KUserId uid) const; QString getGroupName(KGroupId gid) const; bool deleteRecursive(const QString &path); void fileSystemFreeSpace(const QUrl &url); // KF6 TODO: Turn into virtual method in SlaveBase bool privilegeOperationUnitTestMode(); PrivilegeOperationReturnValue execWithElevatedPrivilege(ActionType action, const QVariantList &args, int errcode); PrivilegeOperationReturnValue tryOpen(QFile &f, const QByteArray &path, int flags, int mode, int errcode); // We want to execute chmod/chown/utime with elevated privileges (in copy & put) // only during the brief period privileges are elevated. If it's not the case show // a warning and continue. PrivilegeOperationReturnValue tryChangeFileAttr(ActionType action, const QVariantList &args, int errcode); void redirect(const QUrl &url); + // Close without calling finish(). Use this to close after error. + void closeWithoutFinish(); + private: mutable QHash mUsercache; mutable QHash mGroupcache; QFile *mFile; }; #endif