SystemdMeter.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. /*
  2. htop - SystemdMeter.c
  3. (C) 2020 htop dev team
  4. Released under the GNU GPLv2+, see the COPYING file
  5. in the source distribution for its full text.
  6. */
  7. #include "config.h" // IWYU pragma: keep
  8. #include "linux/SystemdMeter.h"
  9. #include <dlfcn.h>
  10. #include <fcntl.h>
  11. #include <stdbool.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include <unistd.h>
  16. #include <sys/wait.h>
  17. #include "CRT.h"
  18. #include "Macros.h"
  19. #include "Object.h"
  20. #include "RichString.h"
  21. #include "Settings.h"
  22. #include "XUtils.h"
  23. #if defined(BUILD_STATIC) && defined(HAVE_LIBSYSTEMD)
  24. #include <systemd/sd-bus.h>
  25. #endif
  26. #ifdef BUILD_STATIC
  27. #define sym_sd_bus_open_system sd_bus_open_system
  28. #define sym_sd_bus_open_user sd_bus_open_user
  29. #define sym_sd_bus_get_property_string sd_bus_get_property_string
  30. #define sym_sd_bus_get_property_trivial sd_bus_get_property_trivial
  31. #define sym_sd_bus_unref sd_bus_unref
  32. #else
  33. typedef void sd_bus;
  34. typedef void sd_bus_error;
  35. static int (*sym_sd_bus_open_system)(sd_bus**);
  36. static int (*sym_sd_bus_open_user)(sd_bus**);
  37. static int (*sym_sd_bus_get_property_string)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char**);
  38. static int (*sym_sd_bus_get_property_trivial)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char, void*);
  39. static sd_bus* (*sym_sd_bus_unref)(sd_bus*);
  40. static void* dlopenHandle = NULL;
  41. #endif /* BUILD_STATIC */
  42. #define INVALID_VALUE ((unsigned int)-1)
  43. typedef struct SystemdMeterContext {
  44. #if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
  45. sd_bus* bus;
  46. #endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
  47. char* systemState;
  48. unsigned int nFailedUnits;
  49. unsigned int nInstalledJobs;
  50. unsigned int nNames;
  51. unsigned int nJobs;
  52. } SystemdMeterContext_t;
  53. static SystemdMeterContext_t ctx_system;
  54. static SystemdMeterContext_t ctx_user;
  55. static void SystemdMeter_done(ATTR_UNUSED Meter* this) {
  56. SystemdMeterContext_t* ctx = String_eq(Meter_name(this), "SystemdUser") ? &ctx_user : &ctx_system;
  57. free(ctx->systemState);
  58. ctx->systemState = NULL;
  59. #ifdef BUILD_STATIC
  60. # ifdef HAVE_LIBSYSTEMD
  61. if (ctx->bus) {
  62. sym_sd_bus_unref(ctx->bus);
  63. }
  64. ctx->bus = NULL;
  65. # endif /* HAVE_LIBSYSTEMD */
  66. #else /* BUILD_STATIC */
  67. if (ctx->bus && dlopenHandle) {
  68. sym_sd_bus_unref(ctx->bus);
  69. }
  70. ctx->bus = NULL;
  71. if (!ctx_system.systemState && !ctx_user.systemState && dlopenHandle) {
  72. dlclose(dlopenHandle);
  73. dlopenHandle = NULL;
  74. }
  75. #endif /* BUILD_STATIC */
  76. }
  77. #if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
  78. static int updateViaLib(bool user) {
  79. SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
  80. #ifndef BUILD_STATIC
  81. if (!dlopenHandle) {
  82. dlopenHandle = dlopen("libsystemd.so.0", RTLD_LAZY);
  83. if (!dlopenHandle)
  84. goto dlfailure;
  85. /* Clear any errors */
  86. dlerror();
  87. #define resolve(symbolname) do { \
  88. *(void **)(&sym_##symbolname) = dlsym(dlopenHandle, #symbolname); \
  89. if (!sym_##symbolname || dlerror() != NULL) \
  90. goto dlfailure; \
  91. } while(0)
  92. resolve(sd_bus_open_system);
  93. resolve(sd_bus_open_user);
  94. resolve(sd_bus_get_property_string);
  95. resolve(sd_bus_get_property_trivial);
  96. resolve(sd_bus_unref);
  97. #undef resolve
  98. }
  99. #endif /* !BUILD_STATIC */
  100. int r;
  101. /* Connect to the system bus */
  102. if (!ctx->bus) {
  103. if (user) {
  104. r = sym_sd_bus_open_user(&ctx->bus);
  105. } else {
  106. r = sym_sd_bus_open_system(&ctx->bus);
  107. }
  108. if (r < 0)
  109. goto busfailure;
  110. }
  111. static const char* const busServiceName = "org.freedesktop.systemd1";
  112. static const char* const busObjectPath = "/org/freedesktop/systemd1";
  113. static const char* const busInterfaceName = "org.freedesktop.systemd1.Manager";
  114. r = sym_sd_bus_get_property_string(ctx->bus,
  115. busServiceName, /* service to contact */
  116. busObjectPath, /* object path */
  117. busInterfaceName, /* interface name */
  118. "SystemState", /* property name */
  119. NULL, /* object to return error in */
  120. &ctx->systemState);
  121. if (r < 0)
  122. goto busfailure;
  123. r = sym_sd_bus_get_property_trivial(ctx->bus,
  124. busServiceName, /* service to contact */
  125. busObjectPath, /* object path */
  126. busInterfaceName, /* interface name */
  127. "NFailedUnits", /* property name */
  128. NULL, /* object to return error in */
  129. 'u', /* property type */
  130. &ctx->nFailedUnits);
  131. if (r < 0)
  132. goto busfailure;
  133. r = sym_sd_bus_get_property_trivial(ctx->bus,
  134. busServiceName, /* service to contact */
  135. busObjectPath, /* object path */
  136. busInterfaceName, /* interface name */
  137. "NInstalledJobs", /* property name */
  138. NULL, /* object to return error in */
  139. 'u', /* property type */
  140. &ctx->nInstalledJobs);
  141. if (r < 0)
  142. goto busfailure;
  143. r = sym_sd_bus_get_property_trivial(ctx->bus,
  144. busServiceName, /* service to contact */
  145. busObjectPath, /* object path */
  146. busInterfaceName, /* interface name */
  147. "NNames", /* property name */
  148. NULL, /* object to return error in */
  149. 'u', /* property type */
  150. &ctx->nNames);
  151. if (r < 0)
  152. goto busfailure;
  153. r = sym_sd_bus_get_property_trivial(ctx->bus,
  154. busServiceName, /* service to contact */
  155. busObjectPath, /* object path */
  156. busInterfaceName, /* interface name */
  157. "NJobs", /* property name */
  158. NULL, /* object to return error in */
  159. 'u', /* property type */
  160. &ctx->nJobs);
  161. if (r < 0)
  162. goto busfailure;
  163. /* success */
  164. return 0;
  165. busfailure:
  166. sym_sd_bus_unref(ctx->bus);
  167. ctx->bus = NULL;
  168. return -2;
  169. #ifndef BUILD_STATIC
  170. dlfailure:
  171. if (dlopenHandle) {
  172. dlclose(dlopenHandle);
  173. dlopenHandle = NULL;
  174. }
  175. return -1;
  176. #endif /* !BUILD_STATIC */
  177. }
  178. #endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
  179. static void updateViaExec(bool user) {
  180. SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
  181. if (Settings_isReadonly())
  182. return;
  183. int fdpair[2];
  184. if (pipe(fdpair) < 0)
  185. return;
  186. pid_t child = fork();
  187. if (child < 0) {
  188. close(fdpair[1]);
  189. close(fdpair[0]);
  190. return;
  191. }
  192. if (child == 0) {
  193. close(fdpair[0]);
  194. dup2(fdpair[1], STDOUT_FILENO);
  195. close(fdpair[1]);
  196. int fdnull = open("/dev/null", O_WRONLY);
  197. if (fdnull < 0)
  198. exit(1);
  199. dup2(fdnull, STDERR_FILENO);
  200. close(fdnull);
  201. // Use of NULL in variadic functions must have a pointer cast.
  202. // The NULL constant is not required by standard to have a pointer type.
  203. execlp(
  204. "systemctl",
  205. "systemctl",
  206. "show",
  207. user ? "--user" : "--system",
  208. "--property=SystemState",
  209. "--property=NFailedUnits",
  210. "--property=NNames",
  211. "--property=NJobs",
  212. "--property=NInstalledJobs",
  213. (char*)NULL);
  214. exit(127);
  215. }
  216. close(fdpair[1]);
  217. int wstatus;
  218. if (waitpid(child, &wstatus, 0) < 0 || !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
  219. close(fdpair[0]);
  220. return;
  221. }
  222. FILE* commandOutput = fdopen(fdpair[0], "r");
  223. if (!commandOutput) {
  224. close(fdpair[0]);
  225. return;
  226. }
  227. char lineBuffer[128];
  228. while (fgets(lineBuffer, sizeof(lineBuffer), commandOutput)) {
  229. if (String_startsWith(lineBuffer, "SystemState=")) {
  230. char* newline = strchr(lineBuffer + strlen("SystemState="), '\n');
  231. if (newline) {
  232. *newline = '\0';
  233. }
  234. free_and_xStrdup(&ctx->systemState, lineBuffer + strlen("SystemState="));
  235. } else if (String_startsWith(lineBuffer, "NFailedUnits=")) {
  236. ctx->nFailedUnits = strtoul(lineBuffer + strlen("NFailedUnits="), NULL, 10);
  237. } else if (String_startsWith(lineBuffer, "NNames=")) {
  238. ctx->nNames = strtoul(lineBuffer + strlen("NNames="), NULL, 10);
  239. } else if (String_startsWith(lineBuffer, "NJobs=")) {
  240. ctx->nJobs = strtoul(lineBuffer + strlen("NJobs="), NULL, 10);
  241. } else if (String_startsWith(lineBuffer, "NInstalledJobs=")) {
  242. ctx->nInstalledJobs = strtoul(lineBuffer + strlen("NInstalledJobs="), NULL, 10);
  243. }
  244. }
  245. fclose(commandOutput);
  246. }
  247. static void SystemdMeter_updateValues(Meter* this) {
  248. bool user = String_eq(Meter_name(this), "SystemdUser");
  249. SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;
  250. free(ctx->systemState);
  251. ctx->systemState = NULL;
  252. ctx->nFailedUnits = ctx->nInstalledJobs = ctx->nNames = ctx->nJobs = INVALID_VALUE;
  253. #if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)
  254. if (updateViaLib(user) < 0)
  255. updateViaExec(user);
  256. #else
  257. updateViaExec(user);
  258. #endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */
  259. xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s", ctx->systemState ? ctx->systemState : "???");
  260. }
  261. static int zeroDigitColor(unsigned int value) {
  262. switch (value) {
  263. case 0:
  264. return CRT_colors[METER_VALUE];
  265. case INVALID_VALUE:
  266. return CRT_colors[METER_VALUE_ERROR];
  267. default:
  268. return CRT_colors[METER_VALUE_NOTICE];
  269. }
  270. }
  271. static int valueDigitColor(unsigned int value) {
  272. switch (value) {
  273. case 0:
  274. return CRT_colors[METER_VALUE_NOTICE];
  275. case INVALID_VALUE:
  276. return CRT_colors[METER_VALUE_ERROR];
  277. default:
  278. return CRT_colors[METER_VALUE];
  279. }
  280. }
  281. static void _SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out, SystemdMeterContext_t* ctx) {
  282. char buffer[16];
  283. int len;
  284. int color = METER_VALUE_ERROR;
  285. if (ctx->systemState) {
  286. color = String_eq(ctx->systemState, "running") ? METER_VALUE_OK :
  287. String_eq(ctx->systemState, "degraded") ? METER_VALUE_ERROR : METER_VALUE_WARN;
  288. }
  289. RichString_writeAscii(out, CRT_colors[color], ctx->systemState ? ctx->systemState : "N/A");
  290. RichString_appendAscii(out, CRT_colors[METER_TEXT], " (");
  291. if (ctx->nFailedUnits == INVALID_VALUE) {
  292. buffer[0] = '?';
  293. buffer[1] = '\0';
  294. len = 1;
  295. } else {
  296. len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nFailedUnits);
  297. }
  298. RichString_appendnAscii(out, zeroDigitColor(ctx->nFailedUnits), buffer, len);
  299. RichString_appendAscii(out, CRT_colors[METER_TEXT], "/");
  300. if (ctx->nNames == INVALID_VALUE) {
  301. buffer[0] = '?';
  302. buffer[1] = '\0';
  303. len = 1;
  304. } else {
  305. len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nNames);
  306. }
  307. RichString_appendnAscii(out, valueDigitColor(ctx->nNames), buffer, len);
  308. RichString_appendAscii(out, CRT_colors[METER_TEXT], " failed) (");
  309. if (ctx->nJobs == INVALID_VALUE) {
  310. buffer[0] = '?';
  311. buffer[1] = '\0';
  312. len = 1;
  313. } else {
  314. len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nJobs);
  315. }
  316. RichString_appendnAscii(out, zeroDigitColor(ctx->nJobs), buffer, len);
  317. RichString_appendAscii(out, CRT_colors[METER_TEXT], "/");
  318. if (ctx->nInstalledJobs == INVALID_VALUE) {
  319. buffer[0] = '?';
  320. buffer[1] = '\0';
  321. len = 1;
  322. } else {
  323. len = xSnprintf(buffer, sizeof(buffer), "%u", ctx->nInstalledJobs);
  324. }
  325. RichString_appendnAscii(out, valueDigitColor(ctx->nInstalledJobs), buffer, len);
  326. RichString_appendAscii(out, CRT_colors[METER_TEXT], " jobs)");
  327. }
  328. static void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
  329. _SystemdMeter_display(cast, out, &ctx_system);
  330. }
  331. static void SystemdUserMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {
  332. _SystemdMeter_display(cast, out, &ctx_user);
  333. }
  334. static const int SystemdMeter_attributes[] = {
  335. METER_VALUE
  336. };
  337. const MeterClass SystemdMeter_class = {
  338. .super = {
  339. .extends = Class(Meter),
  340. .delete = Meter_delete,
  341. .display = SystemdMeter_display
  342. },
  343. .updateValues = SystemdMeter_updateValues,
  344. .done = SystemdMeter_done,
  345. .defaultMode = TEXT_METERMODE,
  346. .supportedModes = (1 << TEXT_METERMODE),
  347. .maxItems = 0,
  348. .total = 0.0,
  349. .attributes = SystemdMeter_attributes,
  350. .name = "Systemd",
  351. .uiName = "Systemd state",
  352. .description = "Systemd system state and unit overview",
  353. .caption = "Systemd: ",
  354. };
  355. const MeterClass SystemdUserMeter_class = {
  356. .super = {
  357. .extends = Class(Meter),
  358. .delete = Meter_delete,
  359. .display = SystemdUserMeter_display
  360. },
  361. .updateValues = SystemdMeter_updateValues,
  362. .done = SystemdMeter_done,
  363. .defaultMode = TEXT_METERMODE,
  364. .supportedModes = (1 << TEXT_METERMODE),
  365. .maxItems = 0,
  366. .total = 0.0,
  367. .attributes = SystemdMeter_attributes,
  368. .name = "SystemdUser",
  369. .uiName = "Systemd user state",
  370. .description = "Systemd user state and unit overview",
  371. .caption = "Systemd User: ",
  372. };