Settings.c 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  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. #ifdef BUILD_WITH_CPU_TEMP
  423. } else if (String_eq(option[0], "show_cpu_temperature")) {
  424. this->showCPUTemperature = atoi(option[1]);
  425. } else if (String_eq(option[0], "degree_fahrenheit")) {
  426. this->degreeFahrenheit = atoi(option[1]);
  427. #endif
  428. } else if (String_eq(option[0], "update_process_names")) {
  429. this->updateProcessNames = atoi(option[1]);
  430. } else if (String_eq(option[0], "account_guest_in_cpu_meter")) {
  431. this->accountGuestInCPUMeter = atoi(option[1]);
  432. } else if (String_eq(option[0], "delay")) {
  433. this->delay = CLAMP(atoi(option[1]), 1, 255);
  434. } else if (String_eq(option[0], "color_scheme")) {
  435. this->colorScheme = atoi(option[1]);
  436. if (this->colorScheme < 0 || this->colorScheme >= LAST_COLORSCHEME) {
  437. this->colorScheme = 0;
  438. }
  439. #ifdef HAVE_GETMOUSE
  440. } else if (String_eq(option[0], "enable_mouse")) {
  441. this->enableMouse = atoi(option[1]);
  442. #endif
  443. } else if (String_eq(option[0], "header_layout")) {
  444. this->hLayout = isdigit((unsigned char)option[1][0]) ? ((HeaderLayout) atoi(option[1])) : HeaderLayout_fromName(option[1]);
  445. if (this->hLayout < 0 || this->hLayout >= LAST_HEADER_LAYOUT)
  446. this->hLayout = HF_TWO_50_50;
  447. free(this->hColumns);
  448. this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
  449. } else if (String_eq(option[0], "left_meters")) {
  450. Settings_readMeters(this, option[1], 0);
  451. didReadMeters = true;
  452. } else if (String_eq(option[0], "right_meters")) {
  453. Settings_readMeters(this, option[1], 1);
  454. didReadMeters = true;
  455. } else if (String_eq(option[0], "left_meter_modes")) {
  456. Settings_readMeterModes(this, option[1], 0);
  457. didReadMeters = true;
  458. } else if (String_eq(option[0], "right_meter_modes")) {
  459. Settings_readMeterModes(this, option[1], 1);
  460. didReadMeters = true;
  461. } else if (String_startsWith(option[0], "column_meters_")) {
  462. Settings_readMeters(this, option[1], atoi(option[0] + strlen("column_meters_")));
  463. didReadMeters = true;
  464. } else if (String_startsWith(option[0], "column_meter_modes_")) {
  465. Settings_readMeterModes(this, option[1], atoi(option[0] + strlen("column_meter_modes_")));
  466. didReadMeters = true;
  467. } else if (String_eq(option[0], "hide_function_bar")) {
  468. this->hideFunctionBar = atoi(option[1]);
  469. #ifdef HAVE_LIBHWLOC
  470. } else if (String_eq(option[0], "topology_affinity")) {
  471. this->topologyAffinity = !!atoi(option[1]);
  472. #endif
  473. } else if (strncmp(option[0], "screen:", 7) == 0) {
  474. screen = Settings_newScreen(this, &(const ScreenDefaults) { .name = option[0] + 7, .columns = option[1] });
  475. } else if (String_eq(option[0], ".sort_key")) {
  476. if (screen) {
  477. int key = toFieldIndex(this->dynamicColumns, option[1]);
  478. screen->sortKey = key > 0 ? key : PID;
  479. }
  480. } else if (String_eq(option[0], ".tree_sort_key")) {
  481. if (screen) {
  482. int key = toFieldIndex(this->dynamicColumns, option[1]);
  483. screen->treeSortKey = key > 0 ? key : PID;
  484. }
  485. } else if (String_eq(option[0], ".sort_direction")) {
  486. if (screen)
  487. screen->direction = atoi(option[1]);
  488. } else if (String_eq(option[0], ".tree_sort_direction")) {
  489. if (screen)
  490. screen->treeDirection = atoi(option[1]);
  491. } else if (String_eq(option[0], ".tree_view")) {
  492. if (screen)
  493. screen->treeView = atoi(option[1]);
  494. } else if (String_eq(option[0], ".tree_view_always_by_pid")) {
  495. if (screen)
  496. screen->treeViewAlwaysByPID = atoi(option[1]);
  497. } else if (String_eq(option[0], ".all_branches_collapsed")) {
  498. if (screen)
  499. screen->allBranchesCollapsed = atoi(option[1]);
  500. } else if (String_eq(option[0], ".dynamic")) {
  501. if (screen) {
  502. free_and_xStrdup(&screen->dynamic, option[1]);
  503. Platform_addDynamicScreen(screen);
  504. }
  505. }
  506. String_freeArray(option);
  507. }
  508. fclose(fp);
  509. if (!didReadMeters || !Settings_validateMeters(this))
  510. Settings_defaultMeters(this, host);
  511. if (!this->nScreens)
  512. Settings_defaultScreens(this);
  513. return didReadAny;
  514. }
  515. typedef ATTR_FORMAT(printf, 2, 3) int (*OutputFunc)(FILE*, const char*,...);
  516. static void writeFields(OutputFunc of, FILE* fp,
  517. const ProcessField* fields, Hashtable* columns,
  518. bool byName, char separator) {
  519. const char* sep = "";
  520. for (unsigned int i = 0; fields[i]; i++) {
  521. if (fields[i] < LAST_PROCESSFIELD && byName) {
  522. const char* pName = toFieldName(columns, fields[i], NULL);
  523. of(fp, "%s%s", sep, pName);
  524. } else if (fields[i] >= LAST_PROCESSFIELD && byName) {
  525. bool enabled;
  526. const char* pName = toFieldName(columns, fields[i], &enabled);
  527. if (enabled)
  528. of(fp, "%sDynamic(%s)", sep, pName);
  529. } else {
  530. // This "-1" is for compatibility with the older enum format.
  531. of(fp, "%s%d", sep, (int) fields[i] - 1);
  532. }
  533. sep = " ";
  534. }
  535. of(fp, "%c", separator);
  536. }
  537. static void writeList(OutputFunc of, FILE* fp,
  538. char** list, int len, char separator) {
  539. const char* sep = "";
  540. for (int i = 0; i < len; i++) {
  541. of(fp, "%s%s", sep, list[i]);
  542. sep = " ";
  543. }
  544. of(fp, "%c", separator);
  545. }
  546. static void writeMeters(const Settings* this, OutputFunc of,
  547. FILE* fp, char separator, unsigned int column) {
  548. if (this->hColumns[column].len) {
  549. writeList(of, fp, this->hColumns[column].names, this->hColumns[column].len, separator);
  550. } else {
  551. of(fp, "!%c", separator);
  552. }
  553. }
  554. static void writeMeterModes(const Settings* this, OutputFunc of,
  555. FILE* fp, char separator, unsigned int column) {
  556. if (this->hColumns[column].len) {
  557. const char* sep = "";
  558. for (size_t i = 0; i < this->hColumns[column].len; i++) {
  559. of(fp, "%s%u", sep, this->hColumns[column].modes[i]);
  560. sep = " ";
  561. }
  562. } else {
  563. of(fp, "!");
  564. }
  565. of(fp, "%c", separator);
  566. }
  567. ATTR_FORMAT(printf, 2, 3)
  568. static int signal_safe_fprintf(FILE* stream, const char* fmt, ...) {
  569. char buf[2048];
  570. va_list vl;
  571. va_start(vl, fmt);
  572. int n = vsnprintf(buf, sizeof(buf), fmt, vl);
  573. va_end(vl);
  574. if (n <= 0)
  575. return n;
  576. return full_write_str(fileno(stream), buf);
  577. }
  578. int Settings_write(const Settings* this, bool onCrash) {
  579. FILE* fp;
  580. char separator;
  581. char* tmpFilename = NULL;
  582. OutputFunc of;
  583. if (onCrash) {
  584. fp = stderr;
  585. separator = ';';
  586. of = signal_safe_fprintf;
  587. } else if (!this->writeConfig) {
  588. return 0;
  589. } else {
  590. /* create tempfile with mode 0600 */
  591. mode_t cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
  592. xAsprintf(&tmpFilename, "%s.tmp.XXXXXX", this->filename);
  593. int fdtmp = mkstemp(tmpFilename);
  594. umask(cur_umask);
  595. if (fdtmp == -1)
  596. return -errno;
  597. fp = fdopen(fdtmp, "w");
  598. if (!fp)
  599. return -errno;
  600. separator = '\n';
  601. of = fprintf;
  602. }
  603. #define printSettingInteger(setting_, value_) \
  604. of(fp, setting_ "=%d%c", (int) (value_), separator)
  605. #define printSettingString(setting_, value_) \
  606. of(fp, setting_ "=%s%c", value_, separator)
  607. if (!onCrash) {
  608. of(fp, "# Beware! This file is rewritten by htop when settings are changed in the interface.\n");
  609. of(fp, "# The parser is also very primitive, and not human-friendly.\n");
  610. }
  611. printSettingString("htop_version", VERSION);
  612. printSettingInteger("config_reader_min_version", CONFIG_READER_MIN_VERSION);
  613. of(fp, "fields="); writeFields(of, fp, this->screens[0]->fields, this->dynamicColumns, false, separator);
  614. printSettingInteger("hide_kernel_threads", this->hideKernelThreads);
  615. printSettingInteger("hide_userland_threads", this->hideUserlandThreads);
  616. printSettingInteger("hide_running_in_container", this->hideRunningInContainer);
  617. printSettingInteger("shadow_other_users", this->shadowOtherUsers);
  618. printSettingInteger("show_thread_names", this->showThreadNames);
  619. printSettingInteger("show_program_path", this->showProgramPath);
  620. printSettingInteger("highlight_base_name", this->highlightBaseName);
  621. printSettingInteger("highlight_deleted_exe", this->highlightDeletedExe);
  622. printSettingInteger("shadow_distribution_path_prefix", this->shadowDistPathPrefix);
  623. printSettingInteger("highlight_megabytes", this->highlightMegabytes);
  624. printSettingInteger("highlight_threads", this->highlightThreads);
  625. printSettingInteger("highlight_changes", this->highlightChanges);
  626. printSettingInteger("highlight_changes_delay_secs", this->highlightDelaySecs);
  627. printSettingInteger("find_comm_in_cmdline", this->findCommInCmdline);
  628. printSettingInteger("strip_exe_from_cmdline", this->stripExeFromCmdline);
  629. printSettingInteger("show_merged_command", this->showMergedCommand);
  630. printSettingInteger("header_margin", this->headerMargin);
  631. printSettingInteger("screen_tabs", this->screenTabs);
  632. printSettingInteger("detailed_cpu_time", this->detailedCPUTime);
  633. printSettingInteger("cpu_count_from_one", this->countCPUsFromOne);
  634. printSettingInteger("show_cpu_usage", this->showCPUUsage);
  635. printSettingInteger("show_cpu_frequency", this->showCPUFrequency);
  636. #ifdef BUILD_WITH_CPU_TEMP
  637. printSettingInteger("show_cpu_temperature", this->showCPUTemperature);
  638. printSettingInteger("degree_fahrenheit", this->degreeFahrenheit);
  639. #endif
  640. printSettingInteger("update_process_names", this->updateProcessNames);
  641. printSettingInteger("account_guest_in_cpu_meter", this->accountGuestInCPUMeter);
  642. printSettingInteger("color_scheme", this->colorScheme);
  643. #ifdef HAVE_GETMOUSE
  644. printSettingInteger("enable_mouse", this->enableMouse);
  645. #endif
  646. printSettingInteger("delay", (int) this->delay);
  647. printSettingInteger("hide_function_bar", (int) this->hideFunctionBar);
  648. #ifdef HAVE_LIBHWLOC
  649. printSettingInteger("topology_affinity", this->topologyAffinity);
  650. #endif
  651. printSettingString("header_layout", HeaderLayout_getName(this->hLayout));
  652. for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {
  653. of(fp, "column_meters_%u=", i);
  654. writeMeters(this, of, fp, separator, i);
  655. of(fp, "column_meter_modes_%u=", i);
  656. writeMeterModes(this, of, fp, separator, i);
  657. }
  658. // Legacy compatibility with older versions of htop
  659. printSettingInteger("tree_view", this->screens[0]->treeView);
  660. // This "-1" is for compatibility with the older enum format.
  661. printSettingInteger("sort_key", this->screens[0]->sortKey - 1);
  662. printSettingInteger("tree_sort_key", this->screens[0]->treeSortKey - 1);
  663. printSettingInteger("sort_direction", this->screens[0]->direction);
  664. printSettingInteger("tree_sort_direction", this->screens[0]->treeDirection);
  665. printSettingInteger("tree_view_always_by_pid", this->screens[0]->treeViewAlwaysByPID);
  666. printSettingInteger("all_branches_collapsed", this->screens[0]->allBranchesCollapsed);
  667. for (unsigned int i = 0; i < this->nScreens; i++) {
  668. ScreenSettings* ss = this->screens[i];
  669. const char* sortKey = toFieldName(this->dynamicColumns, ss->sortKey, NULL);
  670. const char* treeSortKey = toFieldName(this->dynamicColumns, ss->treeSortKey, NULL);
  671. of(fp, "screen:%s=", ss->heading);
  672. writeFields(of, fp, ss->fields, this->dynamicColumns, true, separator);
  673. if (ss->dynamic) {
  674. printSettingString(".dynamic", ss->dynamic);
  675. if (ss->sortKey && ss->sortKey != PID)
  676. of(fp, "%s=Dynamic(%s)%c", ".sort_key", sortKey, separator);
  677. if (ss->treeSortKey && ss->treeSortKey != PID)
  678. of(fp, "%s=Dynamic(%s)%c", ".tree_sort_key", treeSortKey, separator);
  679. } else {
  680. printSettingString(".sort_key", sortKey);
  681. printSettingString(".tree_sort_key", treeSortKey);
  682. printSettingInteger(".tree_view_always_by_pid", ss->treeViewAlwaysByPID);
  683. }
  684. printSettingInteger(".tree_view", ss->treeView);
  685. printSettingInteger(".sort_direction", ss->direction);
  686. printSettingInteger(".tree_sort_direction", ss->treeDirection);
  687. printSettingInteger(".all_branches_collapsed", ss->allBranchesCollapsed);
  688. }
  689. #undef printSettingString
  690. #undef printSettingInteger
  691. if (onCrash)
  692. return 0;
  693. int r = 0;
  694. if (ferror(fp) != 0)
  695. r = (errno != 0) ? -errno : -EBADF;
  696. if (fclose(fp) != 0)
  697. r = r ? r : -errno;
  698. if (r == 0)
  699. r = (rename(tmpFilename, this->filename) == -1) ? -errno : 0;
  700. free(tmpFilename);
  701. return r;
  702. }
  703. Settings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens) {
  704. Settings* this = xCalloc(1, sizeof(Settings));
  705. this->writeConfig = true;
  706. this->dynamicScreens = dynamicScreens;
  707. this->dynamicColumns = dynamicColumns;
  708. this->dynamicMeters = dynamicMeters;
  709. this->hLayout = HF_TWO_50_50;
  710. this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));
  711. this->shadowOtherUsers = false;
  712. this->showThreadNames = false;
  713. this->hideKernelThreads = true;
  714. this->hideUserlandThreads = false;
  715. this->hideRunningInContainer = false;
  716. this->highlightBaseName = false;
  717. this->highlightDeletedExe = true;
  718. this->shadowDistPathPrefix = false;
  719. this->highlightMegabytes = true;
  720. this->detailedCPUTime = false;
  721. this->countCPUsFromOne = false;
  722. this->showCPUUsage = true;
  723. this->showCPUFrequency = false;
  724. #ifdef BUILD_WITH_CPU_TEMP
  725. this->showCPUTemperature = false;
  726. this->degreeFahrenheit = false;
  727. #endif
  728. this->updateProcessNames = false;
  729. this->showProgramPath = true;
  730. this->highlightThreads = true;
  731. this->highlightChanges = false;
  732. this->highlightDelaySecs = DEFAULT_HIGHLIGHT_SECS;
  733. this->findCommInCmdline = true;
  734. this->stripExeFromCmdline = true;
  735. this->showMergedCommand = false;
  736. this->hideFunctionBar = 0;
  737. this->headerMargin = true;
  738. #ifdef HAVE_LIBHWLOC
  739. this->topologyAffinity = false;
  740. #endif
  741. this->screens = xCalloc(Platform_numberOfDefaultScreens, sizeof(ScreenSettings*));
  742. this->nScreens = 0;
  743. char* legacyDotfile = NULL;
  744. const char* rcfile = getenv("HTOPRC");
  745. if (rcfile) {
  746. this->initialFilename = xStrdup(rcfile);
  747. } else {
  748. const char* home = getenv("HOME");
  749. if (!home || home[0] != '/') {
  750. const struct passwd* pw = getpwuid(getuid());
  751. home = (pw && pw->pw_dir && pw->pw_dir[0] == '/') ? pw->pw_dir : "";
  752. }
  753. const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
  754. char* configDir = NULL;
  755. char* htopDir = NULL;
  756. if (xdgConfigHome && xdgConfigHome[0] == '/') {
  757. this->initialFilename = String_cat(xdgConfigHome, "/htop/htoprc");
  758. configDir = xStrdup(xdgConfigHome);
  759. htopDir = String_cat(xdgConfigHome, "/htop");
  760. } else {
  761. this->initialFilename = String_cat(home, CONFIGDIR "/htop/htoprc");
  762. configDir = String_cat(home, CONFIGDIR);
  763. htopDir = String_cat(home, CONFIGDIR "/htop");
  764. }
  765. (void) mkdir(configDir, 0700);
  766. (void) mkdir(htopDir, 0700);
  767. free(htopDir);
  768. free(configDir);
  769. legacyDotfile = String_cat(home, "/.htoprc");
  770. }
  771. this->filename = xMalloc(PATH_MAX);
  772. if (!realpath(this->initialFilename, this->filename))
  773. free_and_xStrdup(&this->filename, this->initialFilename);
  774. this->colorScheme = 0;
  775. #ifdef HAVE_GETMOUSE
  776. this->enableMouse = true;
  777. #endif
  778. this->changed = false;
  779. this->delay = DEFAULT_DELAY;
  780. bool ok = Settings_read(this, this->filename, host, /*checkWritability*/true);
  781. if (!ok && legacyDotfile) {
  782. ok = Settings_read(this, legacyDotfile, host, this->writeConfig);
  783. if (ok && this->writeConfig) {
  784. // Transition to new location and delete old configuration file
  785. if (Settings_write(this, false) == 0) {
  786. unlink(legacyDotfile);
  787. }
  788. }
  789. }
  790. if (!ok) {
  791. this->screenTabs = true;
  792. this->changed = true;
  793. ok = Settings_read(this, SYSCONFDIR "/htoprc", host, /*checkWritability*/false);
  794. }
  795. if (!ok) {
  796. Settings_defaultMeters(this, host);
  797. Settings_defaultScreens(this);
  798. }
  799. this->ssIndex = 0;
  800. this->ss = this->screens[this->ssIndex];
  801. this->lastUpdate = 1;
  802. free(legacyDotfile);
  803. return this;
  804. }
  805. void ScreenSettings_invertSortOrder(ScreenSettings* this) {
  806. int* attr = (this->treeView) ? &(this->treeDirection) : &(this->direction);
  807. *attr = (*attr == 1) ? -1 : 1;
  808. }
  809. void ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) {
  810. if (this->treeViewAlwaysByPID || !this->treeView) {
  811. this->sortKey = sortKey;
  812. this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
  813. this->treeView = false;
  814. } else {
  815. this->treeSortKey = sortKey;
  816. this->treeDirection = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;
  817. }
  818. }
  819. static bool readonly = false;
  820. void Settings_enableReadonly(void) {
  821. readonly = true;
  822. }
  823. bool Settings_isReadonly(void) {
  824. return readonly;
  825. }
  826. void Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout) {
  827. unsigned int oldColumns = HeaderLayout_getColumns(this->hLayout);
  828. unsigned int newColumns = HeaderLayout_getColumns(hLayout);
  829. if (newColumns > oldColumns) {
  830. this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));
  831. memset(this->hColumns + oldColumns, 0, (newColumns - oldColumns) * sizeof(MeterColumnSetting));
  832. } else if (newColumns < oldColumns) {
  833. for (unsigned int i = newColumns; i < oldColumns; i++) {
  834. if (this->hColumns[i].names) {
  835. for (size_t j = 0; j < this->hColumns[i].len; j++)
  836. free(this->hColumns[i].names[j]);
  837. free(this->hColumns[i].names);
  838. }
  839. free(this->hColumns[i].modes);
  840. }
  841. this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));
  842. }
  843. this->hLayout = hLayout;
  844. this->changed = true;
  845. }