Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.16)
project(RemoteDiscHealthMonitor VERSION 0.2.0)

set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_STANDARD_REQUIRED OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 23)

option(BUILD_AGENT "Build the agent component" ON)
Expand Down Expand Up @@ -37,7 +37,16 @@ set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
add_subdirectory(external/googletest EXCLUDE_FROM_ALL)

if(BUILD_MONITOR)
add_subdirectory(external/QtZeroConf)
add_subdirectory(external/QtZeroConf EXCLUDE_FROM_ALL)
set_target_properties(QtZeroConf PROPERTIES
EXCLUDE_FROM_ALL FALSE
PUBLIC_HEADER ""
)

install(TARGETS QtZeroConf
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

set(CppRestAPI_QtBackend ON CACHE BOOL "" FORCE)
set(CppRestAPI_GitHub OFF CACHE BOOL "" FORCE)
Expand Down
22 changes: 14 additions & 8 deletions packaging/arch/rdhm-agent/rdhm-agent.install
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
run_systemctl() {
if command -v systemctl >/dev/null 2>&1; then
systemctl "$@" || true
fi
}

post_install() {
systemctl daemon-reload
systemctl enable rdhm-agent.service
run_systemctl daemon-reload
run_systemctl enable rdhm-agent.service
echo ">>> Enable and start the agent with: systemctl start rdhm-agent"
}

pre_upgrade() {
systemctl stop rdhm-agent.service || true
run_systemctl stop rdhm-agent.service
}

post_upgrade() {
systemctl daemon-reload
systemctl restart rdhm-agent.service || true
run_systemctl daemon-reload
run_systemctl restart rdhm-agent.service
}

pre_remove() {
systemctl stop rdhm-agent.service || true
systemctl disable rdhm-agent.service || true
run_systemctl stop rdhm-agent.service
run_systemctl disable rdhm-agent.service
}

post_remove() {
systemctl daemon-reload
run_systemctl daemon-reload
}
10 changes: 8 additions & 2 deletions packaging/deb/rdhm-agent/debian/rdhm-agent.postinst
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#!/bin/sh
set -e

run_systemctl() {
if command -v systemctl >/dev/null 2>&1; then
systemctl "$@" || true
fi
}

if [ "$1" = "configure" ]; then
systemctl daemon-reload
systemctl enable rdhm-agent.service
run_systemctl daemon-reload
run_systemctl enable rdhm-agent.service
fi
10 changes: 8 additions & 2 deletions packaging/deb/rdhm-agent/debian/rdhm-agent.prerm
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#!/bin/sh
set -e

run_systemctl() {
if command -v systemctl >/dev/null 2>&1; then
systemctl "$@" || true
fi
}

if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then
systemctl stop rdhm-agent.service || true
systemctl disable rdhm-agent.service || true
run_systemctl stop rdhm-agent.service
run_systemctl disable rdhm-agent.service
fi
2 changes: 0 additions & 2 deletions packaging/rpm/rdhm-monitor.spec
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ network via mDNS/ZeroConf and displays disk health status in real time.
%license LICENSE
%{_bindir}/rdhm-monitor
%{_libdir}/libQtZeroConf.so*
%exclude %{_includedir}/QtZeroConf
%exclude %{_libdir}/cmake/QtZeroConf

%changelog
* Sat Mar 14 2026 Michał Walenciak <michalwalenciak@gmail.com> - 0.2.0-1
Expand Down
1 change: 1 addition & 0 deletions packaging/systemd/rdhm-agent.service
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ After=network.target
[Service]
Type=simple
EnvironmentFile=/etc/rdhm/agent.conf
RuntimeDirectory=rdhm
ExecStart=/usr/sbin/rdhm-agent -n ${AGENT_NAME}
Restart=on-failure
RestartSec=5
Expand Down
4 changes: 4 additions & 0 deletions src/agent/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ ADD_EXECUTABLE( agent
target_include_directories(agent
PRIVATE
${PROJECT_SOURCE_DIR}/src
)

target_include_directories(agent
SYSTEM PRIVATE
${PROJECT_SOURCE_DIR}/external/cpp-httplib
${PROJECT_SOURCE_DIR}/external/nlohmann-json/single_include
${PROJECT_SOURCE_DIR}/external/mdns
Expand Down
7 changes: 4 additions & 3 deletions src/agent/Disk.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Disk.h"

#include <algorithm>
#include "Disk.h"

#include <algorithm>
#include <cctype>

Disk::Disk(const std::string& _deviceId)
: m_deviceId(_deviceId)
Expand Down
14 changes: 8 additions & 6 deletions src/agent/HttpServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <atomic>
#include <algorithm>
#include <chrono>
#include <cctype>
#include <iomanip>
#include <list>
#include <condition_variable>
Expand All @@ -21,9 +22,9 @@ namespace
{
bool isValidDiskName(const std::string& name)
{
return !name.empty() && name.size() <= 64 &&
return !name.empty() && name.size() <= 128 &&
std::ranges::all_of(name, [](char c) {
return std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_';
return std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '\\' || c == ':';
});
}
}
Expand All @@ -45,7 +46,8 @@ struct HttpServer::Impl

// Refresh cooldown
static constexpr int RefreshCooldownSeconds = 600; // 10 minutes
std::chrono::steady_clock::time_point lastRefreshTime{};
std::chrono::steady_clock::time_point lastRefreshTime =
std::chrono::steady_clock::now() - std::chrono::seconds{RefreshCooldownSeconds};
std::mutex refreshMutex;

// SSE support
Expand Down Expand Up @@ -237,12 +239,12 @@ void HttpServer::setStatusData(GeneralHealth::Health overallHealth, std::vector<
m_impl->disks = std::move(disks);

const auto now = std::chrono::system_clock::now();
const auto time_t = std::chrono::system_clock::to_time_t(now);
const auto nowTime = std::chrono::system_clock::to_time_t(now);
std::tm tm_buf{};
#ifdef _WIN32
gmtime_s(&tm_buf, &time_t);
gmtime_s(&tm_buf, &nowTime);
#else
gmtime_r(&time_t, &tm_buf);
gmtime_r(&nowTime, &tm_buf);
#endif
std::ostringstream oss;
oss << std::put_time(&tm_buf, "%Y-%m-%dT%H:%M:%SZ");
Expand Down
43 changes: 22 additions & 21 deletions src/agent/MdnsNetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ timeval toTimeval(std::chrono::microseconds timeout)
const auto microseconds = timeout - seconds;

timeval result{};
result.tv_sec = static_cast<decltype(result.tv_sec)>(seconds.count());
result.tv_usec = static_cast<decltype(result.tv_usec)>(microseconds.count());
result.tv_sec = seconds.count();
result.tv_usec = microseconds.count();
return result;
}

Expand Down Expand Up @@ -189,29 +189,30 @@ std::vector<int> openMdnsServiceSockets()
std::vector<int> waitForReadableSockets(const std::vector<int>& sockets,
std::chrono::microseconds timeout)
{
if (sockets.empty())
return {};
std::vector<int> ready;

fd_set readable;
FD_ZERO(&readable);
if (!sockets.empty())
{
fd_set readable;
FD_ZERO(&readable);

int nfds = 0;
for (int socket : sockets) {
if (socket >= nfds)
nfds = socket + 1;
FD_SET(socket, &readable);
}
int nfds = 0;
for (int socket : sockets) {
if (socket >= nfds)
nfds = socket + 1;
FD_SET(socket, &readable);
}

timeval tv = toTimeval(timeout);
if (select(nfds, &readable, nullptr, nullptr, &tv) <= 0)
return {};
timeval tv = toTimeval(timeout);
if (select(nfds, &readable, nullptr, nullptr, &tv) > 0)
{
ready.reserve(sockets.size());

std::vector<int> ready;
ready.reserve(sockets.size());

for (int socket : sockets) {
if (FD_ISSET(socket, &readable))
ready.push_back(socket);
for (int socket : sockets) {
if (FD_ISSET(socket, &readable))
ready.push_back(socket);
}
}
}

return ready;
Expand Down
54 changes: 28 additions & 26 deletions src/agent/SmartHealthAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,37 +146,39 @@ GeneralHealth::Health SmartHealthAnalyzer::GetStatus(const Disk& disk) const

nlohmann::json SmartHealthAnalyzer::GetRawData(const Disk& disk) const
{
auto it = m_cachedSmartData.find(disk.GetDeviceId());
if (it == m_cachedSmartData.end())
return nlohmann::json{{"type", "smart"}, {"attributes", nlohmann::json::array()}};

const auto& smart = it->second;
auto j = nlohmann::json{{"type", "smart"}, {"attributes", nlohmann::json::array()}};

nlohmann::json attrs = nlohmann::json::array();
for (const auto& attr : smart.attributes)
auto it = m_cachedSmartData.find(disk.GetDeviceId());
if (it != m_cachedSmartData.end())
{
attrs.push_back({
{"id", attr.id},
{"name", attr.name},
{"value", attr.value},
{"worst", attr.worst},
{"threshold", attr.threshold},
{"rawVal", attr.rawVal}
});
}
const auto& smart = it->second;

nlohmann::json attrs = nlohmann::json::array();
for (const auto& attr : smart.attributes)
{
attrs.push_back({
{"id", attr.id},
{"name", attr.name},
{"value", attr.value},
{"worst", attr.worst},
{"threshold", attr.threshold},
{"rawVal", attr.rawVal}
});
}

auto j = nlohmann::json{{"type", "smart"}, {"attributes", attrs}};
j["attributes"] = attrs;

auto testIt = m_cachedTestStatus.find(disk.GetDeviceId());
SmartTestStatus testStatus;
if (testIt != m_cachedTestStatus.end())
testStatus = testIt->second;
auto testIt = m_cachedTestStatus.find(disk.GetDeviceId());
SmartTestStatus testStatus;
if (testIt != m_cachedTestStatus.end())
testStatus = testIt->second;

j["selfTestStatus"] = {
{"running", testStatus.running},
{"percentRemaining", testStatus.percentRemaining},
{"lastResult", testStatus.lastResult}
};
j["selfTestStatus"] = {
{"running", testStatus.running},
{"percentRemaining", testStatus.percentRemaining},
{"lastResult", testStatus.lastResult}
};
}

return j;
}
21 changes: 16 additions & 5 deletions src/agent/linux/DmesgParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,23 @@
namespace
{
const std::vector<std::regex> ErrorPatterns = {
std::regex("Buffer I/O error on device ([a-z0-9]*)")
std::regex(R"(Buffer I/O error on (?:dev(?:ice)? )?([A-Za-z0-9_.-]+))"),
std::regex(R"((?:I/O error|critical medium error|medium error|uncorrectable error).*dev ([A-Za-z0-9_.-]+))"),
std::regex(R"(EXT[234]-fs error.*\(device ([A-Za-z0-9_.-]+)\))")
};
}

std::string physicalDeviceFor(const std::string& deviceName,
const IPartitionsManager& partitionsManager)
{
if (partitionsManager.isPartition(deviceName))
{
const auto physicalDevice = partitionsManager.diskForPartition(deviceName);
return physicalDevice.empty() ? deviceName : physicalDevice;
}
else
return deviceName;
}
}


std::map<Disk, std::set<std::string>> DmesgParser::parse(const std::string& output, const IPartitionsManager& paritionsManager)
Expand All @@ -29,9 +42,7 @@ std::map<Disk, std::set<std::string>> DmesgParser::parse(const std::string& outp
if (std::regex_search(line, errorMatch, errorRegex))
{
const std::string dev = errorMatch[1].str();
const std::string physicalDev = paritionsManager.isPartition(dev)?
paritionsManager.diskForPartition(dev):
dev;
const std::string physicalDev = physicalDeviceFor(dev, paritionsManager);

const Disk disk(physicalDev);

Expand Down
Loading
Loading