Program Listing for File rtkitcodes.hpp

Return to documentation for file (include\input\DefaultDevs\linux\rtkitcodes.hpp)

#pragma once
#include <cerrno>
#include <cstdint>

#include <stdexcept>
#include <string>
#include <sys/syscall.h>
#include <unistd.h>

#include <systemd/sd-bus.h>

namespace rtkit {

static inline uint64_t
get_tid()
{
    const long tid = syscall(SYS_gettid);
    if (tid < 0) {
        throw std::runtime_error("SYS_gettid failed: " + std::to_string(errno));
    }
    return static_cast<uint64_t>(tid);
}

static inline void
throw_bus_err(const char *what, int r, sd_bus_error *err)
{
    std::string msg = what;
    msg += " (";
    msg += std::to_string(r);
    msg += ")";
    if (err && sd_bus_error_is_set(err)) {
        msg += ": ";
        msg += err->message ? err->message : "(no message)";
    }
    if (err) {
        sd_bus_error_free(err);
    }
    throw std::runtime_error(msg);
}

static inline int32_t
get_int32_property(sd_bus *bus, const char *property_name)
{
    sd_bus_error    err   = SD_BUS_ERROR_NULL;
    sd_bus_message *reply = nullptr;

    int r = sd_bus_call_method(bus,
                               "org.freedesktop.RealtimeKit1",
                               "/org/freedesktop/RealtimeKit1",
                               "org.freedesktop.DBus.Properties",
                               "Get",
                               &err,
                               &reply,
                               "ss",
                               "org.freedesktop.RealtimeKit1",
                               property_name);
    if (r < 0) {
        if (reply) {
            sd_bus_message_unref(reply);
        }
        throw_bus_err("Properties.Get failed", r, &err);
    }

    int32_t value = 0;
    r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, "i");
    if (r < 0) {
        sd_bus_error_free(&err);
        sd_bus_message_unref(reply);
        throw std::runtime_error(std::string("Failed to enter variant for ") +
                                 property_name);
    }

    r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_INT32, &value);
    if (r < 0) {
        sd_bus_error_free(&err);
        sd_bus_message_unref(reply);
        throw std::runtime_error(
            std::string("Failed to read property value for ") + property_name);
    }

    r = sd_bus_message_exit_container(reply);
    if (r < 0) {
        sd_bus_error_free(&err);
        sd_bus_message_unref(reply);
        throw std::runtime_error(std::string("Failed to exit variant for ") +
                                 property_name);
    }

    sd_bus_error_free(&err);
    sd_bus_message_unref(reply);
    return value;
}

static inline uint32_t
get_max_realtime_priority_legacy_method(sd_bus *bus)
{
    sd_bus_error    err   = SD_BUS_ERROR_NULL;
    sd_bus_message *reply = nullptr;

    int r = sd_bus_call_method(bus,
                               "org.freedesktop.RealtimeKit1",
                               "/org/freedesktop/RealtimeKit1",
                               "org.freedesktop.RealtimeKit1",
                               "GetMaxRealtimePriority",
                               &err,
                               &reply,
                               "");
    if (r < 0) {
        if (reply) {
            sd_bus_message_unref(reply);
        }
        throw_bus_err("GetMaxRealtimePriority failed", r, &err);
    }

    uint32_t prio = 0;
    r             = sd_bus_message_read(reply, "u", &prio);
    sd_bus_message_unref(reply);
    if (r < 0)
        throw std::runtime_error(
            "Failed to parse GetMaxRealtimePriority reply");
    return prio;
}

static inline int32_t
get_min_nice_level_legacy_method(sd_bus *bus)
{
    sd_bus_error    err   = SD_BUS_ERROR_NULL;
    sd_bus_message *reply = nullptr;

    int r = sd_bus_call_method(bus,
                               "org.freedesktop.RealtimeKit1",
                               "/org/freedesktop/RealtimeKit1",
                               "org.freedesktop.RealtimeKit1",
                               "GetMinNiceLevel",
                               &err,
                               &reply,
                               "");
    if (r < 0) {
        if (reply) {
            sd_bus_message_unref(reply);
        }
        throw_bus_err("GetMinNiceLevel failed", r, &err);
    }

    int32_t nicev = 0;
    r             = sd_bus_message_read(reply, "i", &nicev);
    sd_bus_message_unref(reply);
    if (r < 0)
        throw std::runtime_error("Failed to parse GetMinNiceLevel reply");
    return nicev;
}

