Settings.c 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. /*
  2. htop - Settings.c
  3. (C) 2004-2011 Hisham H. Muhammad
  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 "Settings.h"
  9. #include <ctype.h>
  10. #include <errno.h>
  11. #include <fcntl.h>
  12. #include <limits.h>
  13. #include <pwd.h>
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #include <unistd.h>
  18. #include <sys/stat.h>
  19. #include "CRT.h"
  20. #include "DynamicColumn.h"
  21. #include "DynamicScreen.h"
  22. #include "Macros.h"
  23. #include "Meter.h"
  24. #include "Platform.h"
  25. #include "Process.h"
  26. #include "Table.h"
  27. #include "XUtils.h"
  28. static void Settings_deleteColumns(Settings* this) {
  29. for (size_t i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
  30. String_freeArray(this->hColumns[i].names);
  31. free(this->hColumns[i].modes);
  32. }
  33. free(this->hColumns);
  34. }
  35. static void Settings_deleteScreens(Settings* this) {
  36. if (this->screens) {
  37. for (size_t i = 0; this->screens[i]; i++)
  38. ScreenSettings_delete(this->screens[i]);
  39. free(this->screens);
  40. }
  41. }
  42. void Settings_delete(Settings* this) {
  43. free(this->filename);
  44. free(this->initialFilename);
  45. Settings_deleteColumns(this);
  46. Settings_deleteScreens(this);
  47. free(this);
  48. }
  49. static char** Settings_splitLineToIDs(const char* line) {
  50. char* trim = String_trim(line);
  51. char** ids = String_split(trim, ' ', NULL);
  52. free(trim);
  53. return ids;
  54. }
  55. static void Settings_readMeters(Settings* this, const char* line, size_t column) {
  56. column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);
  57. this->hColumns[column].names = Settings_splitLineToIDs(line);
  58. }
  59. static void Settings_readMeterModes(Settings* this, const char* line, size_t column) {
  60. char** ids = Settings_splitLineToIDs(line);
  61. size_t len = 0;
  62. for (size_t i = 0; ids[i]; i++) {
  63. len++;
  64. }
  65. column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);
  66. this->hColumns[column].len = len;
  67. MeterModeId* modes = len ? xCalloc(len, sizeof(MeterModeId)) : NULL;
  68. for (size_t i = 0; i < len; i++) {
  69. modes[i] = (MeterModeId) atoi(ids[i]);
  70. }
  71. this->hColumns[column].modes = modes;
  72. String_freeArray(ids);
  73. }
  74. static bool Settings_validateMeters(Settings* this) {
  75. const size_t colCount = HeaderLayout_getColumns(this->hLayout);
  76. bool anyMeter = false;
  77. for (size_t column = 0; column < colCount; column++) {
  78. char** names = this->hColumns[column].names;
  79. const MeterModeId* modes = this->hColumns[column].modes;
  80. const size_t len = this->hColumns[column].len;
  81. if (!len)
  82. continue;
  83. if (!names || !modes)
  84. return false;
  85. anyMeter |= !!len;
  86. // Check for each mode there is an entry with a non-NULL name
  87. for (size_t meterIdx = 0; meterIdx < len; meterIdx++)
  88. if (!names[meterIdx])
  89. return false;
  90. if (names[len])
  91. return false;
  92. }
  93. return anyMeter;
  94. }
  95. static void Settings_defaultMeters(Settings* this, const Machine* host) {
  96. unsigned int initialCpuCount = host->activeCPUs;
  97. size_t sizes[] = { 3, 3 };
  98. if (initialCpuCount > 4 && initialCpuCount <= 128) {
  99. sizes[1]++;
  100. }
  101. // Release any previously allocated memory
  102. Settings_deleteColumns(this);
  103. this->hLayout = HF_TWO_50_50;
  104. this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
  105. for (size_t i = 0; i < 2; i++) {
  106. this->hColumns[i].names = xCalloc(sizes[i] + 1, sizeof(*this->hColumns[0].names));
  107. this->hColumns[i].modes = xCalloc(sizes[i], sizeof(*this->hColumns[0].modes));
  108. this->hColumns[i].len = sizes[i];
  109. }
  110. int r = 0;
  111. if (initialCpuCount > 128) {
  112. // Just show the average, ricers need to config for impressive screenshots
  113. this->hColumns[0].names[0] = xStrdup("CPU");
  114. this->hColumns[0].modes[0] = BAR_METERMODE;
  115. } else if (initialCpuCount > 32) {
  116. this->hColumns[0].names[0] = xStrdup("LeftCPUs8");
  117. this->hColumns[0].modes[0] = BAR_METERMODE;
  118. this->hColumns[1].names[r] = xStrdup("RightCPUs8");
  119. this->hColumns[1].modes[r++] = BAR_METERMODE;
  120. } else if (initialCpuCount > 16) {
  121. this->hColumns[0].names[0] = xStrdup("LeftCPUs4");
  122. this->hColumns[0].modes[0] = BAR_METERMODE;
  123. this->hColumns[1].names[r] = xStrdup("RightCPUs4");
  124. this->hColumns[1].modes[r++] = BAR_METERMODE;
  125. } else if (initialCpuCount > 8) {
  126. this->hColumns[0].names[0] = xStrdup("LeftCPUs2");
  127. this->hColumns[0].modes[0] = BAR_METERMODE;
  128. this->hColumns[1].names[r] = xStrdup("RightCPUs2");
  129. this->hColumns[1].modes[r++] = BAR_METERMODE;
  130. } else if (initialCpuCount > 4) {
  131. this->hColumns[0].names[0] = xStrdup("LeftCPUs");
  132. this->hColumns[0].modes[0] = BAR_METERMODE;
  133. this->hColumns[1].names[r] = xStrdup("RightCPUs");
  134. this->hColumns[1].modes[r++] = BAR_METERMODE;
  135. } else {
  136. this->hColumns[0].names[0] = xStrdup("AllCPUs");
  137. this->hColumns[0].modes[0] = BAR_METERMODE;
  138. }
  139. this->hColumns[0].names[1] = xStrdup("Memory");
  140. this->hColumns[0].modes[1] = BAR_METERMODE;
  141. this->hColumns[0].names[2] = xStrdup("Swap");
  142. this->hColumns[0].modes[2] = BAR_METERMODE;
  143. this->hColumns[1].names[r] = xStrdup("Tasks");
  144. this->hColumns[1].modes[r++] = TEXT_METERMODE;
  145. this->hColumns[1].names[r] = xStrdup("LoadAverage");
  146. this->hColumns[1].modes[r++] = TEXT_METERMODE;
  147. this->hColumns[1].names[r] = xStrdup("Uptime");
  148. this->hColumns[1].modes[r++] = TEXT_METERMODE;
  149. }
  150. static const char* toFieldName(Hashtable* columns, int id, bool* enabled) {
  151. if (id < 0) {
  152. if (enabled)
  153. *enabled = false;
  154. return NULL;
  155. }
  156. if (id >= ROW_DYNAMIC_FIELDS) {
  157. const DynamicColumn* column = DynamicColumn_lookup(columns, id);
  158. if (enabled)
  159. *enabled = column ? column->enabled : false;
  160. return column ? column->name : NULL;
  161. }
  162. if (enabled)
  163. *enabled = true;
  164. return Process_fields[id].name;
  165. }
  166. static int toFieldIndex(Hashtable* columns, const char* str) {
  167. if (isdigit((unsigned char)str[0])) {
  168. // This "+1" is for compatibility with the older enum format.
  169. int id = atoi(str) + 1;
  170. if (toFieldName(columns, id, NULL)) {
  171. return id;
  172. }
  173. } else {
  174. // Dynamically-defined columns are always stored by-name.
  175. char dynamic[32] = {0};
  176. if (sscanf(str, "Dynamic(%30s)", dynamic) == 1) {
  177. char* end;
  178. if ((end = strrchr(dynamic, ')')) != NULL) {
  179. bool success;
  180. unsigned int key;
  181. *end = '\0';
  182. success = DynamicColumn_search(columns, dynamic, &key) != NULL;
  183. *end = ')';
  184. if (success)
  185. return key;
  186. }
  187. }
  188. // Fallback to iterative scan of table of fields by-name.
  189. for (int p = 1; p < LAST_PROCESSFIELD; p++) {
  190. const char* pName = toFieldName(columns, p, NULL);
  191. if (pName && strcmp(pName, str) == 0)
  192. return p;
  193. }
  194. }
  195. return -1;
  196. }
  197. static void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, const char* line) {
  198. char* trim = String_trim(line);
  199. char** ids = String_split(trim, ' ', NULL);
  200. free(trim);
  201. /* reset default fields */
  202. memset(ss->fields, '\0', LAST_PROCESSFIELD * sizeof(ProcessField));
  203. for (size_t j = 0, i = 0; ids[i]; i++) {
  204. if (j >= UINT_MAX / sizeof(ProcessField))
  205. continue;
  206. if (j >= LAST_PROCESSFIELD) {
  207. ss->fields = xRealloc(ss->fields, (j + 1) * sizeof(ProcessField));
  208. memset(&ss->fields[j], 0, sizeof(ProcessField));
  209. }
  210. int id = toFieldIndex(columns, ids[i]);
  211. if (id >= 0)
  212. ss->fields[j++] = id;
  213. if (id > 0 && id < LAST_PROCESSFIELD)
  214. ss->flags |= Process_fields[id].flags;
  215. }
  216. String_freeArray(ids);
  217. }
  218. static ScreenSettings* Settings_initScreenSettings(ScreenSettings* ss, Settings* this, const char* columns) {
  219. ScreenSettings_readFields(ss, this->dynamicColumns, columns);
  220. this->screens[this->nScreens] = ss;
  221. this->nScreens++;
  222. this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1));
  223. this->screens[this->nScreens] = NULL;
  224. return ss;
  225. }
  226. ScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults) {
  227. int sortKey = defaults->sortKey ? toFieldIndex(this->dynamicColumns, defaults->sortKey) : PID;
  228. int treeSortKey = defaults->treeSortKey ? toFieldIndex(this->dynamicColumns, defaults->treeSortKey) : PID;
  229. int sortDesc = (sortKey >= 0 && sortKey < LAST_PROCESSFIELD) ? Process_fields[sortKey].defaultSortDesc : 1;
  230. ScreenSettings* ss = xMalloc(sizeof(ScreenSettings));
  231. *ss = (ScreenSettings) {
  232. .heading = xStrdup(defaults->name),
  233. .dynamic = NULL,
  234. .table = NULL,
  235. .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)),
  236. .flags = 0,
  237. .direction = sortDesc ? -1 : 1,
  238. .treeDirection = 1,
  239. .sortKey = sortKey,
  240. .treeSortKey = treeSortKey,
  241. .treeView = false,
  242. .treeViewAlwaysByPID = false,
  243. .allBranchesCollapsed = false,
  244. };
  245. return Settings_initScreenSettings(ss, this, defaults->columns);
  246. }
  247. ScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const DynamicScreen* screen, Table* table) {
  248. int sortKey = toFieldIndex(this->dynamicColumns, screen->columnKeys);
  249. ScreenSettings* ss = xMalloc(sizeof(ScreenSettings));
  250. *ss = (ScreenSettings) {
  251. .heading = xStrdup(tab),
  252. .dynamic = xStrdup(screen->name),
  253. .table = table,
  254. .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)),
  255. .direction = screen->direction,
  256. .treeDirection = 1,
  257. .sortKey = sortKey,
  258. };
  259. return Settings_initScreenSettings(ss, this, screen->columnKeys);
  260. }
  261. void ScreenSettings_delete(ScreenSettings* this) {
  262. free(this->heading);
  263. free(this->dynamic);
  264. free(this->fields);
  265. free(this);
  266. }
  267. static ScreenSettings* Settings_defaultScreens(Settings* this) {
  268. if (this->nScreens)
  269. return this->screens[0];
  270. for (unsigned int i = 0; i < Platform_numberOfDefaultScreens; i++) {
  271. const ScreenDefaults* defaults = &Platform_defaultScreens[i];
  272. Settings_newScreen(this, defaults);
  273. }
  274. Platform_defaultDynamicScreens(this);
  275. return this->screens[0];
  276. }
  277. static bool Settings_read(Settings* this, const char* fileName, const Machine* host, bool checkWritability) {
  278. int fd = -1;
  279. const char* fopen_mode = "r+";
  280. if (checkWritability) {
  281. do {
  282. fd = open(fileName, O_RDWR | O_NOCTTY | O_NOFOLLOW);
  283. } while (fd < 0 && errno == EINTR);
  284. if (fd < 0) {
  285. this->writeConfig = (errno == ENOENT);
  286. if (errno != EACCES && errno != EPERM && errno != EROFS) {
  287. return false;
  288. }
  289. } else {
  290. // Check if this is a regular file
  291. struct stat sb;
  292. int err = fstat(fd, &sb);
  293. this->writeConfig = !err && S_ISREG(sb.st_mode);
  294. }
  295. }
  296. // If opening for read & write is not needed or fails, open for read only.
  297. // There is no risk of following symlink in this case.
  298. if (fd < 0) {
  299. fopen_mode = "r";
  300. do {
  301. fd = open(fileName, O_RDONLY | O_NOCTTY);
  302. } while (fd < 0 && errno == EINTR);
  303. }
  304. if (fd < 0)
  305. return false;
  306. FILE* fp = fdopen(fd, fopen_mode);
  307. if (!fp) {
  308. close(fd);
  309. return false;
  310. }
  311. ScreenSettings* screen = NULL;
  312. bool didReadMeters = false;
  313. bool didReadAny = false;
  314. for (;;) {
  315. char* line = String_readLine(fp);
  316. if (!line) {
  317. break;
  318. }
  319. didReadAny = true;
  320. size_t nOptions;
  321. char** option = String_split(line, '=', &nOptions);
  322. free (line);
  323. if (nOptions < 2) {
  324. String_freeArray(option);
  325. continue;
  326. }
  327. if (String_eq(option[0], "config_reader_min_version")) {
  328. this->config_version = atoi(option[1]);
  329. if (this->config_version > CONFIG_READER_MIN_VERSION) {
  330. // the version of the config file on disk is newer than what we can read
  331. fprintf(stderr, "WARNING: %s specifies configuration format\n", fileName);
  332. fprintf(stderr, " version v%d, but this %s binary only supports up to version v%d.\n", this->config_version, PACKAGE, CONFIG_READER_MIN_VERSION);
  333. fprintf(stderr, " The configuration file will be downgraded to v%d when %s exits.\n", CONFIG_READER_MIN_VERSION, PACKAGE);
  334. String_freeArray(option);
  335. fclose(fp);
  336. return false;
  337. }
  338. } else if (String_eq(option[0], "fields") && this->config_version <= 2) {
  339. // old (no screen) naming also supported for backwards compatibility
  340. screen = Settings_defaultScreens(this);
  341. ScreenSettings_readFields(screen, this->dynamicColumns, option[1]);
  342. } else if (String_eq(option[0], "sort_key") && this->config_version <= 2) {
  343. // old (no screen) naming also supported for backwards compatibility
  344. // This "+1" is for compatibility with the older enum format.
  345. screen = Settings_defaultScreens(this);
  346. screen->sortKey = atoi(option[1]) + 1;
  347. } else if (String_eq(option[0], "tree_sort_key") && this->config_version <= 2) {
  348. // old (no screen) naming also supported for backwards compatibility
  349. // This "+1" is for compatibility with the older enum format.
  350. screen = Settings_defaultScreens(this);
  351. screen->treeSortKey = atoi(option[1]) + 1;
  352. } else if (String_eq(option[0], "sort_direction") && this->config_version <= 2) {
  353. // old (no screen) naming also supported for backwards compatibility
  354. screen = Settings_defaultScreens(this);
  355. screen->direction = atoi(option[1]);
  356. } else if (String_eq(option[0], "tree_sort_direction") && this->config_version <= 2) {
  357. // old (no screen) naming also supported for backwards compatibility
  358. screen = Settings_defaultScreens(this);
  359. screen->treeDirection = atoi(option[1]);
  360. } else if (String_eq(option[0], "tree_view") && this->config_version <= 2) {
  361. // old (no screen) naming also supported for backwards compatibility
  362. screen = Settings_defaultScreens(this);
  363. screen->treeView = atoi(option[1]);
  364. } else if (String_eq(option[0], "tree_view_always_by_pid") && this->config_version <= 2) {
  365. // old (no screen) naming also supported for backwards compatibility
  366. screen = Settings_defaultScreens(this);
  367. screen->treeViewAlwaysByPID = atoi(option[1]);
  368. } else if (String_eq(option[0], "all_branches_collapsed") && this->config_version <= 2) {
  369. // old (no screen) naming also supported for backwards compatibility
  370. screen = Settings_defaultScreens(this);
  371. screen->allBranchesCollapsed = atoi(option[1]);
  372. } else if (String_eq(option[0], "hide_kernel_threads")) {
  373. this->hideKernelThreads = atoi(option[1]);
  374. } else if (String_eq(option[0], "hide_userland_threads")) {
  375. this->hideUserlandThreads = atoi(option[1]);
  376. } else if (String_eq(option[0], "hide_running_in_container")) {
  377. this->hideRunningInContainer = atoi(option[1]);
  378. } else if (String_eq(option[0], "shadow_other_users")) {
  379. this->shadowOtherUsers = atoi(option[1]);
  380. } else if (String_eq(option[0], "show_thread_names")) {
  381. this->showThreadNames = atoi(option[1]);
  382. } else if (String_eq(option[0], "show_program_path")) {
  383. this->showProgramPath = atoi(option[1]);
  384. } else if (String_eq(option[0], "highlight_base_name")) {
  385. this->highlightBaseName = atoi(option[1]);
  386. } else if (String_eq(option[0], "highlight_deleted_exe")) {
  387. this->highlightDeletedExe = atoi(option[1]);
  388. } else if (String_eq(option[0], "shadow_distribution_path_prefix")) {
  389. this->shadowDistPathPrefix = atoi(option[1]);
  390. } else if (String_eq(option[0], "highlight_megabytes")) {
  391. this->highlightMegabytes = atoi(option[1]);
  392. } else if (String_eq(option[0], "highlight_threads")) {
  393. this->highlightThreads = atoi(option[1]);
  394. } else if (String_eq(option[0], "highlight_changes")) {
  395. this->highlightChanges = atoi(option[1]);
  396. } else if (String_eq(option[0], "highlight_changes_delay_secs")) {
  397. this->highlightDelaySecs = CLAMP(atoi(option[1]), 1, 24 * 60 * 60);
  398. } else if (String_eq(option[0], "find_comm_in_cmdline")) {
  399. this->findCommInCmdline = atoi(option[1]);
  400. } else if (String_eq(option[0], "strip_exe_from_cmdline")) {
  401. this->stripExeFromCmdline = atoi(option[1]);
  402. } else if (String_eq(option[0], "show_merged_command")) {
  403. this->showMergedCommand = atoi(option[1]);
  404. } else if (String_eq(option[0], "header_margin")) {
  405. this->headerMargin = atoi(option[1]);
  406. } else if (String_eq(option[0], "screen_tabs")) {
  407. this->screenTabs = atoi(option[1]);
  408. } else if (String_eq(option[0], "expand_system_time")) {
  409. // Compatibility option.
  410. this->detailedCPUTime = atoi(option[1]);
  411. } else if (String_eq(option[0], "detailed_cpu_time")) {
  412. this->detailedCPUTime = atoi(option[1]);
  413. } else if (String_eq(option[0], "cpu_count_from_one")) {
  414. this->countCPUsFromOne = atoi(option[1]);
  415. } else if (String_eq(option[0], "cpu_count_from_zero")) {
  416. // old (inverted) naming also supported for backwards compatibility
  417. this->countCPUsFromOne = !atoi(option[1]);
  418. } else if (String_eq(option[0], "show_cpu_usage")) {
  419. this->showCPUUsage = atoi(option[1]);
  420. } else if (String_eq(option[0], "show_cpu_frequency")) {
  421. this->showCPUFrequency = atoi(option[1]);
  422. } else if (String_eq(option[0], "show_cached_memory")) {
  423. this->showCachedMemory = atoi(option[1]);
  424. #ifdef BUILD_WITH_CPU_TEMP
  425. } else if (String_eq(option[0], "show_cpu_temperature")) {
  426. this->showCPUTemperature = atoi(option[1]);
  427. } else if (String_eq(option[0], "degree_fahrenheit")) {
  428. this->degreeFahrenheit = atoi(option[1]);
  429. #endif
  430. } else if (String_eq(option[0], "update_process_names")) {
  431. this->updateProcessNames = atoi(option[1]);
  432. } else if (String_eq(option[0], "account_guest_in_cpu_meter")) {
  433. this->accountGuestInCPUMeter = atoi(option[1]);
  434. } else if (String_eq(option[0], "delay")) {
  435. this->delay = CLAMP(atoi(option[1]), 1, 255);
  436. } else if (String_eq(option[0], "color_scheme")) {
  437. this->colorScheme = atoi(option[1]);
  438. if (this->colorScheme < 0 || this->colorScheme >= LAST_COLORSCHEME) {
  439. this->colorScheme = 0;
  440. }
  441. #ifdef HAVE_GETMOUSE
  442. } else if (String_eq(option[0], "enable_mouse")) {
  443. this->enableMouse = atoi(option[1]);
  444. #endif
  445. } else if (String_eq(option[0], "header_layout")) {
  446. this->hLayout = isdigit((unsigned char)option[1][0]) ? ((HeaderLayout) atoi(option[1])) : HeaderLayout_fromName(option[1]);
  447. if (this->hLayout < 0 || this->hLayout >= LAST_HEADER_LAYOUT)
  448. this->hLayout = HF_TWO_50_50;
  449. free(this->hColumns);
  450. this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
  451. } else if (String_eq(option[0], "left_meters")) {
  452. Settings_readMeters(this, option[1], 0);
  453. didReadMeters = true;
  454. } else if (String_eq(option[0], "right_meters")) {
  455. Settings_readMeters(this, option[1], 1);
  456. didReadMeters = true;
  457. } else if (String_eq(option[0], "left_meter_modes")) {
  458. Settings_readMeterModes(this, option[1], 0);
  459. didReadMeters = true;
  460. } else if (String_eq(option[0], "right_meter_modes")) {
  461. Settings_readMeterModes(this, option[1], 1);
  462. didReadMeters = true;
  463. } else if (String_startsWith(option[0], "column_meters_")) {
  464. Settings_readMeters(this, option[1], atoi(option[0] + strlen("column_meters_")));
  465. didReadMeters = true;
  466. } else if (String_startsWith(option[0], "column_meter_modes_")) {
  467. Settings_readMeterModes(this, option[1], atoi(option[0] + strlen("column_meter_modes_")));
  468. didReadMeters = true;
  469. } else if (String_eq(option[0], "hide_function_bar")) {
  470. this->hideFunctionBar = atoi(option[1]);
  471. #ifdef HAVE_LIBHWLOC
  472. } else if (String_eq(option[0], "topology_affinity")) {
  473. this->topologyAffinity = !!atoi(option[1]);
  474. #endif
  475. } else if (strncmp(option[0], "screen:", 7) == 0) {
  476. screen = Settings_newScreen(this, &(const ScreenDefaults) { .name = option[0] + 7, .columns = option[1] });
  477. } else if (String_eq(option[0], ".sort_key")) {
  478. if (screen) {
  479. int key = toFieldIndex(this->dynamicColumns, option[1]);
  480. screen->sortKey = key > 0 ? key : PID;
  481. }
  482. } else if (String_eq(option[0], ".tree_sort_key")) {
  483. if (screen) {
  484. int key = toFieldIndex(this->dynamicColumns, option[1]);
  485. screen->treeSortKey = key > 0 ? key : PID;
  486. }
  487. } else if (String_eq(option[0], ".sort_direction")) {
  488. if (screen)
  489. screen->direction = atoi(option[1]);
  490. } else if (String_eq(option[0], ".tree_sort_direction")) {
  491. if (screen)
  492. screen->treeDirection = atoi(option[1]);
  493. } else if (String_eq(option[0], ".tree_view")) {
  494. if (screen)
  495. screen->treeView = atoi(option[1]);
  496. } else if (String_eq(option[0], ".tree_view_always_by_pid")) {
  497. if (screen)
  498. screen->treeViewAlwaysByPID = atoi(option[1]);
  499. } else if (String_eq(option[0], ".all_branches_collapsed")) {
  500. if (screen)
  501. screen->allBranchesCollapsed = atoi(option[1]);
  502. } else if (String_eq(option[0], ".dynamic")) {
  503. if (screen) {
  504. free_and_xStrdup(&screen->dynamic, option[1]);
  505. Platform_addDynamicScreen(screen);
  506. }
  507. }
  508. String_freeArray(option);
  509. }
  510. fclose(fp);
  511. if (!didReadMeters || !Settings_validateMeters(this))
  512. Settings_defaultMeters(this, host);
  513. if (!this->nScreens)
  514. Settings_defaultScreens(this);
  515. return didReadAny;
  516. }
  517. typedef ATTR_FORMAT(printf, 2, 3) int (*OutputFunc)(FILE*, const char*,...);
  518. static void writeFields(OutputFunc of, FILE* fp,
  519. const ProcessField* fields, Hashtable* columns,
  520. bool byName, char separator) {
  521. const char* sep = "";
  522. for (unsigned int i = 0; fields[i]; i++) {
  523. if (fields[i] < LAST_PROCESSFIELD && byName) {
  524. const char* pName = toFieldName(columns, fields[i], NULL);
  525. of(fp, "%s%s", sep, pName);
  526. } else if (fields[i] >= LAST_PROCESSFIELD && byName) {
  527. bool enabled;
  528. const char* pName = toFieldName(columns, fields[i], &enabled);
  529. if (enabled)
  530. of(fp, "%sDynamic(%s)", sep, pName);
  531. } else {
  532. // This "-1" is for compatibility with the older enum format.
  533. of(fp, "%s%d", sep, (int) fields[i] - 1);
  534. }
  535. sep = " ";
  536. }
  537. of(fp, "%c", separator);
  538. }
  539. static void writeList(OutputFunc of, FILE* fp,
  540. char** list, int len, char separator) {
  541. const char* sep = "";
  542. for (int i = 0; i < len; i++) {
  543. of(fp, "%s%s", sep, list[i]);
  544. sep = " ";
  545. }
  546. of(fp, "%c", separator);
  547. }
  548. static void writeMeters(const Settings* this, OutputFunc of,
  549. FILE* fp, char separator, unsigned int column) {
  550. if (this->hColumns[column].len) {
  551. writeList(of, fp, this->hColumns[column].names, this->hColumns[column].len, separator);
  552. } else {
  553. of(fp, "!%c", separator);
  554. }
  555. }
  556. static void writeMeterModes(const Settings* this, OutputFunc of,
  557. FILE* fp, char separator, unsigned int column) {
  558. if (this->hColumns[column].len) {
  559. const char* sep = "";
  560. for (size_t i = 0; i < this->hColumns[column].len; i++) {
  561. of(fp, "%s%u", sep, this->hColumns[column].modes[i]);
  562. sep = " ";
  563. }
  564. } else {
  565. of(fp, "!");
  566. }
  567. of(fp, "%c", separator);
  568. }
  569. ATTR_FORMAT(printf, 2, 3)
  570. static int signal_safe_fprintf(FILE* stream, const char* fmt, ...) {
  571. char buf[2048];
  572. va_list vl;
  573. va_start(vl, fmt);
  574. int n = vsnprintf(buf, sizeof(buf), fmt, vl);
  575. va_end(vl);
  576. if (n <= 0)
  577. return n;
  578. return full_write_str(fileno(stream), buf);
  579. }
  580. int Settings_write(const Settings* this, bool onCrash) {
  581. FILE* fp;
  582. char separator;
  583. char* tmpFilename = NULL;
  584. OutputFunc of;
  585. if (onCrash) {
  586. fp = stderr;
  587. separator = ';';
  588. of = signal_safe_fprintf;
  589. } else if (!this->writeConfig) {
  590. return 0;
  591. } else {
  592. /* create tempfile with mode 0600 */
  593. mode_t cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
  594. xAsprintf(&tmpFilename, "%s.tmp.XXXXXX", this->filename);
  595. int fdtmp = mkstemp(tmpFilename);
  596. umask(cur_umask);
  597. if (fdtmp == -1) {
  598. free(tmpFilename);
  599. return -errno;
  600. }
  601. fp = fdopen(fdtmp, "w");
  602. if (!fp) {
  603. free(tmpFilename);
  604. return -errno;
  605. }
  606. separator = '\n';
  607. of = fprintf;
  608. }
  609. #define printSettingInteger(setting_, value_) \
  610. of(fp, setting_ "=%d%c", (int) (value_), separator)
  611. #define printSettingString(setting_, value_) \
  612. of(fp, setting_ "=%s%c", value_, separator)
  613. if (!onCrash) {
  614. of(fp, "# Beware! This file is rewritten by htop when settings are changed in the interface.\n");
  615. of(fp, "# The parser is also very primitive, and not human-friendly.\n");
  616. }
  617. printSettingString("htop_version", VERSION);
  618. printSettingInteger("config_reader_min_version", CONFIG_READER_MIN_VERSION);
  619. of(fp, "fields="); writeFields(of, fp, this->screens[0]->fields, this->dynamicColumns, false, separator);
  620. printSettingInteger("hide_kernel_threads", this->hideKernelThreads);
  621. printSettingInteger("hide_userland_threads", this->hideUserlandThreads);
  622. printSettingInteger("hide_running_in_container", this->hideRunningInContainer);
  623. printSettingInteger("shadow_other_users", this->shadowOtherUsers);
  624. printSettingInteger("show_thread_names", this->showThreadNames);
  625. printSettingInteger("show_program_path", this->showProgramPath);
  626. printSettingInteger("highlight_base_name", this->highlightBaseName);
  627. printSettingInteger("highlight_deleted_exe", this->highlightDeletedExe);
  628. printSettingInteger("shadow_distribution_path_prefix", this->shadowDistPathPrefix);
  629. printSettingInteger("highlight_megabytes", this->highlightMegabytes);
  630. printSettingInteger("highlight_threads", this->highlightThreads);
  631. printSettingInteger("highlight_changes", this->highlightChanges);
  632. printSettingInteger("highlight_changes_delay_secs", this->highlightDelaySecs);
  633. printSettingInteger("find_comm_in_cmdline", this->findCommInCmdline);
  634. printSettingInteger("strip_exe_from_cmdline", this->stripExeFromCmdline);
  635. printSettingInteger("show_merged_command", this->showMergedCommand);
  636. printSettingInteger("header_margin", this->headerMargin);
  637. printSettingInteger("screen_tabs", this->screenTabs);
  638. printSettingInteger("detailed_cpu_time", this->detailedCPUTime);
  639. printSettingInteger("cpu_count_from_one", this->countCPUsFromOne);
  640. printSettingInteger("show_cpu_usage", this->showCPUUsage);
  641. printSettingInteger("show_cpu_frequency", this->showCPUFrequency);
  642. #ifdef BUILD_WITH_CPU_TEMP
  643. printSettingInteger("show_cpu_temperature", this->showCPUTemperature);
  644. printSettingInteger("degree_fahrenheit", this->degreeFahrenheit);
  645. #endif
  646. printSettingInteger("show_cached_memory", this->showCachedMemory);
  647. printSettingInteger("update_process_names", this->updateProcessNames);
  648. printSettingInteger("account_guest_in_cpu_meter", this->accountGuestInCPUMeter);
  649. printSettingInteger("color_scheme", this->colorScheme);
  650. #ifdef HAVE_GETMOUSE
  651. printSettingInteger("enable_mouse", this->enableMouse);
  652. #endif
  653. printSettingInteger("delay", (int) this->delay);
  654. printSettingInteger("hide_function_bar", (int) this->hideFunctionBar);
  655. #ifdef HAVE_LIBHWLOC
  656. printSettingInteger("topology_affinity", this->topologyAffinity);
  657. #endif
  658. printSettingString("header_layout", HeaderLayout_getName(this->hLayout));
  659. for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
  660. of(fp, "column_meters_%u=", i);
  661. writeMeters(this, of, fp, separator, i);
  662. of(fp, "column_meter_modes_%u=", i);
  663. writeMeterModes(this, of, fp, separator, i);
  664. }
  665. // Legacy compatibility with older versions of htop
  666. printSettingInteger("tree_view", this->screens[0]->treeView);
  667. // This "-1" is for compatibility with the older enum format.
  668. printSettingInteger("sort_key", this->screens[0]->sortKey - 1);
  669. printSettingInteger("tree_sort_key", this->screens[0]->treeSortKey - 1);
  670. printSettingInteger("sort_direction", this->screens[0]->direction);
  671. printSettingInteger("tree_sort_direction", this->screens[0]->treeDirection);
  672. printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByPID);
  673. printSettingInteger("all_branches_collapsed", this->screens[0]->allBranchesCollapsed);
  674. for (unsigned int i = 0; i < this->nScreens; i++) {
  675. ScreenSettings* ss = this->screens[i];
  676. const char* sortKey = toFieldName(this->dynamicColumns, ss->sortKey, NULL);
  677. const char* treeSortKey = toFieldName(this->dynamicColumns, ss->treeSortKey, NULL);
  678. of(fp, "screen:%s=", ss->heading);
  679. writeFields(of, fp, ss->fields, this->dynamicColumns, true, separator);
  680. if (ss->dynamic) {
  681. printSettingString(".dynamic", ss->dynamic);
  682. if (ss->sortKey && ss->sortKey != PID)
  683. of(fp, "%s=Dynamic(%s)%c", ".sort_key", sortKey, separator);
  684. if (ss->treeSortKey && ss->treeSortKey != PID)
  685. of(fp, "%s=Dynamic(%s)%c", ".tree_sort_key", treeSortKey, separator);
  686. } else {
  687. printSettingString(".sort_key", sortKey);
  688. printSettingString(".tree_sort_key", treeSortKey);
  689. printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID);
  690. }
  691. printSettingInteger(".tree_view", ss->treeView);
  692. printSettingInteger(".sort_direction", ss->direction);
  693. printSettingInteger(".tree_sort_direction", ss->treeDirection);
  694. printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed);
  695. }
  696. #undef printSettingString
  697. #undef printSettingInteger
  698. if (onCrash)
  699. return 0;
  700. int r = 0;
  701. if (ferror(fp) != 0)
  702. r = (errno != 0) ? -errno : -EBADF;
  703. if (fclose(fp) != 0)
  704. r = r ? r : -errno;
  705. if (r == 0)
  706. r = (rename(tmpFilename, this->filename) == -1) ? -errno : 0;
  707. free(tmpFilename);
  708. return r;
  709. }
  710. Settings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens) {
  711. Settings* this = xCalloc(1, sizeof(Settings));
  712. this->writeConfig = true;
  713. this->dynamicScreens = dynamicScreens;
  714. this->dynamicColumns = dynamicColumns;
  715. this->dynamicMeters = dynamicMeters;
  716. this->hLayout = HF_TWO_50_50;
  717. this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
  718. this->shadowOtherUsers = false;
  719. this->showThreadNames = false;
  720. this->hideKernelThreads = true;
  721. this->hideUserlandThreads = false;
  722. this->hideRunningInContainer = false;
  723. this->highlightBaseName = false;
  724. this->highlightDeletedExe = true;
  725. this->shadowDistPathPrefix = false;
  726. this->highlightMegabytes = true;
  727. this->detailedCPUTime = false;
  728. this->countCPUsFromOne = false;
  729. this->showCPUUsage = true;
  730. this->showCPUFrequency = false;
  731. #ifdef BUILD_WITH_CPU_TEMP
  732. this->showCPUTemperature = false;
  733. this->degreeFahrenheit = false;
  734. #endif
  735. this->showCachedMemory = true;
  736. this->updateProcessNames = false;
  737. this->showProgramPath = true;
  738. this->highlightThreads = true;
  739. this->highlightChanges = false;
  740. this->highlightDelaySecs = DEFAULT_HIGHLIGHT_SECS;
  741. this->findCommInCmdline = true;
  742. this->stripExeFromCmdline = true;
  743. this->showMergedCommand = false;
  744. this->hideFunctionBar = 0;
  745. this->headerMargin = true;
  746. #ifdef HAVE_LIBHWLOC
  747. this->topologyAffinity = false;
  748. #endif
  749. this->screens = xCalloc(Platform_numberOfDefaultScreens, sizeof(ScreenSettings*));
  750. this->nScreens = 0;
  751. char* legacyDotfile = NULL;
  752. const char* rcfile = getenv("HTOPRC");
  753. if (rcfile) {
  754. this->initialFilename = xStrdup(rcfile);
  755. } else {
  756. const char* home = getenv("HOME");
  757. if (!home || home[0] != '/') {
  758. const struct passwd* pw = getpwuid(getuid());
  759. home = (pw && pw->pw_dir && pw->pw_dir[0] == '/') ? pw->pw_dir : "";
  760. }
  761. const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
  762. char* configDir = NULL;
  763. char* htopDir = NULL;
  764. if (xdgConfigHome && xdgConfigHome[0] == '/') {
  765. this->initialFilename = String_cat(xdgConfigHome, "/htop/htoprc");
  766. configDir = xStrdup(xdgConfigHome);
  767. htopDir = String_cat(xdgConfigHome, "/htop");
  768. } else {
  769. this->initialFilename = String_cat(home, CONFIGDIR "/htop/htoprc");
  770. configDir = String_cat(home, CONFIGDIR);
  771. htopDir = String_cat(home, CONFIGDIR "/htop");
  772. }
  773. (void) mkdir(configDir, 0700);
  774. (void) mkdir(htopDir, 0700);
  775. free(htopDir);
  776. free(configDir);
  777. legacyDotfile = String_cat(home, "/.htoprc");
  778. }
  779. this->filename = xMalloc(PATH_MAX);
  780. if (!realpath(this->initialFilename, this->filename))
  781. free_and_xStrdup(&this->filename, this->initialFilename);
  782. this->colorScheme = 0;
  783. #ifdef HAVE_GETMOUSE
  784. this->enableMouse = true;
  785. #endif
  786. this->changed = false;
  787. this->delay = DEFAULT_DELAY;
  788. bool ok = Settings_read(this, this->filename, host, /*checkWritability*/true);
  789. if (!ok && legacyDotfile) {
  790. ok = Settings_read(this, legacyDotfile, host, this->writeConfig);
  791. if (ok && this->writeConfig) {
  792. // Transition to new location and delete old configuration file
  793. if (Settings_write(this, false) == 0) {
  794. unlink(legacyDotfile);
  795. }
  796. }
  797. }
  798. if (!ok) {
  799. this->screenTabs = true;
  800. this->changed = true;
  801. ok = Settings_read(this, SYSCONFDIR "/htoprc", host, /*checkWritability*/false);
  802. }
  803. if (!ok) {
  804. Settings_defaultMeters(this, host);
  805. Settings_defaultScreens(this);
  806. }
  807. this->ssIndex = 0;
  808. this->ss = this->screens[this->ssIndex];
  809. this->lastUpdate = 1;
  810. free(legacyDotfile);
  811. return this;
  812. }
  813. void ScreenSettings_invertSortOrder(ScreenSettings* this) {
  814. int* attr = (this->treeView) ? &(this->treeDirection) : &(this->direction);
  815. *attr = (*attr == 1) ? -1 : 1;
  816. }
  817. void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) {
  818. if (this->treeViewAlwaysByPID || !this->treeView) {
  819. this->sortKey = sortKey;
  820. this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
  821. this->treeView = false;
  822. } else {
  823. this->treeSortKey = sortKey;
  824. this->treeDirection = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
  825. }
  826. }
  827. static bool readonly = false;
  828. void Settings_enableReadonly(void) {
  829. readonly = true;
  830. }
  831. bool Settings_isReadonly(void) {
  832. return readonly;
  833. }
  834. void Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout) {
  835. unsigned int oldColumns = HeaderLayout_getColumns(this->hLayout);
  836. unsigned int newColumns = HeaderLayout_getColumns(hLayout);
  837. if (newColumns > oldColumns) {
  838. this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));
  839. memset(this->hColumns + oldColumns, 0, (newColumns - oldColumns) * sizeof(MeterColumnSetting));
  840. } else if (newColumns < oldColumns) {
  841. for (unsigned int i = newColumns; i < oldColumns; i++) {
  842. if (this->hColumns[i].names) {
  843. for (size_t j = 0; j < this->hColumns[i].len; j++)
  844. free(this->hColumns[i].names[j]);
  845. free(this->hColumns[i].names);
  846. }
  847. free(this->hColumns[i].modes);
  848. }
  849. this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));
  850. }
  851. this->hLayout = hLayout;
  852. this->changed = true;
  853. }