blob: a352e4e3f14e21124876e101b46e62845fe9c09b [file] [log] [blame]
Nathan Scott36389fb2021-03-23 15:04:54 +11001/*
2htop - CommandLine.c
3(C) 2004-2011 Hisham H. Muhammad
4(C) 2020-2021 htop dev team
Daniel Lange94ad1112021-09-22 11:33:00 +02005Released under the GNU GPLv2+, see the COPYING file
Nathan Scott36389fb2021-03-23 15:04:54 +11006in the source distribution for its full text.
7*/
8
9#include "config.h" // IWYU pragma: keep
10
11#include "CommandLine.h"
12
13#include <assert.h>
14#include <getopt.h>
15#include <locale.h>
16#include <stdbool.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <time.h>
21#include <unistd.h>
22
23#include "Action.h"
24#include "CRT.h"
Sohaib Mohamed6f2021f2021-07-11 03:11:29 +020025#include "DynamicColumn.h"
Nathan Scottf0ed0fd2021-06-23 17:44:56 +100026#include "DynamicMeter.h"
Nathan Scott36389fb2021-03-23 15:04:54 +110027#include "Hashtable.h"
28#include "Header.h"
29#include "IncSet.h"
30#include "MainPanel.h"
31#include "MetersPanel.h"
32#include "Panel.h"
33#include "Platform.h"
34#include "Process.h"
35#include "ProcessList.h"
36#include "ProvideCurses.h"
37#include "ScreenManager.h"
38#include "Settings.h"
39#include "UsersTable.h"
40#include "XUtils.h"
41
42
43static void printVersionFlag(const char* name) {
44 printf("%s " VERSION "\n", name);
45}
46
47static void printHelpFlag(const char* name) {
48 printf("%s " VERSION "\n"
Benny Baumann68edf922021-07-14 19:20:16 +020049 COPYRIGHT "\n"
Daniel Lange94ad1112021-09-22 11:33:00 +020050 "Released under the GNU GPLv2+.\n\n"
Benny Baumann68edf922021-07-14 19:20:16 +020051 "-C --no-color Use a monochrome color scheme\n"
52 "-d --delay=DELAY Set the delay between updates, in tenths of seconds\n"
53 "-F --filter=FILTER Show only the commands matching the given filter\n"
54 "-h --help Print this help screen\n"
55 "-H --highlight-changes[=DELAY] Highlight new and old processes\n"
56 "-M --no-mouse Disable the mouse\n"
57 "-p --pid=PID[,PID,PID...] Show only the given PIDs\n"
58 " --readonly Disable all system and process changing features\n"
59 "-s --sort-key=COLUMN Sort by COLUMN in list view (try --sort-key=help for a list)\n"
60 "-t --tree Show the tree view (can be combined with -s)\n"
61 "-u --user[=USERNAME] Show only processes for a given user (or $USER)\n"
62 "-U --no-unicode Do not use unicode but plain ASCII\n"
63 "-V --version Print version info\n", name);
Nathan Scott36389fb2021-03-23 15:04:54 +110064 Platform_longOptionsUsage(name);
65 printf("\n"
Benny Baumann68edf922021-07-14 19:20:16 +020066 "Long options may be passed with a single dash.\n\n"
67 "Press F1 inside %s for online help.\n"
68 "See 'man %s' for more information.\n", name, name);
Nathan Scott36389fb2021-03-23 15:04:54 +110069}
70
71// ----------------------------------------
72
73typedef struct CommandLineSettings_ {
74 Hashtable* pidMatchList;
75 char* commFilter;
76 uid_t userId;
77 int sortKey;
78 int delay;
79 bool useColors;
80 bool enableMouse;
81 bool treeView;
82 bool allowUnicode;
83 bool highlightChanges;
84 int highlightDelaySecs;
Christian Göttsche36880cd2021-01-21 20:27:37 +010085 bool readonly;
Nathan Scott36389fb2021-03-23 15:04:54 +110086} CommandLineSettings;
87
88static CommandLineSettings parseArguments(const char* program, int argc, char** argv) {
89
90 CommandLineSettings flags = {
91 .pidMatchList = NULL,
92 .commFilter = NULL,
93 .userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2))
94 .sortKey = 0,
95 .delay = -1,
96 .useColors = true,
97 .enableMouse = true,
98 .treeView = false,
99 .allowUnicode = true,
100 .highlightChanges = false,
101 .highlightDelaySecs = -1,
Christian Göttsche36880cd2021-01-21 20:27:37 +0100102 .readonly = false,
Nathan Scott36389fb2021-03-23 15:04:54 +1100103 };
104
105 const struct option long_opts[] =
106 {
107 {"help", no_argument, 0, 'h'},
108 {"version", no_argument, 0, 'V'},
109 {"delay", required_argument, 0, 'd'},
110 {"sort-key", required_argument, 0, 's'},
111 {"user", optional_argument, 0, 'u'},
112 {"no-color", no_argument, 0, 'C'},
113 {"no-colour", no_argument, 0, 'C'},
114 {"no-mouse", no_argument, 0, 'M'},
115 {"no-unicode", no_argument, 0, 'U'},
116 {"tree", no_argument, 0, 't'},
117 {"pid", required_argument, 0, 'p'},
118 {"filter", required_argument, 0, 'F'},
119 {"highlight-changes", optional_argument, 0, 'H'},
Christian Göttsche36880cd2021-01-21 20:27:37 +0100120 {"readonly", no_argument, 0, 128},
Nathan Scott36389fb2021-03-23 15:04:54 +1100121 PLATFORM_LONG_OPTIONS
Benny Baumann0d85af22021-07-14 19:18:27 +0200122 {0, 0, 0, 0}
Nathan Scott36389fb2021-03-23 15:04:54 +1100123 };
124
Benny Baumann0d85af22021-07-14 19:18:27 +0200125 int opt, opti = 0;
Nathan Scott36389fb2021-03-23 15:04:54 +1100126 /* Parse arguments */
127 while ((opt = getopt_long(argc, argv, "hVMCs:td:u::Up:F:H::", long_opts, &opti))) {
Benny Baumanne7f8d7b2021-07-14 19:11:18 +0200128 if (opt == EOF)
129 break;
Nathan Scott36389fb2021-03-23 15:04:54 +1100130 switch (opt) {
131 case 'h':
132 printHelpFlag(program);
133 exit(0);
134 case 'V':
135 printVersionFlag(program);
136 exit(0);
137 case 's':
138 assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
139 if (String_eq(optarg, "help")) {
140 for (int j = 1; j < LAST_PROCESSFIELD; j++) {
141 const char* name = Process_fields[j].name;
142 const char* description = Process_fields[j].description;
143 if (name) printf("%19s %s\n", name, description);
144 }
145 exit(0);
146 }
147 flags.sortKey = 0;
148 for (int j = 1; j < LAST_PROCESSFIELD; j++) {
149 if (Process_fields[j].name == NULL)
150 continue;
151 if (String_eq(optarg, Process_fields[j].name)) {
152 flags.sortKey = j;
153 break;
154 }
155 }
156 if (flags.sortKey == 0) {
157 fprintf(stderr, "Error: invalid column \"%s\".\n", optarg);
158 exit(1);
159 }
160 break;
161 case 'd':
162 if (sscanf(optarg, "%16d", &(flags.delay)) == 1) {
163 if (flags.delay < 1) flags.delay = 1;
164 if (flags.delay > 100) flags.delay = 100;
165 } else {
166 fprintf(stderr, "Error: invalid delay value \"%s\".\n", optarg);
167 exit(1);
168 }
169 break;
170 case 'u':
171 {
172 const char *username = optarg;
173 if (!username && optind < argc && argv[optind] != NULL &&
174 (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
175 username = argv[optind++];
176 }
177
178 if (!username) {
179 flags.userId = geteuid();
180 } else if (!Action_setUserOnly(username, &(flags.userId))) {
181 fprintf(stderr, "Error: invalid user \"%s\".\n", username);
182 exit(1);
183 }
184 break;
185 }
186 case 'C':
187 flags.useColors = false;
188 break;
189 case 'M':
niae8f27eb2021-07-14 21:07:43 +0200190#ifdef HAVE_GETMOUSE
Nathan Scott36389fb2021-03-23 15:04:54 +1100191 flags.enableMouse = false;
niae8f27eb2021-07-14 21:07:43 +0200192#endif
Nathan Scott36389fb2021-03-23 15:04:54 +1100193 break;
194 case 'U':
195 flags.allowUnicode = false;
196 break;
197 case 't':
198 flags.treeView = true;
199 break;
200 case 'p': {
201 assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */
202 char* argCopy = xStrdup(optarg);
203 char* saveptr;
204 const char* pid = strtok_r(argCopy, ",", &saveptr);
205
206 if (!flags.pidMatchList) {
207 flags.pidMatchList = Hashtable_new(8, false);
208 }
209
210 while(pid) {
211 unsigned int num_pid = atoi(pid);
212 // deepcode ignore CastIntegerToAddress: we just want a non-NUll pointer here
213 Hashtable_put(flags.pidMatchList, num_pid, (void *) 1);
214 pid = strtok_r(NULL, ",", &saveptr);
215 }
216 free(argCopy);
217
218 break;
219 }
220 case 'F': {
221 assert(optarg);
222 free_and_xStrdup(&flags.commFilter, optarg);
223 break;
224 }
225 case 'H': {
226 const char *delay = optarg;
227 if (!delay && optind < argc && argv[optind] != NULL &&
228 (argv[optind][0] != '\0' && argv[optind][0] != '-')) {
229 delay = argv[optind++];
230 }
231 if (delay) {
232 if (sscanf(delay, "%16d", &(flags.highlightDelaySecs)) == 1) {
233 if (flags.highlightDelaySecs < 1)
234 flags.highlightDelaySecs = 1;
235 } else {
236 fprintf(stderr, "Error: invalid highlight delay value \"%s\".\n", delay);
237 exit(1);
238 }
239 }
240 flags.highlightChanges = true;
241 break;
242 }
Christian Göttsche36880cd2021-01-21 20:27:37 +0100243 case 128:
244 flags.readonly = true;
245 break;
Nathan Scott36389fb2021-03-23 15:04:54 +1100246
247 default:
248 if (Platform_getLongOption(opt, argc, argv) == false)
249 exit(1);
250 break;
251 }
252 }
253 return flags;
254}
255
Nathan Scott36756112021-04-08 09:26:48 +1000256static void CommandLine_delay(ProcessList* pl, unsigned long millisec) {
Nathan Scott36389fb2021-03-23 15:04:54 +1100257 struct timespec req = {
258 .tv_sec = 0,
259 .tv_nsec = millisec * 1000000L
260 };
Nathan Scott36756112021-04-08 09:26:48 +1000261 while (nanosleep(&req, &req) == -1)
Nathan Scott36389fb2021-03-23 15:04:54 +1100262 continue;
Nathan Scott36756112021-04-08 09:26:48 +1000263 Platform_gettime_realtime(&pl->realtime, &pl->realtimeMs);
Nathan Scott36389fb2021-03-23 15:04:54 +1100264}
265
266static void setCommFilter(State* state, char** commFilter) {
267 ProcessList* pl = state->pl;
268 IncSet* inc = state->mainPanel->inc;
269
270 IncSet_setFilter(inc, *commFilter);
271 pl->incFilter = IncSet_filter(inc);
272
273 free(*commFilter);
274 *commFilter = NULL;
275}
276
277int CommandLine_run(const char* name, int argc, char** argv) {
278
279 /* initialize locale */
280 const char* lc_ctype;
281 if ((lc_ctype = getenv("LC_CTYPE")) || (lc_ctype = getenv("LC_ALL")))
282 setlocale(LC_CTYPE, lc_ctype);
283 else
284 setlocale(LC_CTYPE, "");
285
286 CommandLineSettings flags = parseArguments(name, argc, argv);
287
Christian Göttsche36880cd2021-01-21 20:27:37 +0100288 if (flags.readonly)
289 Settings_enableReadonly();
290
Nathan Scott36389fb2021-03-23 15:04:54 +1100291 Platform_init();
292
293 Process_setupColumnWidths();
294
295 UsersTable* ut = UsersTable_new();
Sohaib Mohamed6f2021f2021-07-11 03:11:29 +0200296 Hashtable* dc = DynamicColumns_new();
Nathan Scottf0ed0fd2021-06-23 17:44:56 +1000297 Hashtable* dm = DynamicMeters_new();
Sohaib Mohamed6f2021f2021-07-11 03:11:29 +0200298 if (!dc)
299 dc = Hashtable_new(0, true);
Nathan Scott36389fb2021-03-23 15:04:54 +1100300
Sohaib Mohamed6f2021f2021-07-11 03:11:29 +0200301 ProcessList* pl = ProcessList_new(ut, dm, dc, flags.pidMatchList, flags.userId);
302
303 Settings* settings = Settings_new(pl->activeCPUs, dc);
Nathan Scott36389fb2021-03-23 15:04:54 +1100304 pl->settings = settings;
305
306 Header* header = Header_new(pl, settings, 2);
307
308 Header_populateFromSettings(header);
309
310 if (flags.delay != -1)
311 settings->delay = flags.delay;
312 if (!flags.useColors)
313 settings->colorScheme = COLORSCHEME_MONOCHROME;
niae8f27eb2021-07-14 21:07:43 +0200314#ifdef HAVE_GETMOUSE
Nathan Scott36389fb2021-03-23 15:04:54 +1100315 if (!flags.enableMouse)
316 settings->enableMouse = false;
niae8f27eb2021-07-14 21:07:43 +0200317#endif
Nathan Scott36389fb2021-03-23 15:04:54 +1100318 if (flags.treeView)
319 settings->treeView = true;
320 if (flags.highlightChanges)
321 settings->highlightChanges = true;
322 if (flags.highlightDelaySecs != -1)
323 settings->highlightDelaySecs = flags.highlightDelaySecs;
324 if (flags.sortKey > 0) {
325 // -t -s <key> means "tree sorted by key"
326 // -s <key> means "list sorted by key" (previous existing behavior)
327 if (!flags.treeView) {
328 settings->treeView = false;
329 }
330 Settings_setSortKey(settings, flags.sortKey);
331 }
332
333 CRT_init(settings, flags.allowUnicode);
334
335 MainPanel* panel = MainPanel_new();
336 ProcessList_setPanel(pl, (Panel*) panel);
337
338 MainPanel_updateTreeFunctions(panel, settings->treeView);
339
340 State state = {
341 .settings = settings,
342 .ut = ut,
343 .pl = pl,
344 .mainPanel = panel,
345 .header = header,
346 .pauseProcessUpdate = false,
347 .hideProcessSelection = false,
348 };
349
350 MainPanel_setState(panel, &state);
351 if (flags.commFilter)
352 setCommFilter(&state, &(flags.commFilter));
353
354 ScreenManager* scr = ScreenManager_new(header, settings, &state, true);
355 ScreenManager_add(scr, (Panel*) panel, -1);
356
357 ProcessList_scan(pl, false);
Nathan Scott36756112021-04-08 09:26:48 +1000358 CommandLine_delay(pl, 75);
Nathan Scott36389fb2021-03-23 15:04:54 +1100359 ProcessList_scan(pl, false);
360
361 if (settings->allBranchesCollapsed)
362 ProcessList_collapseAllBranches(pl);
363
364 ScreenManager_run(scr, NULL, NULL);
365
366 attron(CRT_colors[RESET_COLOR]);
Benny Baumann0d85af22021-07-14 19:18:27 +0200367 mvhline(LINES - 1, 0, ' ', COLS);
Nathan Scott36389fb2021-03-23 15:04:54 +1100368 attroff(CRT_colors[RESET_COLOR]);
369 refresh();
370
371 Platform_done();
372
373 CRT_done();
374
375 if (settings->changed) {
Christian Göttsche1f5f40c2021-05-16 19:55:31 +0200376 int r = Settings_write(settings, false);
Nathan Scott36389fb2021-03-23 15:04:54 +1100377 if (r < 0)
378 fprintf(stderr, "Can not save configuration to %s: %s\n", settings->filename, strerror(-r));
379 }
380
381 Header_delete(header);
382 ProcessList_delete(pl);
383
384 ScreenManager_delete(scr);
385 MetersPanel_cleanup();
386
387 UsersTable_delete(ut);
Nathan Scott36389fb2021-03-23 15:04:54 +1100388
389 if (flags.pidMatchList)
390 Hashtable_delete(flags.pidMatchList);
391
Christian Göttsche68460b22021-08-14 19:52:26 +0200392 CRT_resetSignalHandlers();
393
Sohaib Mohamed6f2021f2021-07-11 03:11:29 +0200394 /* Delete these last, since they can get accessed in the crash handler */
Christian Göttsche1f5f40c2021-05-16 19:55:31 +0200395 Settings_delete(settings);
Nathan Scottc0c2bb92021-09-03 12:11:31 +1000396 DynamicColumns_delete(dc);
397 DynamicMeters_delete(dm);
Christian Göttsche1f5f40c2021-05-16 19:55:31 +0200398
Nathan Scott36389fb2021-03-23 15:04:54 +1100399 return 0;
400}