// Query helpers (optional)
static inline uint32_t
get_max_realtime_priority(sd_bus *bus)
{
    try {
        const int32_t property_prio =
            get_int32_property(bus, "MaxRealtimePriority");
        if (property_prio <= 0) {
            throw std::runtime_error(
                "MaxRealtimePriority property returned non-positive value");
        }
        return static_cast<uint32_t>(property_prio);
    } catch (const std::exception &prop_err) {
        try {
            const uint32_t method_prio =
                get_max_realtime_priority_legacy_method(bus);
            if (method_prio == 0) {
                throw std::runtime_error(
                    "GetMaxRealtimePriority returned zero value");
            }
            return method_prio;
        } catch (const std::exception &legacy_err) {
            throw std::runtime_error(
                std::string("Failed to query MaxRealtimePriority. property: ") +
                prop_err.what() + "; legacy method: " + legacy_err.what());
        }
    }
}

static inline int32_t
get_min_nice_level(sd_bus *bus)
{
    try {
        return get_int32_property(bus, "MinNiceLevel");
    } catch (const std::exception &prop_err) {
        try {
            return get_min_nice_level_legacy_method(bus);
        } catch (const std::exception &legacy_err) {
            throw std::runtime_error(
                std::string("Failed to query MinNiceLevel. property: ") +
                prop_err.what() + "; legacy method: " + legacy_err.what());
        }
    }
}

// Make current thread SCHED_FIFO realtime with given priority (1..99, but rtkit
// caps it)

static inline void
make_current_thread_realtime(uint32_t priority)
{
    sd_bus         *bus   = nullptr;
    sd_bus_error    err   = SD_BUS_ERROR_NULL;
    sd_bus_message *reply = nullptr;

    int r = sd_bus_open_system(&bus);
    if (r < 0)
        throw std::runtime_error("sd_bus_open_system failed: " +
                                 std::to_string(r));
    if (priority == 0) {
        priority = 1;
    }

    try {
        const uint64_t tid = get_tid();

        // optional: clamp to rtkit max
        try {
            const uint32_t max_prio = get_max_realtime_priority(bus);
            if (priority > max_prio)
                priority = max_prio;
        } catch (const std::exception &) {
        }

        r = sd_bus_call_method(
            bus,
            "org.freedesktop.RealtimeKit1",
            "/org/freedesktop/RealtimeKit1",
            "org.freedesktop.RealtimeKit1",
            "MakeThreadRealtime",
            &err,
            &reply,
            "tu", // t = uint64 (thread id), u = uint32 (priority)
            tid,
            priority);

        if (r < 0) {
            throw_bus_err("MakeThreadRealtime failed", r, &err);
        }
    } catch (...) {
        if (reply) {
            sd_bus_message_unref(reply);
        }
        if (bus) {
            sd_bus_unref(bus);
        }
        throw;
    }

    if (reply) {
        sd_bus_message_unref(reply);
    }
    if (bus) {
        sd_bus_unref(bus);
    }
}

// Make current thread "high priority" by lowering nice (negative is higher
// priority). rtkit enforces a minimum nice level; values below that will be
// denied.
static inline void
make_current_thread_high_priority(int32_t nice_level)
{
    sd_bus         *bus   = nullptr;
    sd_bus_error    err   = SD_BUS_ERROR_NULL;
    sd_bus_message *reply = nullptr;

    int r = sd_bus_open_system(&bus);
    if (r < 0)
        throw std::runtime_error("sd_bus_open_system failed: " +
                                 std::to_string(r));

    try {
        const uint64_t tid = get_tid();

        // optional: clamp to rtkit min nice
        try {
            const int32_t min_nice = get_min_nice_level(bus);
            if (nice_level < min_nice)
                nice_level = min_nice;
        } catch (const std::exception &) {
            // query failure is non-fatal; try with provided level
        }

        r = sd_bus_call_method(
            bus,
            "org.freedesktop.RealtimeKit1",
            "/org/freedesktop/RealtimeKit1",
            "org.freedesktop.RealtimeKit1",
            "MakeThreadHighPriority",
            &err,
            &reply,
            "ti", // t = uint64 (thread id), i = int32 (nice level)
            tid,
            nice_level);

        if (r < 0) {
            throw_bus_err("MakeThreadHighPriority failed", r, &err);
        }
    } catch (...) {
        if (reply) {
            sd_bus_message_unref(reply);
        }
        if (bus) {
            sd_bus_unref(bus);
        }
        throw;
    }

    if (reply) {
        sd_bus_message_unref(reply);
    }
    if (bus) {
        sd_bus_unref(bus);
    }
}

} // namespace rtkit