| /* |
| 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_open_user sd_bus_open_user |
| #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, |
| .supportedModes = (1 << TEXT_METERMODE), |
| .maxItems = 0, |
| .total = 0.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, |
| .supportedModes = (1 << TEXT_METERMODE), |
| .maxItems = 0, |
| .total = 0.0, |
| .attributes = SystemdMeter_attributes, |
| .name = "SystemdUser", |
| .uiName = "Systemd user state", |
| .description = "Systemd user state and unit overview", |
| .caption = "Systemd User: ", |
| }; |