Commit 74850eb2 authored by Harald Sitter's avatar Harald Sitter 🏳🌈
Browse files

sftp: port to Result system to force serialization of error/finish condition

Summary:
the Result system was originally introduced to the FTP slave and now also
makes an appearance in the SFTP slave. the system introduces a separation
between logic and fronting API class to more tightly control when state
changing calls (finished()/error()) are made. since these calls may only
be made once during a given command multiple calls are at the very least
indicative of bad code and at worst cause severe state confusion for the
slavebase that it won't be able to recover from, rendering the slave
instance broken.

in the internal class Results are returned whenever an error can appear and
these Results must be handled in some form. the only way to effectively
produce user visible errors is to forward results up the call chain.

Test Plan:
- connect
- copy remotely
- overwrite remotely
- copy to local
- overwrite to local
- copy from local to remote
- copy form local to remote and overwrite

Reviewers: dfaure, feverfew

Reviewed By: dfaure, feverfew

Subscribers: kde-frameworks-devel, kfm-devel

Tags: #dolphin, #frameworks

Differential Revision: https://phabricator.kde.org/D27153
parent 70f2dc85
This diff is collapsed.
/*
* Copyright (c) 2001 Lucas Fisher <ljfisher@purdue.edu>
* Copyright (c) 2009 Andreas Schneider <mail@cynapses.org>
* Copyright (c) 2020 Harald Sitter <sitter@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
......@@ -35,35 +36,67 @@ namespace KIO {
class AuthInfo;
}
class sftpProtocol : public KIO::SlaveBase
/**
* Result type for returning error context.
*
* This is meant to be returned by functions that do not have a simple
* error conditions that could be represented by returning a bool, or
* when the contextual error string can only be correctly constructed
* inside the function. When using the Result type always mark the
* function Q_REQUIRED_RESULT to enforce handling of the Result.
*
* The Result is forwarded all the way to the frontend API where it is
* turned into an error() or finished() call.
*/
struct Result
{
bool success;
int error;
QString errorString;
Q_REQUIRED_RESULT inline static Result fail(int _error = KIO::ERR_UNKNOWN,
const QString &_errorString = QString())
{
return Result { false, _error, _errorString };
}
Q_REQUIRED_RESULT inline static Result pass()
{
return Result { true, 0, QString() };
}
};
class SFTPSlave;
class SFTPInternal
{
public:
sftpProtocol(const QByteArray &pool_socket, const QByteArray &app_socket);
~sftpProtocol() override;
void setHost(const QString &h, quint16 port, const QString& user, const QString& pass) override;
void get(const QUrl &url) override;
void listDir(const QUrl &url) override ;
void mimetype(const QUrl &url) override;
void stat(const QUrl &url) override;
void copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) override;
void put(const QUrl &url, int permissions, KIO::JobFlags flags) override;
void closeConnection() override;
void slave_status() override;
void del(const QUrl &url, bool isfile) override;
void chmod(const QUrl &url, int permissions) override;
void symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags) override;
void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) override;
void mkdir(const QUrl &url, int permissions) override;
void openConnection() override;
explicit SFTPInternal(SFTPSlave *qptr);
~SFTPInternal();
void setHost(const QString &h, quint16 port, const QString& user, const QString& pass);
Q_REQUIRED_RESULT Result get(const QUrl &url);
Q_REQUIRED_RESULT Result listDir(const QUrl &url);
Q_REQUIRED_RESULT Result mimetype(const QUrl &url);
Q_REQUIRED_RESULT Result stat(const QUrl &url);
Q_REQUIRED_RESULT Result copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags);
Q_REQUIRED_RESULT Result put(const QUrl &url, int permissions, KIO::JobFlags flags);
void closeConnection();
void slave_status();
Q_REQUIRED_RESULT Result del(const QUrl &url, bool isfile);
Q_REQUIRED_RESULT Result chmod(const QUrl &url, int permissions);
Q_REQUIRED_RESULT Result symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags);
Q_REQUIRED_RESULT Result rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags);
Q_REQUIRED_RESULT Result mkdir(const QUrl &url, int permissions);
Q_REQUIRED_RESULT Result openConnection();
// KIO::FileJob interface
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 truncate(KIO::filesize_t length);
void close() override;
void special(const QByteArray &data) override;
Q_REQUIRED_RESULT Result open(const QUrl &url, QIODevice::OpenMode mode);
Q_REQUIRED_RESULT Result read(KIO::filesize_t size);
Q_REQUIRED_RESULT Result write(const QByteArray &data);
Q_REQUIRED_RESULT Result seek(KIO::filesize_t offset);
Q_REQUIRED_RESULT Result truncate(KIO::filesize_t length);
void close();
Q_REQUIRED_RESULT Result special(const QByteArray &data);
// libssh authentication callback (note that this is called by the
// global ::auth_callback() call.
......@@ -75,24 +108,30 @@ public:
void log_callback(int priority, const char *function, const char *buffer,
void *userdata);
protected:
void virtual_hook(int id, void *data) override;
// Must call after construction!
// Bit rubbish, but we need to return something on init.
Q_REQUIRED_RESULT Result init();
Q_REQUIRED_RESULT Result fileSystemFreeSpace(const QUrl &url); // KF6 TODO: Once a virtual fileSystemFreeSpace method in SlaveBase exists, override it
private: // Private variables
/** Fronting SlaveBase instance */
SFTPSlave *q = nullptr;
/** True if ioslave is connected to sftp server. */
bool mConnected;
bool mConnected = false;
/** Host we are connected to. */
QString mHost;
/** Port we are connected to. */
int mPort;
int mPort = -1;
/** The ssh session for the connection */
ssh_session mSession;
ssh_session mSession = nullptr;
/** The sftp session for the connection */
sftp_session mSftp;
sftp_session mSftp = nullptr;
/** Username to use when connecting */
QString mUsername;
......@@ -101,33 +140,20 @@ private: // Private variables
QString mPassword;
/** The open file */
sftp_file mOpenFile;
sftp_file mOpenFile = nullptr;
/** The open URL */
QUrl mOpenUrl;
ssh_callbacks mCallbacks;
/** Version of the sftp protocol we are using. */
int sftpVersion;
struct Status
{
int code;
KIO::filesize_t size;
QString text;
};
ssh_callbacks mCallbacks = nullptr;
// KIO::FileJob interface
/** The opened handle */
QByteArray openHandle;
QUrl openUrl;
KIO::filesize_t openOffset;
KIO::filesize_t openOffset = 0;
/**
* Holds public key authentication info for proper retry handling.
*/
KIO::AuthInfo* mPublicKeyAuthInfo;
* Holds public key authentication info for proper retry handling.
*/
KIO::AuthInfo *mPublicKeyAuthInfo = nullptr;
/**
* GetRequest encapsulates several SFTP get requests into a single object.
......@@ -183,7 +209,7 @@ private: // Private variables
private: // private methods
int authenticateKeyboardInteractive(KIO::AuthInfo &info);
void reportError(const QUrl &url, const int err);
Q_REQUIRED_RESULT Result reportError(const QUrl &url, const int err);
bool createUDSEntry(const QString &filename, const QByteArray &path,
KIO::UDSEntry &entry, short int details);
......@@ -191,30 +217,99 @@ private: // private methods
QString canonicalizePath(const QString &path);
void requiresUserNameRedirection();
void clearPubKeyAuthInfo();
bool sftpLogin();
bool sftpOpenConnection(const KIO::AuthInfo&);
void sftpSendWarning(int errorCode, const QString& url);
Q_REQUIRED_RESULT Result sftpLogin();
Q_REQUIRED_RESULT Result sftpOpenConnection(const KIO::AuthInfo &);
Q_REQUIRED_RESULT Result sftpGet(const QUrl &url, KIO::fileoffset_t offset = -1, int fd = -1);
Q_REQUIRED_RESULT Result sftpPut(const QUrl &url, int permissions, KIO::JobFlags flags, int fd = -1);
Q_REQUIRED_RESULT Result sftpCopyGet(const QUrl &url, const QString &src, int permissions, KIO::JobFlags flags);
Q_REQUIRED_RESULT Result sftpCopyPut(const QUrl &url, const QString &dest, int permissions, KIO::JobFlags flags);
};
/**
* Fronting class.
* The purpose of this is to separate the slave interface from the slave logic and force state
* convergence.
* Specifically logic code must not call finalization API error()/finished() but instead move
* a single Result object up the call chain. This is to prevent overwriting errors and/or finished
* finality states and broken-state situations those can cause.
*/
class SFTPSlave : public KIO::SlaveBase
{
public:
SFTPSlave(const QByteArray &pool_socket, const QByteArray &app_socket);
~SFTPSlave() override = default;
void setHost(const QString &host, quint16 port, const QString &user, const QString &pass) override;
void get(const QUrl &url) override;
void listDir(const QUrl &url) override ;
void mimetype(const QUrl &url) override;
void stat(const QUrl &url) override;
void copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags) override;
void put(const QUrl &url, int permissions, KIO::JobFlags flags) override;
void slave_status() override;
void del(const QUrl &url, bool isfile) override;
void chmod(const QUrl &url, int permissions) override;
void symlink(const QString &target, const QUrl &dest, KIO::JobFlags flags) override;
void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) override;
void mkdir(const QUrl &url, int permissions) override;
void openConnection() override;
void closeConnection() override;
// KIO::FileJob interface
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 truncate(KIO::filesize_t length);
void close() override;
void special(const QByteArray &data) override;
void virtual_hook(int id, void *data) override;
private:
// WARNING: All members and all logic not confined to one of the public functions
// must go into SftpInternal!
/**
* Overridden to prevent SftpInternal from easily calling
* q->opened(). Use a Result return type on error conditions
* instead. When there was no error Result the
* connection is considered opened.
*
* SftpInternal must not call any state-changing signals!
*/
void opened()
{
SlaveBase::opened();
}
/**
* @see opened()
*/
void error(int _errid, const QString &_text)
{
SlaveBase::error(_errid, _text);
}
// Close without error() or finish() call (in case of errors for example)
void closeWithoutFinish();
/**
* @see opened()
*/
void finished()
{
SlaveBase::finished();
}
/**
* Status Code returned from ftpPut() and ftpGet(), used to select
* source or destination url for error messages
*/
typedef enum {
Success,
ClientError,
ServerError
} StatusCode;
StatusCode sftpGet(const QUrl& url, int& errorCode, KIO::fileoffset_t offset = -1, int fd = -1);
StatusCode sftpPut(const QUrl& url, int permissions, KIO::JobFlags flags, int& errorCode, int fd = -1);
StatusCode sftpCopyGet(const QUrl& url, const QString& src, int permissions, KIO::JobFlags flags, int& errorCode);
StatusCode sftpCopyPut(const QUrl& url, const QString& dest, int permissions, KIO::JobFlags flags, int& errorCode);
void fileSystemFreeSpace(const QUrl& url); // KF6 TODO: Once a virtual fileSystemFreeSpace method in SlaveBase exists, override it
* Calls finished() or error() as appropriate
*/
void finalize(const Result &result);
/**
* Calls error() if and only if the result is an error
*/
void maybeError(const Result &result);
QScopedPointer<SFTPInternal> d { new SFTPInternal(this) };
};
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment