[OpenDocString] kdeconnect-kde (cpp)
mounter.cpp
Mounter::Mounter(SftpPlugin *sftp)
    : QObject(sftp)
    , m_sftp(sftp)
    , m_proc(nullptr)
    , m_mountPoint(sftp->mountPoint())
    , m_started(false)
{
    connect(m_sftp, &SftpPlugin::packetReceived, this, &Mounter::onPackageReceived);

    connect(&m_connectTimer, &QTimer::timeout, this, &Mounter::onMountTimeout);

    connect(this, &Mounter::mounted, &m_connectTimer, &QTimer::stop);
    connect(this, &Mounter::failed, &m_connectTimer, &QTimer::stop);

    m_connectTimer.setInterval(10000);
    m_connectTimer.setSingleShot(true);

    QTimer::singleShot(0, this, &Mounter::start);
    qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created mounter";
}
This constructor builds a mounter object from an SftpPlugin object. It takes the sftp object as input and creates a QTimer object to track the mount time. It also starts a signaling the start of the mounter.
Mounter::~Mounter()
{
    qCDebug(KDECONNECT_PLUGIN_SFTP) << "Destroy mounter";
    unmount(false);
}
This removes the mounter object upon destruction.
bool Mounter::wait()
{
    if (m_started) {
        return true;
    }

    qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting loop to wait for mount";

    MountLoop loop;
    connect(this, &Mounter::mounted, &loop, &MountLoop::succeeded);
    connect(this, &Mounter::failed, &loop, &MountLoop::failed);
    return loop.exec();
}
This loops for the mount events and waits for them to finish. It returns true if the loop has already been started, false otherwise.
void Mounter::onPackageReceived(const NetworkPacket &np)
{
    if (np.get(QStringLiteral("stop"), false)) {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "SFTP server stopped";
        unmount(false);
        return;
    }

    if (np.has(QStringLiteral("errorMessage"))) {
        Q_EMIT failed(np.get(QStringLiteral("errorMessage")));
        return;
    }

    // This is the previous code, to access sftp server using KIO. Now we are
    // using the external binary sshfs, and accessing it as a local filesystem.
    /*
     *    QUrl url;
     *    url.setScheme("sftp");
     *    url.setHost(np.get("ip"));
     *    url.setPort(np.get("port").toInt());
     *    url.setUserName(np.get("user"));
     *    url.setPassword(np.get("password"));
     *    url.setPath(np.get("path"));
     *    new KRun(url, 0);
     *    Q_EMIT mounted();
     */

    unmount(false);

    m_proc = new KProcess();
    m_proc->setOutputChannelMode(KProcess::MergedChannels);

    connect(m_proc, &QProcess::started, this, &Mounter::onStarted);
    connect(m_proc, &QProcess::errorOccurred, this, &Mounter::onError);
    connect(m_proc, QOverload::of(&QProcess::finished), this, &Mounter::onFinished);

    QDir().mkpath(m_mountPoint);

    const QString program = QStringLiteral("sshfs");

    QString path;
    if (np.has(QStringLiteral("multiPaths")))
        path = QStringLiteral("/");
    else
        path = np.get(QStringLiteral("path"));

    QHostAddress addr = m_sftp->device()->getLocalIpAddress();
    if (addr == QHostAddress::Null) {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "Device doesn't have a LanDeviceLink, unable to get IP address";
        return;
    }
    QString ip = addr.toString();
    if (addr.protocol() == QAbstractSocket::IPv6Protocol) {
        ip.prepend(QLatin1Char('['));
        ip.append(QLatin1Char(']'));
    }

    const QStringList arguments =
        QStringList() << QStringLiteral("%1@%2:%3")
                             .arg(np.get(QStringLiteral("user")),
                                  ip,
                                  path)
                      << m_mountPoint << QStringLiteral("-p") << np.get(QStringLiteral("port"))
                      << QStringLiteral("-s") // This fixes a bug where file chunks are sent out of order and get corrupted on reception
                      << QStringLiteral("-f") << QStringLiteral("-F") << QStringLiteral("/dev/null") // Do not use ~/.ssh/config
                      << QStringLiteral("-o") << QStringLiteral("IdentityFile=") + KdeConnectConfig::instance().privateKeyPath() << QStringLiteral("-o")
                      << QStringLiteral("StrictHostKeyChecking=no") // Do not ask for confirmation because it is not a known host
                      << QStringLiteral("-o") << QStringLiteral("UserKnownHostsFile=/dev/null") // Prevent storing as a known host
                      << QStringLiteral("-o") << QStringLiteral("HostKeyAlgorithms=+ssh-dss\\,ssh-rsa") // https://bugs.kde.org/show_bug.cgi?id=351725
                      << QStringLiteral("-o") << QStringLiteral("PubkeyAcceptedKeyTypes=+ssh-rsa") // https://bugs.kde.org/show_bug.cgi?id=443155
                      << QStringLiteral("-o") << QStringLiteral("uid=") + QString::number(getuid()) << QStringLiteral("-o")
                      << QStringLiteral("gid=") + QString::number(getgid()) << QStringLiteral("-o") << QStringLiteral("reconnect") << QStringLiteral("-o")
                      << QStringLiteral("ServerAliveInterval=30") << QStringLiteral("-o") << QStringLiteral("password_stdin");

    m_proc->setProgram(program, arguments);

    qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting process: " << m_proc->program().join(QStringLiteral(" "));
    m_proc->start();

    // qCDebug(KDECONNECT_PLUGIN_SFTP) << "Passing password: " << np.get("password").toLatin1();
    m_proc->write(np.get(QStringLiteral("password")).toLatin1());
    m_proc->write("\n");
}
This creates a remote server and writes its password to it. It takes the input of the network package np and creates a URL, sets the host and port of the device and connects it to the mounted process. It also creates a remote host and port if it is a LanDeviceLink.
void Mounter::onStarted()
{
    qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process started";
    m_started = true;
    Q_EMIT mounted();

    // m_proc->setStandardOutputFile("/tmp/kdeconnect-sftp.out");
    // m_proc->setStandardErrorFile("/tmp/kdeconnect-sftp.err");

    auto proc = m_proc;
    connect(m_proc, &KProcess::readyReadStandardError, this, [proc]() {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "stderr: " << proc->readAll();
    });
    connect(m_proc, &KProcess::readyReadStandardOutput, this, [proc]() {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "stdout:" << proc->readAll();
    });
}
This sets up the output and error files for the process and sets up connections for stderr and stdout.
void Mounter::onError(QProcess::ProcessError error)
{
    if (error == QProcess::FailedToStart) {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process failed to start";
        m_started = false;
        Q_EMIT failed(i18n("Failed to start sshfs"));
    } else if (error == QProcess::ProcessError::Crashed) {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process crashed";
        m_started = false;
        Q_EMIT failed(i18n("sshfs process crashed"));
    } else {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process error" << error;
        m_started = false;
        Q_EMIT failed(i18n("Unknown error in sshfs"));
    }
}
This method is called when a process fails to start or crashed. It logs the error and marks the process as failed.
void Mounter::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    if (exitStatus == QProcess::NormalExit && exitCode == 0) {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process finished (exit code: " << exitCode << ")";
        Q_EMIT unmounted();
    } else {
        qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process failed (exit code:" << exitCode << ")";
        Q_EMIT failed(i18n("Error when accessing filesystem. sshfs finished with exit code %0").arg(exitCode));
    }

    unmount(true);
}
This removes the filesystem after the process has finished with the given exit code. If the exit code is 0, the process is unmounted and the function logs a debug message.
void Mounter::onMountTimeout()
{
    qCDebug(KDECONNECT_PLUGIN_SFTP) << "Timeout: device not responding";
    Q_EMIT failed(i18n("Failed to mount filesystem: device not responding"));
}
This function is called when the device is not responding. It logs a debug message and emits a failed error.
void Mounter::start()
{
    NetworkPacket np(PACKET_TYPE_SFTP_REQUEST, {{QStringLiteral("startBrowsing"), true}});
    m_sftp->sendPacket(np);

    m_connectTimer.start();
}
This sends a startBrowsing network packet and starts the timer.
void Mounter::unmount(bool finished)
{
    qCDebug(KDECONNECT_PLUGIN_SFTP) << "Unmount" << m_proc;
    if (m_proc) {
        if (!finished) {
            // Process is still running, we want to stop it
            // But when the finished signal come, we might have already gone.
            // Disconnect everything.
            m_proc->disconnect();
            m_proc->kill();

            auto proc = m_proc;
            m_proc = nullptr;
            connect(proc, static_cast(&QProcess::finished), [proc]() {
                qCDebug(KDECONNECT_PLUGIN_SFTP) << "Free" << proc;
                proc->deleteLater();
            });
            Q_EMIT unmounted();
        } else
            m_proc->deleteLater();

            // Free mount point (won't always succeed if the path is in use)
#if defined(HAVE_FUSERMOUNT)
        KProcess::execute(QStringList() << QStringLiteral("fusermount") << QStringLiteral("-u") << m_mountPoint, 10000);
#else
        KProcess::execute(QStringList() << QStringLiteral("umount") << m_mountPoint, 10000);
#endif
        m_proc = nullptr;
    }
    m_started = false;
}
This removes the current process if it is not running. It kills the process if it is not running, and marks the process as unmounted.