diff --git a/meson.build b/meson.build index 518645c..368b5ea 100644 --- a/meson.build +++ b/meson.build @@ -90,6 +90,7 @@ base_sources = files('src/core/system_data.cpp', 'src/os/shared.cpp', 'src/confi platform_sources = { 'linux' : ['src/os/linux.cpp', 'src/os/linux/pkg_count.cpp'], 'freebsd' : ['src/os/bsd.cpp'], + 'netbsd' : ['src/os/bsd.cpp'], 'dragonfly' : ['src/os/bsd.cpp'], 'darwin' : ['src/os/macos.cpp', 'src/os/macos/bridge.mm'], 'windows' : ['src/os/windows.cpp'], diff --git a/src/os/bsd.cpp b/src/os/bsd.cpp index 3478190..18c5cae 100644 --- a/src/os/bsd.cpp +++ b/src/os/bsd.cpp @@ -1,19 +1,22 @@ -#if defined(__FreeBSD__) || defined(__DragonFly__) +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) // clang-format off #include // DBUS_TYPE_* #include // DBUS_BUS_SESSION #include // ifstream -#include // kenv #include // ucred, getsockopt, SOL_SOCKET, SO_PEERCRED #include // statvfs #include // sysctlbyname #include -#include #include #include // utsname, uname #include +#if defined(__FreeBSD__) || defined(__DragonFly__) +#include // kenv +#include // xucred +#endif + #include "src/util/defs.hpp" #include "src/util/error.hpp" #include "src/util/helpers.hpp" @@ -30,6 +33,32 @@ using namespace util::types; using util::error::DracError, util::error::DracErrorCode; namespace { + fn GetPathByPid(pid_t pid) -> Result { + Array exePathBuf; + usize size = exePathBuf.size(); + int mib[4]; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC_ARGS; // Use KERN_PROC_ARGS which includes path + mib[2] = pid; + mib[3] = KERN_PROC_PATHNAME; // The specific subcommand + + if (sysctl(mib, 4, exePathBuf.data(), &size, nullptr, 0) == -1) { + // Fallback for older systems or if KERN_PROC_PATHNAME is restricted/unavailable + // Note: KERN_PROC_ARGS might give args too, need parsing. + // For simplicity, we'll just report the error for now. + return Err(DracError::withErrno(std::format("sysctl KERN_PROC_PATHNAME failed for pid {}", pid))); + } + + if (size == 0 || exePathBuf[0] == '\0') { + return Err(DracError(DracErrorCode::NotFound, std::format("sysctl KERN_PROC_PATHNAME returned empty path for pid {}", pid))); + } + + exePathBuf[std::min(size, exePathBuf.size() - 1)] = '\0'; + + return String(exePathBuf.data()); + } + fn GetX11WindowManager() -> Result { using namespace xcb; @@ -119,64 +148,64 @@ namespace { if (fileDescriptor < 0) return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to get Wayland file descriptor")); - xucred cred; + pid_t peer_pid = -1; // Initialize PID + +#if defined(__FreeBSD__) || defined(__DragonFly__) + struct xucred cred; // Use xucred on FreeBSD/DragonFly socklen_t len = sizeof(cred); + if (getsockopt(fileDescriptor, SOL_SOCKET, LOCAL_PEERCRED, &cred, &len) == -1) { + return Err(DracError::withErrno("Failed to get socket credentials (LOCAL_PEERCRED)")); + } + if (len != sizeof(cred) || cred.cr_version != XUCRED_VERSION) { + return Err(DracError(DracErrorCode::PlatformSpecific, "Invalid xucred structure received")); + } + peer_pid = cred.cr_pid; // <<< Get PID from xucred - if (getsockopt(fileDescriptor, SOL_SOCKET, LOCAL_PEERCRED, &cred, &len) == -1) - return Err(DracError::withErrno("Failed to get socket credentials (SO_PEERCRED)")); - - Array exeLinkPathBuf; - - const isize count = exeLinkPathBuf.size() - 1; - - std::format_to_n_result result = std::format_to_n(exeLinkPathBuf.data(), count, "/proc/{}/exe", cred.cr_uid); - - if (result.size >= count) - return Err(DracError(DracErrorCode::InternalError, "Failed to format /proc path (PID too large?)")); - - *result.out = '\0'; - - const char* exeLinkPath = exeLinkPathBuf.data(); - - Array exeRealPathBuf; // NOLINT(misc-include-cleaner) - PATH_MAX is in - - const isize bytesRead = readlink(exeLinkPath, exeRealPathBuf.data(), exeRealPathBuf.size() - 1); - - if (bytesRead == -1) - return Err(DracError::withErrno(std::format("Failed to read link '{}'", exeLinkPath))); - - exeRealPathBuf.at(bytesRead) = '\0'; - - StringView compositorNameView; - - const StringView pathView(exeRealPathBuf.data(), bytesRead); - - StringView filenameView; - - if (const usize lastCharPos = pathView.find_last_not_of('/'); lastCharPos != StringView::npos) { - const StringView relevantPart = pathView.substr(0, lastCharPos + 1); - - if (const usize separatorPos = relevantPart.find_last_of('/'); separatorPos == StringView::npos) - filenameView = relevantPart; - else - filenameView = relevantPart.substr(separatorPos + 1); +#elif defined(__NetBSD__) + uid_t euid; + gid_t egid; + if (getpeereid(fileDescriptor, &euid, &egid) == -1) { + return Err(DracError::withErrno("getpeereid failed on Wayland socket")); } - if (!filenameView.empty()) - compositorNameView = filenameView; + return "Wayland Compositor (Unknown Path)"; +#endif + + if (peer_pid <= 0) { + return Err(DracError(DracErrorCode::PlatformSpecific, "Failed to obtain a valid peer PID")); + } + + // --- Use the helper function to get the path --- + Result exePathResult = GetPathByPid(peer_pid); + if (!exePathResult) { + // Forward the error from GetPathByPid + return Err(std::move(exePathResult).error()); + } + + const String& exeRealPath = *exePathResult; + + // --- Extract filename from path (OS-independent) --- + StringView compositorNameView; + if (const usize lastSlash = exeRealPath.rfind('/'); lastSlash != String::npos) { + compositorNameView = StringView(exeRealPath).substr(lastSlash + 1); + } else { + compositorNameView = exeRealPath; // Path is just the filename + } if (compositorNameView.empty() || compositorNameView == "." || compositorNameView == "/") - return Err(DracError(DracErrorCode::NotFound, "Failed to get compositor name from path")); + return Err(DracError(DracErrorCode::NotFound, "Failed to get compositor name from path")); + + // --- Heuristic cleanup (OS-independent) --- if (constexpr StringView wrappedSuffix = "-wrapped"; compositorNameView.length() > 1 + wrappedSuffix.length() && compositorNameView[0] == '.' && compositorNameView.ends_with(wrappedSuffix)) { - const StringView cleanedView = - compositorNameView.substr(1, compositorNameView.length() - 1 - wrappedSuffix.length()); + const StringView cleanedView = + compositorNameView.substr(1, compositorNameView.length() - 1 - wrappedSuffix.length()); - if (cleanedView.empty()) - return Err(DracError(DracErrorCode::NotFound, "Compositor name invalid after heuristic")); + if (cleanedView.empty()) + return Err(DracError(DracErrorCode::NotFound, "Compositor name invalid after heuristic")); - return String(cleanedView); + return String(cleanedView); } return String(compositorNameView); @@ -191,37 +220,44 @@ namespace os { std::ifstream file(path); - if (!file) - return Err(DracError(DracErrorCode::NotFound, std::format("Failed to open {}", path))); + if (file) { + String line; + constexpr StringView prefix = "NAME="; - String line; - constexpr StringView prefix = "NAME="; + while (std::getline(file, line)) { + if (StringView(line).starts_with(prefix)) { + String value = line.substr(prefix.size()); - while (std::getline(file, line)) { - if (StringView(line).starts_with(prefix)) { - String value = line.substr(prefix.size()); + if ((value.length() >= 2 && value.front() == '"' && value.back() == '"') || + (value.length() >= 2 && value.front() == '\'' && value.back() == '\'')) + value = value.substr(1, value.length() - 2); - if ((value.length() >= 2 && value.front() == '"' && value.back() == '"') || - (value.length() >= 2 && value.front() == '\'' && value.back() == '\'')) - value = value.substr(1, value.length() - 2); - - if (value.empty()) - return Err( - DracError(DracErrorCode::ParseError, std::format("PRETTY_NAME value is empty or only quotes in {}", path)) - ); - - return value; + return value; + } } } - return Err(DracError(DracErrorCode::NotFound, std::format("PRETTY_NAME line not found in {}", path))); + utsname uts; + if (uname(&uts) == -1) + return Err(DracError::withErrno(std::format("Failed to open {} and uname() call also failed", path))); + + String osName = uts.sysname; + + if (osName.empty()) + return Err(DracError(DracErrorCode::ParseError,"uname() returned empty sysname or release")); + + return osName; } fn GetMemInfo() -> Result { u64 mem = 0; usize size = sizeof(mem); +#ifdef __NetBSD__ + sysctlbyname("hw.physmem64", &mem, &size, nullptr, 0); +#else sysctlbyname("hw.physmem", &mem, &size, nullptr, 0); +#endif return mem; } @@ -407,18 +443,43 @@ namespace os { } fn GetHost() -> Result { - auto getKenv = [](const char* name) -> Result { - Array buffer {}; - int result = kenv(KENV_GET, name, buffer.data(), sizeof(buffer)); + Array buffer {}; + usize size = buffer.size(); - if (result == -1) - return Err(DracError(DracErrorCode::NotFound, std::format("Environment variable '{}' not found", name))); +#if defined(__FreeBSD__) || defined(__DragonFly__) + // Use kenv on FreeBSD / DragonFly + int result = kenv(KENV_GET, "smbios.system.product", buffer.data(), buffer.size() -1); // Ensure space for null + if (result == -1) { + // Fallback: Maybe try hw.model? + if (sysctlbyname("hw.model", buffer.data(), &size, nullptr, 0) == -1) { + return Err(DracError::withErrno("kenv smbios.system.product failed and sysctl hw.model also failed")); + } + buffer[std::min(size, buffer.size() - 1)] = '\0'; // Null terminate sysctl result + return String(buffer.data()); + } + // kenv should null-terminate, but let's be safe if result > 0 (bytes written) + if (result > 0) buffer[result] = '\0'; + else buffer[0] = '\0'; // Ensure null if result was 0 (empty string?) - return std::string(buffer.data(), buffer.size()); - }; +#elif defined(__NetBSD__) + // Use sysctl on NetBSD + constexpr const char* netbsd_mib = "machdep.dmi.system-product"; + if (sysctlbyname(netbsd_mib, buffer.data(), &size, nullptr, 0) == -1) { + // Fallback: Try hw.model on NetBSD too + size = buffer.size(); // reset size for next attempt + if (sysctlbyname("hw.model", buffer.data(), &size, nullptr, 0) == -1) { + return Err(DracError::withErrno(std::format("sysctlbyname failed for both {} and hw.model", netbsd_mib))); + } + } + // Ensure null termination for sysctl results + buffer[std::min(size, buffer.size() - 1)] = '\0'; +#endif + // Ensure the buffer isn't empty before returning + if(buffer[0] == '\0') { + return Err(DracError(DracErrorCode::NotFound, "Failed to get host product information (empty result)")); + } - return getKenv("smbios.system.product"); - } + return String(buffer.data());} fn GetKernelVersion() -> Result { utsname uts;