File indexing completed on 2024-04-14 15:39:43
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 if (lstat(QFile::encodeName(path), &sb) == -1) { 0036 const int err = errno; 0037 qWarning() << "Failed to stat block device" << name << strerror(err); 0038 return {}; 0039 } 0040 0041 #ifdef Q_OS_FREEBSD // There are only character devices 0042 if (!S_ISCHR(sb.st_mode)) { 0043 #else // On others assume they are block devices (e.g. linux) 0044 if (!S_ISBLK(sb.st_mode)) { 0045 #endif 0046 qWarning() << "Device is not actually a block device" << name; 0047 return {}; 0048 } 0049 0050 if (sb.st_uid != 0) { 0051 qWarning() << "Device is not owned by root" << name; 0052 return {}; 0053 } 0054 0055 return path; 0056 } 0057 0058 ActionReply SMARTHelper::smartctl(const QVariantMap &args) 0059 { 0060 // For security reasons we only accept fully resolved device names which 0061 // we use to construct the final /dev/$name path. 0062 const QString name = args.value(QStringLiteral("deviceName")).toString(); 0063 const QString devicePath = nameToPath(name); 0064 if (devicePath.isEmpty()) { 0065 return ActionReply::HelperErrorReply(); 0066 } 0067 0068 // PATH is super minimal when invoked through dbus 0069 setenv("PATH", "/usr/sbin:/sbin:/usr/local/sbin", 1); 0070 0071 const QString command = QStringLiteral("smartctl"); 0072 const QString all = QStringLiteral("--all"); 0073 0074 // JSON output. 0075 QProcess jsonProcess; 0076 // json=c is badly documented and means "gimme json but don't pretty print" 0077 jsonProcess.start(command, {all, QStringLiteral("--json=c"), devicePath}, QProcess::ReadOnly); 0078 0079 // Diagnostic CLI output for advanced users. 0080 QProcess cliProcess; 0081 cliProcess.start(command, {all, devicePath}, QProcess::ReadOnly); 0082 0083 // Wait for 120 seconds + 5 seconds leeway. 0084 // This allows us to ideally let smartctl time out internally and still 0085 // construct a json blob if possible. The kernel ioctl timeout for nvme 0086 // apparently is 60 seconds and internal smartctl timeouts also largely 0087 // appear to be in that range but there may be more than one :| 0088 // https://bugs.kde.org/show_bug.cgi?id=428844 0089 using namespace std::chrono_literals; 0090 if (!jsonProcess.waitForFinished(std::chrono::milliseconds(125s).count())) { 0091 qDebug() << "jsonProcess has not finished in time"; 0092 } 0093 // CLI was also running. Give it a bit of extra time still in case there was resource contention. 0094 if (!cliProcess.waitForFinished(std::chrono::milliseconds(10s).count())) { 0095 qDebug() << "cliProcess has not finished in time"; 0096 } 0097 0098 ActionReply reply; 0099 reply.addData(QStringLiteral("exitCode"), jsonProcess.exitCode()); 0100 reply.addData(QStringLiteral("data"), jsonProcess.readAllStandardOutput()); 0101 reply.addData(QStringLiteral("cliExitCode"), cliProcess.exitCode()); 0102 reply.addData(QStringLiteral("cliData"), cliProcess.readAllStandardOutput()); 0103 return reply; 0104 } 0105 0106 KAUTH_HELPER_MAIN("org.kde.kded.smart", SMARTHelper)