File indexing completed on 2024-04-21 05:33:27

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
0003 
0004 #include "helper.h"
0005 
0006 #include <QDebug>
0007 #include <QFileInfo>
0008 #include <QProcess>
0009 
0010 #include <chrono>
0011 
0012 #include <errno.h>
0013 #include <fcntl.h>
0014 #include <string.h>
0015 #include <sys/stat.h>
0016 #include <sys/types.h>
0017 #include <unistd.h>
0018 
0019 // Append name to /dev/ and ensure it is a trustable block device.
0020 static QString nameToPath(const QString &name)
0021 {
0022     if (name.isEmpty()) {
0023         return {};
0024     }
0025 
0026     // This also excludes relative path shenanigans as they'd all need to contain a separator.
0027     if (name.contains(QLatin1Char('/'))) {
0028         qWarning() << "Device names must not contain slashes";
0029         return {};
0030     }
0031 
0032     const QString path = QStringLiteral("/dev/%1").arg(name);
0033 
0034     struct stat sb;
0035     QByteArray fileName(QFile::encodeName(path));
0036     if (lstat(fileName.constData(), &sb) == -1) {
0037         const int err = errno;
0038         qWarning() << "Failed to stat block device" << name << strerror(err);
0039         return {};
0040     }
0041 
0042 #ifdef Q_OS_FREEBSD // There are only character devices
0043     if (!S_ISCHR(sb.st_mode)) {
0044 #else // On others assume they are block devices (e.g. linux)
0045     if (!S_ISBLK(sb.st_mode)) {
0046 #endif
0047         qWarning() << "Device is not actually a block device" << name;
0048         return {};
0049     }
0050 
0051     if (sb.st_uid != 0) {
0052         qWarning() << "Device is not owned by root" << name;
0053         return {};
0054     }
0055 
0056     return path;
0057 }
0058 
0059 ActionReply SMARTHelper::smartctl(const QVariantMap &args)
0060 {
0061     // For security reasons we only accept fully resolved device names which
0062     // we use to construct the final /dev/$name path.
0063     const QString name = args.value(QStringLiteral("deviceName")).toString();
0064     const QString devicePath = nameToPath(name);
0065     if (devicePath.isEmpty()) {
0066         return ActionReply::HelperErrorReply();
0067     }
0068 
0069     // PATH is super minimal when invoked through dbus
0070     setenv("PATH", "/usr/sbin:/sbin:/usr/local/sbin", 1);
0071 
0072     const QString command = QStringLiteral("smartctl");
0073     const QString all = QStringLiteral("--all");
0074 
0075     // JSON output.
0076     QProcess jsonProcess;
0077     // json=c is badly documented and means "gimme json but don't pretty print"
0078     jsonProcess.start(command, {all, QStringLiteral("--json=c"), devicePath}, QProcess::ReadOnly);
0079 
0080     // Diagnostic CLI output for advanced users.
0081     QProcess cliProcess;
0082     cliProcess.start(command, {all, devicePath}, QProcess::ReadOnly);
0083 
0084     // Wait for 120 seconds + 5 seconds leeway.
0085     // This allows us to ideally let smartctl time out internally and still
0086     // construct a json blob if possible. The kernel ioctl timeout for nvme
0087     // apparently is 60 seconds and internal smartctl timeouts also largely
0088     // appear to be in that range but there may be more than one :|
0089     // https://bugs.kde.org/show_bug.cgi?id=428844
0090     using namespace std::chrono_literals;
0091     if (!jsonProcess.waitForFinished(std::chrono::milliseconds(125s).count())) {
0092         qDebug() << "jsonProcess has not finished in time";
0093     }
0094     // CLI was also running. Give it a bit of extra time still in case there was resource contention.
0095     if (!cliProcess.waitForFinished(std::chrono::milliseconds(10s).count())) {
0096         qDebug() << "cliProcess has not finished in time";
0097     }
0098 
0099     ActionReply reply;
0100     reply.addData(QStringLiteral("exitCode"), jsonProcess.exitCode());
0101     reply.addData(QStringLiteral("data"), jsonProcess.readAllStandardOutput());
0102     reply.addData(QStringLiteral("cliExitCode"), cliProcess.exitCode());
0103     reply.addData(QStringLiteral("cliData"), cliProcess.readAllStandardOutput());
0104     return reply;
0105 }
0106 
0107 KAUTH_HELPER_MAIN("org.kde.kded.smart", SMARTHelper)
0108 
0109 #include "moc_helper.cpp"