/* htop - PCPDynamicScreen.c (C) 2022 Sohaib Mohammed (C) 2022-2023 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 "pcp/PCPDynamicScreen.h" #include #include #include #include #include "AvailableColumnsPanel.h" #include "Macros.h" #include "Platform.h" #include "Settings.h" #include "XUtils.h" #include "pcp/InDomTable.h" #include "pcp/PCPDynamicColumn.h" static char* formatFields(PCPDynamicScreen* screen) { char* columns = strdup(""); for (size_t j = 0; j < screen->totalColumns; j++) { const PCPDynamicColumn* column = screen->columns[j]; if (column->super.enabled == false) continue; char* prefix = columns; xAsprintf(&columns, "%s Dynamic(%s)", prefix, column->super.name); free(prefix); } return columns; } static void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { for (size_t i = 0; i < screens->count; i++) { PCPDynamicScreen* screen = Hashtable_get(screens->table, i); if (!screen) return; /* setup default fields (columns) based on configuration */ for (size_t j = 0; j < screen->totalColumns; j++) { PCPDynamicColumn* column = screen->columns[j]; column->id = columns->offset + columns->cursor; columns->cursor++; Platform_addMetric(column->id, column->metricName); size_t id = columns->count + LAST_PROCESSFIELD; Hashtable_put(columns->table, id, column); columns->count++; if (j == 0) { const pmDesc* desc = Metric_desc(column->id); assert(desc->indom != PM_INDOM_NULL); screen->indom = desc->indom; screen->key = column->id; } } screen->super.columnKeys = formatFields(screen); } } static PCPDynamicColumn* PCPDynamicScreen_lookupMetric(PCPDynamicScreen* screen, const char* name) { PCPDynamicColumn* column = NULL; size_t bytes = strlen(name) + strlen(screen->super.name) + 1; /* colon */ if (bytes >= sizeof(column->super.name)) return NULL; bytes += 16; /* prefix, dots and terminator */ char* metricName = xMalloc(bytes); xSnprintf(metricName, bytes, "htop.screen.%s.%s", screen->super.name, name); for (size_t i = 0; i < screen->totalColumns; i++) { column = screen->columns[i]; if (String_eq(column->metricName, metricName)) { free(metricName); return column; } } /* not an existing column in this screen - create it and add to the list */ column = xCalloc(1, sizeof(PCPDynamicColumn)); xSnprintf(column->super.name, sizeof(column->super.name), "%s:%s", screen->super.name, name); column->super.table = &screen->table->super; column->metricName = metricName; column->super.enabled = true; size_t n = screen->totalColumns + 1; screen->columns = xReallocArray(screen->columns, n, sizeof(PCPDynamicColumn*)); screen->columns[n - 1] = column; screen->totalColumns = n; return column; } static void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* path, unsigned int line, char* key, char* value) { PCPDynamicColumn* column; char* p; if ((p = strchr(key, '.')) == NULL) return; *p++ = '\0'; /* end the name, p is now the attribute, e.g. 'label' */ /* lookup a dynamic column with this name, else create */ column = PCPDynamicScreen_lookupMetric(screen, key); if (String_eq(p, "metric")) { char* error; if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) { char* note; xAsprintf(¬e, "%s: failed to parse expression in %s at line %u\n%s\n", pmGetProgname(), path, line, error); free(error); errno = EINVAL; CRT_fatalError(note); free(note); } /* pmLookupText - add optional metric help text */ if (!column->super.description && !column->instances) Metric_lookupText(value, &column->super.description); } else { /* this is a property of a dynamic column - the column expression */ /* may not have been observed yet; i.e. we allow for any ordering */ if (String_eq(p, "caption")) { free_and_xStrdup(&column->super.caption, value); } else if (String_eq(p, "heading")) { free_and_xStrdup(&column->super.heading, value); } else if (String_eq(p, "description")) { free_and_xStrdup(&column->super.description, value); } else if (String_eq(p, "width")) { column->width = strtoul(value, NULL, 10); } else if (String_eq(p, "format")) { free_and_xStrdup(&column->format, value); } else if (String_eq(p, "instances")) { if (String_eq(value, "True") || String_eq(value, "true")) column->instances = true; free_and_xStrdup(&column->super.description, screen->super.caption); } else if (String_eq(p, "default")) { /* displayed by default */ if (String_eq(value, "False") || String_eq(value, "false")) column->defaultEnabled = column->super.enabled = false; } } } static bool PCPDynamicScreen_validateScreenName(char* key, const char* path, unsigned int line) { char* p = key; char* end = strrchr(key, ']'); if (end) { *end = '\0'; } else { fprintf(stderr, "%s: no closing brace on screen name at %s line %u\n\"%s\"", pmGetProgname(), path, line, key); return false; } while (*p) { if (p == key) { if (!isalpha(*p) && *p != '_') break; } else { if (!isalnum(*p) && *p != '_') break; } p++; } if (*p != '\0') { /* badness */ fprintf(stderr, "%s: invalid screen name at %s line %u\n\"%s\"", pmGetProgname(), path, line, key); return false; } return true; } /* Ensure a screen name has not been defined previously */ static bool PCPDynamicScreen_uniqueName(char* key, PCPDynamicScreens* screens) { return !DynamicScreen_search(screens->table, key, NULL); } static PCPDynamicScreen* PCPDynamicScreen_new(PCPDynamicScreens* screens, const char* name) { PCPDynamicScreen* screen = xCalloc(1, sizeof(*screen)); String_safeStrncpy(screen->super.name, name, sizeof(screen->super.name)); screen->defaultEnabled = true; size_t id = screens->count; Hashtable_put(screens->table, id, screen); screens->count++; return screen; } static void PCPDynamicScreen_parseFile(PCPDynamicScreens* screens, const char* path) { FILE* file = fopen(path, "r"); if (!file) return; PCPDynamicScreen* screen = NULL; unsigned int lineno = 0; bool ok = true; for (;;) { char* line = String_readLine(file); if (!line) break; lineno++; /* cleanup whitespace, skip comment lines */ char* trimmed = String_trim(line); free(line); if (!trimmed || !trimmed[0] || trimmed[0] == '#') { free(trimmed); continue; } size_t n; char** config = String_split(trimmed, '=', &n); free(trimmed); if (config == NULL) continue; char* key = String_trim(config[0]); char* value = n > 1 ? String_trim(config[1]) : NULL; if (key[0] == '[') { /* new section name - i.e. new screen */ ok = PCPDynamicScreen_validateScreenName(key + 1, path, lineno); if (ok) ok = PCPDynamicScreen_uniqueName(key + 1, screens); if (ok) screen = PCPDynamicScreen_new(screens, key + 1); if (pmDebugOptions.appl0) fprintf(stderr, "[%s] screen: %s\n", path, key + 1); } else if (!ok) { ; /* skip this one, we're looking for a new header */ } else if (!value || !screen) { ; /* skip this one as we always need value strings */ } else if (String_eq(key, "heading")) { free_and_xStrdup(&screen->super.heading, value); } else if (String_eq(key, "caption")) { free_and_xStrdup(&screen->super.caption, value); } else if (String_eq(key, "sortKey")) { free_and_xStrdup(&screen->super.sortKey, value); } else if (String_eq(key, "sortDirection")) { screen->super.direction = strtoul(value, NULL, 10); } else if (String_eq(key, "default") || String_eq(key, "enabled")) { if (String_eq(value, "False") || String_eq(value, "false")) screen->defaultEnabled = false; else if (String_eq(value, "True") || String_eq(value, "true")) screen->defaultEnabled = true; /* also default */ } else { PCPDynamicScreen_parseColumn(screen, path, lineno, key, value); } String_freeArray(config); free(value); free(key); } fclose(file); } static void PCPDynamicScreen_scanDir(PCPDynamicScreens* screens, char* path) { DIR* dir = opendir(path); if (!dir) return; struct dirent* dirent; while ((dirent = readdir(dir)) != NULL) { if (dirent->d_name[0] == '.') continue; char* file = String_cat(path, dirent->d_name); PCPDynamicScreen_parseFile(screens, file); free(file); } closedir(dir); } void PCPDynamicScreens_init(PCPDynamicScreens* screens, PCPDynamicColumns* columns) { const char* share = pmGetConfig("PCP_SHARE_DIR"); const char* sysconf = pmGetConfig("PCP_SYSCONF_DIR"); const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); const char* override = getenv("PCP_HTOP_DIR"); const char* home = getenv("HOME"); char* path; screens->table = Hashtable_new(0, true); /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */ if (override) { path = String_cat(override, "/screens/"); PCPDynamicScreen_scanDir(screens, path); free(path); } /* next, search in home directory alongside htoprc */ if (xdgConfigHome) path = String_cat(xdgConfigHome, "/htop/screens/"); else if (home) path = String_cat(home, "/.config/htop/screens/"); else path = NULL; if (path) { PCPDynamicScreen_scanDir(screens, path); free(path); } /* next, search in the system screens directory */ path = String_cat(sysconf, "/htop/screens/"); PCPDynamicScreen_scanDir(screens, path); free(path); /* next, try the readonly system screens directory */ path = String_cat(share, "/htop/screens/"); PCPDynamicScreen_scanDir(screens, path); free(path); /* establish internal metric identifier mappings */ PCPDynamicScreens_appendDynamicColumns(screens, columns); } static void PCPDynamicScreen_done(PCPDynamicScreen* ds) { DynamicScreen_done(&ds->super); Object_delete(ds->table); free(ds->columns); } static void PCPDynamicScreens_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) { PCPDynamicScreen* ds = (PCPDynamicScreen*) value; PCPDynamicScreen_done(ds); } void PCPDynamicScreens_done(Hashtable* table) { Hashtable_foreach(table, PCPDynamicScreens_free, NULL); } void PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host) { PCPDynamicScreen* ds; for (size_t i = 0; i < screens->count; i++) { if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) continue; ds->table = InDomTable_new(host, ds->indom, ds->key); } } void PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings) { PCPDynamicScreen* ds; for (size_t i = 0; i < screens->count; i++) { if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) continue; if (ds->defaultEnabled == false) continue; const char* tab = ds->super.heading; Settings_newDynamicScreen(settings, tab, &ds->super, &ds->table->super); } } /* called when htoprc .dynamic line is parsed for a dynamic screen */ void PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss) { PCPDynamicScreen* ds; for (size_t i = 0; i < screens->count; i++) { if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL) continue; if (String_eq(ss->dynamic, ds->super.name) == false) continue; ss->table = &ds->table->super; } } void PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen) { Vector_prune(availableColumns->items); bool success; unsigned int key; success = DynamicScreen_search(screens, screen, &key); if (!success) return; PCPDynamicScreen* dynamicScreen = Hashtable_get(screens, key); if (!screen) return; for (unsigned int j = 0; j < dynamicScreen->totalColumns; j++) { PCPDynamicColumn* column = dynamicScreen->columns[j]; const char* title = column->super.heading ? column->super.heading : column->super.name; const char* text = column->super.description ? column->super.description : column->super.caption; char description[256]; if (text) xSnprintf(description, sizeof(description), "%s - %s", title, text); else xSnprintf(description, sizeof(description), "%s", title); Panel_add(availableColumns, (Object*) ListItem_new(description, j)); } }