File indexing completed on 2024-04-21 05:45:52

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
0003 
0004 #include "entries.h"
0005 
0006 #include <sys/stat.h>
0007 #include <sys/types.h>
0008 #include <unistd.h>
0009 
0010 #include <cctype>
0011 #include <filesystem>
0012 
0013 #include <QDebug>
0014 #include <QFile>
0015 
0016 static constexpr auto kernelMaxWatches = 1048576UL;
0017 static constexpr auto kernelDefaultWatches = 8192UL;
0018 static constexpr auto kernelDefaultInstances = 128UL;
0019 
0020 qulonglong procULongLong(const QString &path)
0021 {
0022     if (QFile file(path); file.open(QFile::ReadOnly)) {
0023         bool ok = false;
0024         const qulonglong value = file.readAll().simplified().toULongLong(&ok);
0025         if (ok) {
0026             return value;
0027         }
0028     }
0029     return 0;
0030 }
0031 
0032 std::vector<Entry> collectEntries(const std::string_view &procPath)
0033 {
0034     std::vector<Entry> ret;
0035     std::filesystem::path proc(procPath);
0036     for (const auto &procEntry : std::filesystem::directory_iterator(proc)) {
0037         if (!procEntry.is_directory()) {
0038             continue;
0039         }
0040 
0041         bool isInt = false;
0042         Entry entry;
0043         entry.pid = QString::fromStdString(procEntry.path().filename()).toInt(&isInt);
0044         if (!isInt) {
0045             continue;
0046         }
0047 
0048         struct stat statbuf {
0049         };
0050         if (lstat(procEntry.path().c_str(), &statbuf) != 0) {
0051             qWarning() << "Failed to stat" << procEntry.path().c_str() << strerror(errno);
0052             continue;
0053         }
0054         entry.uid = statbuf.st_uid;
0055 
0056         std::error_code error;
0057         for (const auto &fdEntry : std::filesystem::directory_iterator(procEntry.path() / u"fd", error)) {
0058             if (!fdEntry.is_symlink()) {
0059                 continue;
0060             }
0061             const auto symlinkTarget = std::filesystem::read_symlink(fdEntry, error);
0062             if (error) {
0063                 continue;
0064             }
0065             if (symlinkTarget != u"anon_inode:inotify") {
0066                 continue;
0067             }
0068             ++entry.instances;
0069 
0070             const QString fdinfoPath = QString::fromStdString(procEntry.path() / u"fdinfo" / fdEntry.path().filename());
0071             if (QFile fdinfo(fdinfoPath); fdinfo.open(QFile::ReadOnly)) {
0072                 // This being a pseudo filesystem we want to read this all at once as canReadLine and friends will
0073                 // not necessarily work correctly.
0074                 const QList<QByteArray> lines = fdinfo.readAll().split('\n');
0075                 for (const auto &line : lines) {
0076                     if (line.startsWith(QByteArrayLiteral("inotify "))) {
0077                         ++entry.watches;
0078                     }
0079                 }
0080             } else {
0081                 qWarning() << "Failed to open fdinfo" << fdinfoPath;
0082                 continue;
0083             }
0084         }
0085         if (error) {
0086             continue;
0087         }
0088         if (entry.instances <= 0) {
0089             continue;
0090         }
0091 
0092         const QString cmdlinePath = QString::fromStdString(procEntry.path() / u"cmdline");
0093         if (QFile cmdline(cmdlinePath); cmdline.open(QFile::ReadOnly)) {
0094             entry.cmdline = cmdline.readAll().simplified();
0095             static constexpr auto limit = 70;
0096             static constexpr auto limitWithEllipsis = 70 + 3;
0097             if (entry.cmdline.size() > limitWithEllipsis) {
0098                 entry.cmdline = entry.cmdline.left(limit) + QLatin1String("...");
0099             }
0100         }
0101 
0102         ret.push_back(entry);
0103     }
0104     return ret;
0105 }
0106 
0107 INotifyCapacity inotifyCapacity(const std::string_view &procPath)
0108 {
0109     return {
0110         .max_user_instances = procULongLong(QString::fromStdU16String(max_user_instances_path(procPath).u16string())),
0111         .max_user_watches = procULongLong(QString::fromStdU16String(max_user_watches_path(procPath).u16string())),
0112     };
0113 }
0114 
0115 INotifyCapacity maximumINotifyCapacity()
0116 {
0117     return {
0118         .max_user_instances = kernelMaxWatches / kernelDefaultInstances,
0119         .max_user_watches = kernelMaxWatches,
0120     };
0121 }
0122 
0123 qulonglong instanceStepSize()
0124 {
0125     return kernelDefaultInstances;
0126 }
0127 
0128 qulonglong watchStepSize()
0129 {
0130     return kernelDefaultWatches;
0131 }
0132 
0133 std::filesystem::path max_user_instances_path(const std::string_view &procPath)
0134 {
0135     return std::filesystem::path(procPath) / std::filesystem::path("sys/fs/inotify/max_user_instances");
0136 }
0137 
0138 std::filesystem::path max_user_watches_path(const std::string_view &procPath)
0139 {
0140     return std::filesystem::path(procPath) / std::filesystem::path("sys/fs/inotify/max_user_watches");
0141 }