Changeset View
Standalone View
src/ioslaves/file/file_unix.cpp
Show All 33 Lines | |||||
34 | #include <QDebug> | 34 | #include <QDebug> | ||
35 | #include <kconfiggroup.h> | 35 | #include <kconfiggroup.h> | ||
36 | #include <klocalizedstring.h> | 36 | #include <klocalizedstring.h> | ||
37 | #include <kmountpoint.h> | 37 | #include <kmountpoint.h> | ||
38 | 38 | | |||
39 | #include <errno.h> | 39 | #include <errno.h> | ||
40 | #include <utime.h> | 40 | #include <utime.h> | ||
41 | 41 | | |||
42 | #include <KAuth> | ||||
43 | | ||||
42 | //sendfile has different semantics in different platforms | 44 | //sendfile has different semantics in different platforms | ||
43 | #if defined HAVE_SENDFILE && defined Q_OS_LINUX | 45 | #if defined HAVE_SENDFILE && defined Q_OS_LINUX | ||
44 | #define USE_SENDFILE 1 | 46 | #define USE_SENDFILE 1 | ||
45 | #endif | 47 | #endif | ||
46 | 48 | | |||
47 | #ifdef USE_SENDFILE | 49 | #ifdef USE_SENDFILE | ||
48 | #include <sys/sendfile.h> | 50 | #include <sys/sendfile.h> | ||
49 | #endif | 51 | #endif | ||
▲ Show 20 Lines • Show All 595 Lines • ▼ Show 20 Line(s) | 646 | for (; it1 != mOutgoingMetaData.end(); it1++) { | |||
645 | // qDebug() << it1.key() << " = " << it1.data(); | 647 | // qDebug() << it1.key() << " = " << it1.data(); | ||
646 | } | 648 | } | ||
647 | ///////// | 649 | ///////// | ||
648 | #endif | 650 | #endif | ||
649 | statEntry(entry); | 651 | statEntry(entry); | ||
650 | 652 | | |||
651 | finished(); | 653 | finished(); | ||
652 | } | 654 | } | ||
655 | | ||||
656 | bool FileProtocol::execWithElevatedPrivilege(ActionType action, const QVariant &arg1, | ||||
657 | const QVariant &arg2, const QVariant &arg3) | ||||
658 | { | ||||
659 | if (!(errno == EACCES || errno == EPERM)) { | ||||
dfaure: with EPERM we should try as root too, no?
(e.g. for unlink, rename, chown...) | |||||
660 | return false; | ||||
661 | } | ||||
662 | | ||||
663 | KIO::PrivilegeOperationStatus opStatus = privilegeOperationStatus(); | ||||
664 | if (opStatus != KIO::OperationAllowed) { | ||||
665 | if (opStatus == KIO::OperationCanceled) { | ||||
666 | mUserCanceled = true; | ||||
Who's going to read that value? The caller? Abusing errno to ship a return value via global state reads horrible to me.... we're not a libc function.... dfaure: Who's going to read that value? The caller? Abusing errno to ship a return value via global… | |||||
No chance to return an enum instead of a bool here, to avoid modifying state? The problem with mUserCanceled is that it's easy to get it wrong. Like in this patch, which never resets it to false for the next operation after one canceled operation. It's just cleaner to return an enum instead of modifying a member variable temporarily, although the question is, in both cases, what the caller code will look like... My fear with a member var is that too many callers will forget to check it. Before: if (!dir.rmdir(itemPath)) { error(KIO::ERR_CANNOT_DELETE, itemPath); return false; } After: if (!dir.rmdir(itemPath)) { if (auto ret = execWithElevatedPrivilege(RMDIR, itemPath)) { // for this to work, success must be 0 (or the returned type can be a class with operator bool()...) if (ret == KIO::OperationCanceled) { // or a different enum error(KIO::ERR_USER_CANCELED, itemPath); } else { error(KIO::ERR_CANNOT_DELETE, itemPath); } return false; } } Alternatively, since we know cancelling will always need to error(KIO::ERR_USER_CANCELED) (and the QString argument doesn't matter), we could call error from within execWithElevatedPrivilege. But we still need the enum ret val: if (!dir.rmdir(itemPath)) { if (auto ret = execWithElevatedPrivilege(RMDIR, itemPath)) { // see above if (ret != KIO::OperationCanceled) { // or a different enum error(KIO::ERR_CANNOT_DELETE, itemPath); } return false; } } I actually like the idea of returning a small class. Then it could even have a wasCanceled() method... we don't need an enum, we need a value that can convert to bool (for the if) and additionally let us know if cancelation happened. But all this in a temporary value, not in a member var of slavebase. To flesh it out in more details: class ExecWithElevatedPrivilegeReturnValue { public: static ExecWithElevatedPrivilegeReturnValue success() { return ExecWithElevatedPrivilegeReturnValue{true, false}; } static ExecWithElevatedPrivilegeReturnValue failure() { return ExecWithElevatedPrivilegeReturnValue{true, false}; } static ExecWithElevatedPrivilegeReturnValue canceled() { return ExecWithElevatedPrivilegeReturnValue{true, true}; } operator bool() const { return m_success; } bool wasCanceled() const { return m_canceled; } private: ExecWithElevatedPrivilegeReturnValue(bool success, bool canceled) : m_success(success), m_canceled(canceled) {} const bool m_success; const bool m_canceled; }; (no setters, all of the slave code shouldn't be able to change this object, it's created immediately with the right values) With this, the calling code can be simply if (!dir.rmdir(itemPath)) { if (auto ret = execWithElevatedPrivilege(RMDIR, itemPath)) { if (!ret.wasCanceled()) { error(KIO::ERR_CANNOT_DELETE, itemPath); } return false; } } and the code in this review (execWithElevatedPrivilege) can do things like return ExecWithElevatedPrivilegeReturnValue::canceled(); etc. How does that sound? dfaure: No chance to return an enum instead of a bool here, to avoid modifying state?
The problem with… | |||||
667 | } | ||||
668 | return false; | ||||
669 | } | ||||
670 | | ||||
Wait, why the two step roundtrip here? Which even removes the need for any enum, it's really just one request and one reply which is true or false. dfaure: Wait, why the two step roundtrip here?
Instead of "can you become root?" / "yes I can" / "ok… | |||||
671 | KAuth::Action execAction(QStringLiteral("org.kde.kio.file.exec")); | ||||
672 | execAction.setHelperId(QStringLiteral("org.kde.kio.file")); | ||||
673 | | ||||
674 | // if we are unit testing let's pretend to execute the action. | ||||
675 | if (metaData(QStringLiteral("UnitTesting")) == QLatin1String("true")) { | ||||
676 | const QString metaData = execAction.name() + ',' | ||||
677 | + (execAction.isValid() ? "true" : "false") + ',' | ||||
dfaure: single-quotes for the commas (','), like in my earlier recommendation. | |||||
678 | + execAction.helperId() + ',' | ||||
679 | + (execAction.status() == KAuth::Action::AuthRequiredStatus ? "true" : "false"); | ||||
680 | setMetaData(QStringLiteral("TestData"), metaData); | ||||
681 | return true; | ||||
682 | } | ||||
683 | | ||||
684 | QByteArray helperArgs; | ||||
685 | QDataStream out(&helperArgs, QIODevice::WriteOnly); | ||||
686 | out << action << arg1 << arg2 << arg3; | ||||
687 | | ||||
688 | QVariantMap argv; | ||||
689 | argv.insert(QStringLiteral("arguments"), helperArgs); | ||||
690 | execAction.setArguments(argv); | ||||
691 | | ||||
use C++11 initializer to avoid reallocations, i.e. QStringList testData{execAction.name(), ..., ..., ...}; Ah but then you just use a join(","), so the most efficient thing to do here would be to drop the QStringList and do const QString metaData = execAction.name() + ',' + QString::number... + ',' + etc. dfaure: use C++11 initializer to avoid reallocations, i.e. `QStringList testData{execAction.name(), ... | |||||
692 | auto reply = execAction.execute(); | ||||
693 | if (reply->exec()) { | ||||
694 | return true; | ||||
695 | } | ||||
696 | | ||||
697 | return false; | ||||
698 | } |
with EPERM we should try as root too, no?
(e.g. for unlink, rename, chown...)