diff options
Diffstat (limited to 'linux/SystemdMeter.c')
-rw-r--r-- | linux/SystemdMeter.c | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c new file mode 100644 index 0000000..e13c646 --- /dev/null +++ b/linux/SystemdMeter.c @@ -0,0 +1,435 @@ +/* +htop - SystemdMeter.c +(C) 2020 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "linux/SystemdMeter.h" + +#include <dlfcn.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> + +#include "CRT.h" +#include "Macros.h" +#include "Object.h" +#include "RichString.h" +#include "Settings.h" +#include "XUtils.h" + +#if defined(BUILD_STATIC) && defined(HAVE_LIBSYSTEMD) +#include <systemd/sd-bus.h> +#endif + + +#ifdef BUILD_STATIC + +#define sym_sd_bus_open_system sd_bus_open_system +#define sym_sd_bus_get_property_string sd_bus_get_property_string +#define sym_sd_bus_get_property_trivial sd_bus_get_property_trivial +#define sym_sd_bus_unref sd_bus_unref + +#else + +typedef void sd_bus; +typedef void sd_bus_error; +static int (*sym_sd_bus_open_system)(sd_bus**); +static int (*sym_sd_bus_open_user)(sd_bus**); +static int (*sym_sd_bus_get_property_string)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char**); +static int (*sym_sd_bus_get_property_trivial)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char, void*); +static sd_bus* (*sym_sd_bus_unref)(sd_bus*); +static void* dlopenHandle = NULL; + +#endif /* BUILD_STATIC */ + + +#define INVALID_VALUE ((unsigned int)-1) + +typedef struct SystemdMeterContext { +#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD) + sd_bus* bus; +#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */ + char* systemState; + unsigned int nFailedUnits; + unsigned int nInstalledJobs; + unsigned int nNames; + unsigned int nJobs; +} SystemdMeterContext_t; + +static SystemdMeterContext_t ctx_system; +static SystemdMeterContext_t ctx_user; + +static void SystemdMeter_done(ATTR_UNUSED Meter* this) { + SystemdMeterContext_t* ctx = String_eq(Meter_name(this), "SystemdUser") ? &ctx_user : &ctx_system; + + free(ctx->systemState); + ctx->systemState = NULL; + +#ifdef BUILD_STATIC +# ifdef HAVE_LIBSYSTEMD + if (ctx->bus) { + sym_sd_bus_unref(ctx->bus); + } + ctx->bus = NULL; +# endif /* HAVE_LIBSYSTEMD */ +#else /* BUILD_STATIC */ + if (ctx->bus && dlopenHandle) { + sym_sd_bus_unref(ctx->bus); + } + ctx->bus = NULL; + + if (!ctx_system.systemState && !ctx_user.systemState && dlopenHandle) { + dlclose(dlopenHandle); + dlopenHandle = NULL; + } +#endif /* BUILD_STATIC */ +} + +#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD) +static int updateViaLib(bool user) { + SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system; +#ifndef BUILD_STATIC + if (!dlopenHandle) { + dlopenHandle = dlopen("libsystemd.so.0", RTLD_LAZY); + if (!dlopenHandle) + goto dlfailure; + + /* Clear any errors */ + dlerror(); + + #define resolve(symbolname) do { \ + *(void **)(&sym_##symbolname) = dlsym(dlopenHandle, #symbolname); \ + if (!sym_##symbolname || dlerror() != NULL) \ + goto dlfailure; \ + } while(0) + + resolve(sd_bus_open_system); + resolve(sd_bus_open_user); + resolve(sd_bus_get_property_string); + resolve(sd_bus_get_property_trivial); + resolve(sd_bus_unref); + + #undef resolve + } +#endif /* !BUILD_STATIC */ + + int r; + /* Connect to the system bus */ + if (!ctx->bus) { + if (user) { + r = sym_sd_bus_open_user(&ctx->bus); + } else { + r = sym_sd_bus_open_system(&ctx->bus); + } + if (r < 0) + goto busfailure; + } + + static const char* const busServiceName = "org.freedesktop.systemd1"; + static const char* const busObjectPath = "/org/freedesktop/systemd1"; + static const char* const busInterfaceName = "org.freedesktop.systemd1.Manager"; + + r = sym_sd_bus_get_property_string(ctx->bus, + busServiceName, /* service to contact */ + busObjectPath, /* object path */ + busInterfaceName, /* interface name */ + "SystemState", /* property name */ + NULL, /* object to return error in */ + &ctx->systemState); + if (r < 0) + goto busfailure; + + r = sym_sd_bus_get_property_trivial(ctx->bus, + busServiceName, /* service to contact */ + busObjectPath, /* object path */ + busInterfaceName, /* interface name */ + "NFailedUnits", /* property name */ + NULL, /* object to return error in */ + 'u', /* property type */ + &ctx->nFailedUnits); + if (r < 0) + goto busfailure; + + r = sym_sd_bus_get_property_trivial(ctx->bus, + busServiceName, /* service to contact */ + busObjectPath, /* object path */ + busInterfaceName, /* interface name */ + "NInstalledJobs", /* property name */ + NULL, /* object to return error in */ + 'u', /* property type */ + &ctx->nInstalledJobs); + if (r < 0) + goto busfailure; + + r = sym_sd_bus_get_property_trivial(ctx->bus, + busServiceName, /* service to contact */ + busObjectPath, /* object path */ + busInterfaceName, /* interface name */ + "NNames", /* property name */ + NULL, /* object to return error in */ + 'u', /* property type */ + &ctx->nNames); + if (r < 0) + goto busfailure; + + r = sym_sd_bus_get_property_trivial(ctx->bus, + busServiceName, /* service to contact */ + busObjectPath, /* object path */ + busInterfaceName, /* interface name */ + "NJobs", /* property name */ + NULL, /* object to return error in */ + 'u', /* property type */ + &ctx->nJobs); + if (r < 0) + goto busfailure; + + /* success */ + return 0; + +busfailure: + sym_sd_bus_unref(ctx->bus); + ctx->bus = NULL; + return -2; + +#ifndef BUILD_STATIC +dlfailure: + if (dlopenHandle) { + dlclose(dlopenHandle); + dlopenHandle = NULL; + } + return -1; +#endif /* !BUILD_STATIC */ +} +#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */ + +static void updateViaExec(bool user) { + SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system; + + if (Settings_isReadonly()) + return; + + int fdpair[2]; + if (pipe(fdpair) < 0) + return; + + pid_t child = fork(); + if (child < 0) { + close(fdpair[1]); + close(fdpair[0]); + return; + } + + if (child == 0) { + close(fdpair[0]); + dup2(fdpair[1], STDOUT_FILENO); + close(fdpair[1]); + int fdnull = open("/dev/null", O_WRONLY); + if (fdnull < 0) + exit(1); + dup2(fdnull, STDERR_FILENO); + close(fdnull); + // Use of NULL in variadic functions must have a pointer cast. + // The NULL constant is not required by standard to have a pointer type. + execlp( + "systemctl", + "systemctl", + "show", + user ? "--user" : "--system", + "--property=SystemState", + "--property=NFailedUnits", + "--property=NNames", + "--property=NJobs", + "--property=NInstalledJobs", + (char*)NULL); + exit(127); + } + close(fdpair[1]); + + int wstatus; + if (waitpid(child, &wstatus, 0) < 0 || !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { + close(fdpair[0]); + return; + } + + FILE* commandOutput = fdopen(fdpair[0], "r"); + if (!commandOutput) { + close(fdpair[0]); + return; + } + + char lineBuffer[128]; + while (fgets(lineBuffer, sizeof(lineBuffer), commandOutput)) { + if (String_startsWith(lineBuffer, "SystemState=")) { + char* newline = strchr(lineBuffer + strlen("SystemState="), '\n'); + if (newline) { + *newline = '\0'; + } + free_and_xStrdup(&ctx->systemState, lineBuffer + strlen("SystemState=")); + } else if (String_startsWith(lineBuffer, "NFailedUnits=")) { + ctx->nFailedUnits = strtoul(lineBuffer + strlen("NFailedUnits="), NULL, 10); + } else if (String_startsWith(lineBuffer, "NNames=")) { + ctx->nNames = strtoul(lineBuffer + strlen("NNames="), NULL, 10); + } else if (String_startsWith(lineBuffer, "NJobs=")) { + ctx->nJobs = strtoul(lineBuffer + strlen("NJobs="), NULL, 10); + } else if (String_startsWith(lineBuffer, "NInstalledJobs=")) { + ctx->nInstalledJobs = strtoul(lineBuffer + strlen("NInstalledJobs="), NULL, 10); + } + } + + fclose(commandOutput); +} + +static void SystemdMeter_updateValues(Meter* this) { + bool user = String_eq(Meter_name(this), "SystemdUser"); + SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system; + + free(ctx->systemState); + ctx->systemState = NULL; + ctx->nFailedUnits = ctx->nInstalledJobs = ctx->nNames = ctx->nJobs = INVALID_VALUE; + +#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD) + if (updateViaLib(user) < 0) + updateViaExec(user); +#else + updateViaExec(user); +#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */ + + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s", ctx->systemState ? ctx->systemState : "???"); +} + +static int zeroDigitColor(unsigned int value) { + switch (value) { + case 0: + return CRT_colors[METER_VALUE]; + case INVALID_VALUE: + return CRT_colors[METER_VALUE_ERROR]; + default: + return CRT_colors[METER_VALUE_NOTICE]; + } +} + +static int valueDigitColor(unsigned int value) { + switch (value) { + case 0: + return CRT_colors[METER_VALUE_NOTICE]; + case INVALID_VALUE: + return CRT_colors[METER_VALUE_ERROR]; + default: + return CRT_colors[METER_VALUE]; + } +} + + +static void _SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out, SystemdMeterContext_t* ctx) { + char buffer[16]; + int len; + int color = METER_VALUE_ERROR; + + if (ctx->systemState) { + color = String_eq(ctx->systemState, "running") ? METER_VALUE_OK : + String_eq(ctx->systemState, "degraded") ? METER_VALUE_ERROR : METER_VALUE_WARN; + } + RichString_writeAscii(out, CRT_colors[color], ctx->systemState ? ctx->systemState : "N/A"); + + RichString_appendAscii(out, CRT_colors[METER_TEXT], " ("); + + if (ctx->nFailedUnits == INVALID_VALUE) { + buffer[0] = '?'; + buffer[1] = '\0'; + len = 1; + } else { + len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nFailedUnits); + } + RichString_appendnAscii(out, zeroDigitColor(ctx->nFailedUnits), buffer, len); + + RichString_appendAscii(out, CRT_colors[METER_TEXT], "/"); + + if (ctx->nNames == INVALID_VALUE) { + buffer[0] = '?'; + buffer[1] = '\0'; + len = 1; + } else { + len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nNames); + } + RichString_appendnAscii(out, valueDigitColor(ctx->nNames), buffer, len); + + RichString_appendAscii(out, CRT_colors[METER_TEXT], " failed) ("); + + if (ctx->nJobs == INVALID_VALUE) { + buffer[0] = '?'; + buffer[1] = '\0'; + len = 1; + } else { + len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nJobs); + } + RichString_appendnAscii(out, zeroDigitColor(ctx->nJobs), buffer, len); + + RichString_appendAscii(out, CRT_colors[METER_TEXT], "/"); + + if (ctx->nInstalledJobs == INVALID_VALUE) { + buffer[0] = '?'; + buffer[1] = '\0'; + len = 1; + } else { + len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nInstalledJobs); + } + RichString_appendnAscii(out, valueDigitColor(ctx->nInstalledJobs), buffer, len); + + RichString_appendAscii(out, CRT_colors[METER_TEXT], " jobs)"); +} + +static void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { + _SystemdMeter_display(cast, out, &ctx_system); +} + +static void SystemdUserMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { + _SystemdMeter_display(cast, out, &ctx_user); +} + +static const int SystemdMeter_attributes[] = { + METER_VALUE +}; + +const MeterClass SystemdMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = SystemdMeter_display + }, + .updateValues = SystemdMeter_updateValues, + .done = SystemdMeter_done, + .defaultMode = TEXT_METERMODE, + .maxItems = 0, + .total = 100.0, + .attributes = SystemdMeter_attributes, + .name = "Systemd", + .uiName = "Systemd state", + .description = "Systemd system state and unit overview", + .caption = "Systemd: ", +}; + +const MeterClass SystemdUserMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = SystemdUserMeter_display + }, + .updateValues = SystemdMeter_updateValues, + .done = SystemdMeter_done, + .defaultMode = TEXT_METERMODE, + .maxItems = 0, + .total = 100.0, + .attributes = SystemdMeter_attributes, + .name = "SystemdUser", + .uiName = "Systemd user state", + .description = "Systemd user state and unit overview", + .caption = "Systemd User: ", +}; |