1/*
2 * Copyright (c) 2008 Apple Computer, Inc.  All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License").  You may not use this file except in compliance with the
9 * License.  Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <assert.h>
27#include <ctype.h>
28#include <pwd.h>
29#include "options.h"
30#include "preferences.h"
31
32#include "log.h"
33
34enum {
35    OPT_COUNTING = 1,
36    OPT_FRAMEWORK_OFF,
37    OPT_FRAMEWORK_ON,
38    OPT_HELP,
39    OPT_ORDER,
40    OPT_SECONDARY_ORDER,
41    OPT_SLEEP,
42    OPT_INTERVAL,
43    OPT_SAMPLES,
44    OPT_NCOLS,
45    OPT_NPROCS,
46    OPT_STATS,
47    OPT_PID,
48    OPT_USER,
49    OPT_DEBUGLOG,
50    OPT_U_SORT,
51    OPT_SWAP,
52    OPT_MMR_OFF,
53    OPT_MMR_ON,
54    /*compat/deprecated options*/
55    OPT_ACCUM,
56    OPT_DELTA,
57    OPT_ABSOLUTE
58};
59
60enum {
61    TOP_OPTION_REQUIRED = 1, /* A -flag value pair is specified as required. */
62    TOP_OPTION_SET /* The option is a boolean to enable/disable something. */
63};
64
65
66struct top_option {
67    const char *option_string;
68    int option_flag;
69    int option_value;
70};
71
72/* Return true if found, and false if not. */
73/* The caller of this is expected to have initialized the *offset value to a valid argv array offset. */
74bool top_option_get(int argc, char *argv[], struct top_option *opts, int *offset, int *optvalue, char **argresult) {
75    int opti;
76
77    assert(*offset < argc);
78
79    /* Set the argument result (AKA the option value) to NULL. */
80    *argresult = NULL;
81    *optvalue = -1;
82
83    for(opti = 0; opts[opti].option_string; ++opti) {
84	if(TOP_OPTION_REQUIRED == opts[opti].option_flag) {
85	    /* First check for an exact match. */
86	    /* Otherwise check for a single char option with a value like -n4. */
87	    if(!strcmp(argv[*offset], opts[opti].option_string)) {
88		if((*offset + 1) >= argc) {
89		    /* The option was something like -stats without a value. */
90		    return false;
91		}
92		*argresult = argv[*offset + 1];
93		*optvalue = opts[opti].option_value;
94		*offset += 2;
95		return true;
96	    } else if(2 == strlen(opts[opti].option_string) && strlen(argv[*offset]) >= 2
97		      && opts[opti].option_string[0] == argv[*offset][0]
98		      && opts[opti].option_string[1] == argv[*offset][1]) {
99		/* We found a pattern like -n123 and the argresult should be 123. */
100		*argresult = argv[*offset] + 2;
101		*optvalue = opts[opti].option_value;
102		*offset += 1;
103		return true;
104	    }
105	} else {
106	    /* TOP_OPTION_SET */
107	    if(!strcmp(argv[*offset], opts[opti].option_string)) {
108		*optvalue = opts[opti].option_value;
109		*offset += 1;
110		return true;
111	    }
112	}
113    }
114
115    return false;
116}
117
118/* Note: it's important that options with the same prefix have the long option in this struct first. */
119static struct top_option opts[] = {
120    {"-stats", TOP_OPTION_REQUIRED, OPT_STATS},
121    {"-ncols", TOP_OPTION_REQUIRED, OPT_NCOLS},
122    {"-pid", TOP_OPTION_REQUIRED, OPT_PID},
123    {"-user", TOP_OPTION_REQUIRED, OPT_USER},
124    {"-c", TOP_OPTION_REQUIRED, OPT_COUNTING},
125    {"-F", TOP_OPTION_SET, OPT_FRAMEWORK_OFF},
126    {"-f", TOP_OPTION_SET, OPT_FRAMEWORK_ON},
127    {"-h", TOP_OPTION_SET, OPT_HELP},
128    {"-o", TOP_OPTION_REQUIRED, OPT_ORDER},
129    {"-O", TOP_OPTION_REQUIRED, OPT_SECONDARY_ORDER},
130    {"-s", TOP_OPTION_REQUIRED, OPT_SLEEP},
131    {"-i", TOP_OPTION_REQUIRED, OPT_INTERVAL},
132    {"-l", TOP_OPTION_REQUIRED, OPT_SAMPLES},
133    {"-n", TOP_OPTION_REQUIRED, OPT_NPROCS},
134    {"-U", TOP_OPTION_REQUIRED, OPT_USER},
135    {"-u", TOP_OPTION_SET, OPT_U_SORT},
136    {"-S", TOP_OPTION_SET, OPT_SWAP},
137    {"-R", TOP_OPTION_SET, OPT_MMR_OFF},
138    {"-r", TOP_OPTION_SET, OPT_MMR_ON},
139    /*compat/deprecated options*/
140    {"-a", TOP_OPTION_SET, OPT_ACCUM},
141    {"-d", TOP_OPTION_SET, OPT_DELTA},
142    {"-e", TOP_OPTION_SET, OPT_ABSOLUTE},
143    {NULL, 0, 0}
144};
145
146void top_options_init(void) {
147    /* do nothing */
148}
149
150void top_options_usage(FILE *fp, char *argv0) {
151    fprintf(fp,
152	    "%s usage: %s\n"
153	    "\t\t[-a | -d | -e | -c <mode>]\n"
154	    "\t\t[-F | -f]\n"
155	    "\t\t[-h]\n"
156	    "\t\t[-i <interval>]\n"
157	    "\t\t[-l <samples>]\n"
158	    "\t\t[-ncols <columns>]\n"
159	    "\t\t[-o <key>] [-O <secondaryKey>]\n"
160	    "\t\t[-R | -r]\n"
161	    "\t\t[-S]\n"
162	    "\t\t[-s <delay>]\n"
163	    "\t\t[-n <nprocs>]\n"
164	    "\t\t[-stats <key(s)>]\n"
165	    "\t\t[-pid <processid>]\n"
166	    "\t\t[-user <username>]\n"
167	    "\t\t[-U <username>]\n"
168	    "\t\t[-u]\n",
169	    argv0, argv0);
170
171    fprintf(fp, "\n");
172}
173
174static bool string_is_integer(const char *s) {
175    const char *b = s;
176    bool indicator = false;
177
178    /*skip whitespace*/
179    for(; *s && isspace((int)*s); ++s)
180	/*empty body*/;
181
182    if('-' == *s || '+' == *s) {
183	++s;
184	indicator = true;
185    }
186
187    for(; *s && isdigit((int)*s); ++s)
188	/*empty body*/;
189
190    /*
191     * At this point we could have trailing whitespace, but
192     * in the use case that is not a real problem.  If this is
193     * reused we might want the whitespace handled here.
194     */
195
196    if('\0' == *s && b != s) {
197	/* Check if it was just - or + */
198	if(indicator) {
199	    if((s - b) > 1) {
200		return true;
201	    } else {
202		return false;
203	    }
204	} else {
205	    return true;
206	}
207    }
208
209    return false;
210}
211
212/* Return true if an error occurred. */
213bool top_options_parse(int argc, char *argv[]) {
214    int offset = 1;
215
216    while(offset < argc) {
217	char *optarg;
218	int optvalue;
219
220	if(false == top_option_get(argc, argv, opts, &offset, &optvalue, &optarg)) {
221	    fprintf(stderr, "invalid option or syntax: %s\n", argv[offset]);
222	    return true;
223	}
224
225	switch(optvalue) {
226	case OPT_COUNTING:
227	    if(top_prefs_set_mode(optarg)) {
228		fprintf(stderr, "invalid argument for -c: %s\n", optarg);
229		return true;
230	    }
231	    break;
232
233	case OPT_FRAMEWORK_OFF:
234	    top_prefs_set_frameworks(false);
235	    break;
236
237	case OPT_FRAMEWORK_ON:
238	    top_prefs_set_frameworks(true);
239	    break;
240
241	case OPT_HELP:
242	    top_options_usage(stdout, argv[0]);
243	    exit(EXIT_SUCCESS);
244	    break;
245
246	case OPT_INTERVAL: {
247	    int n;
248
249	    if(!string_is_integer(optarg)) {
250		fprintf(stderr,
251			"invalid interval number (not an integer): %s\n",
252			optarg);
253		return true;
254	    }
255
256	    n = atoi(optarg);
257
258	    if(n < 1) {
259		fprintf(stderr, "invalid argument for -i: %s\n", optarg);
260		return true;
261	    }
262
263	    top_prefs_set_frameworks_interval(n);
264	}
265	    break;
266
267	case OPT_SAMPLES: {
268	    int s;
269
270	    if(!string_is_integer(optarg)) {
271		fprintf(stderr,
272			"invalid number of samples (not an integer): %s\n",
273			optarg);
274		return true;
275	    }
276
277	    s = atoi(optarg);
278
279	    if(s < 0) {
280		fprintf(stderr, "invalid number of samples: %d\n", s);
281		return true;
282	    }
283
284	    top_prefs_set_samples(s);
285	}
286	    break;
287
288	case OPT_NCOLS: {
289	    int n;
290
291	    if(!string_is_integer(optarg)) {
292		fprintf(stderr,
293			"invalid argument for -ncols (not an integer): %s\n",
294			optarg);
295		return true;
296	    }
297
298	    n = atoi(optarg);
299
300	    if(n < 1) {
301		fprintf(stderr, "-ncols requires an argument > 0\n");
302		return true;
303	    }
304
305	    top_prefs_set_ncols(n);
306	}
307	    break;
308
309	case OPT_NPROCS: {
310	    int n;
311
312	    if(!string_is_integer(optarg)) {
313		fprintf(stderr,
314			"invalid argument for -n (not an integer): %s\n",
315			optarg);
316		return true;
317	    }
318
319	    n = atoi(optarg);
320
321	    if(n < 0) {
322		fprintf(stderr, "-n argument may not be negative: %s\n",
323			optarg);
324		return true;
325	    }
326
327	    top_prefs_set_nprocs(n);
328	}
329	    break;
330
331	case OPT_ORDER:
332	    if(top_prefs_set_sort(optarg)) {
333		fprintf(stderr, "invalid argument -o: %s\n", optarg);
334 		return true;
335	    }
336	    break;
337
338	case OPT_SECONDARY_ORDER:
339	    if(top_prefs_set_secondary_sort(optarg)) {
340		fprintf(stderr, "invalid argument -O: %s\n", optarg);
341		return true;
342	    }
343	    break;
344
345	case OPT_SLEEP: {
346	    int s;
347
348	    if(!string_is_integer(optarg)) {
349		fprintf(stderr,
350			"invalid argument for sleep interval (not an integer):"
351			" %s\n",
352			optarg);
353		return true;
354	    }
355
356	    s = atoi(optarg);
357
358	    if(s < 0) {
359		fprintf(stderr, "invalid argument for -s: %s\n", optarg);
360		return true;
361	    }
362
363	    top_prefs_set_sleep(s);
364	}
365	    break;
366
367	case OPT_STATS:
368	    if(top_prefs_set_stats(optarg)) {
369		fprintf(stderr, "invalid argument for -stats: %s\n", optarg);
370		return true;
371	    }
372	    break;
373
374	case OPT_PID: {
375	    pid_t p;
376
377	    if(!string_is_integer(optarg)) {
378		fprintf(stderr,
379			"invalid -pid argument (not an integer): %s\n", optarg);
380		return true;
381	    }
382
383	    p = atoi(optarg);
384
385	    if(p < 0) {
386		fprintf(stderr, "pid arguments can not be < 0: %s\n",
387			optarg);
388		return true;
389	    }
390
391	    top_prefs_set_pid(p);
392	}
393	    break;
394
395	case OPT_USER: {
396	    struct passwd *pw;
397
398	    pw = getpwnam(optarg);
399
400	    if(NULL == pw) {
401		endpwent();
402		fprintf(stderr, "invalid user: %s\n", optarg);
403		return true;
404	    }
405
406	    top_prefs_set_user(optarg);
407	    top_prefs_set_user_uid(pw->pw_uid);
408
409	    endpwent();
410	}
411	    break;
412
413	case OPT_U_SORT: {
414	    if(top_prefs_set_sort("cpu")
415	       || top_prefs_set_secondary_sort("time")) {
416		fprintf(stderr,
417			"An unexpected error occurred while performing the -u "
418			"preference setting.\n");
419		abort();
420	    }
421	}
422	    break;
423
424	case OPT_MMR_OFF:
425	    top_prefs_set_mmr(false);
426	    break;
427
428	case OPT_MMR_ON:
429	    top_prefs_set_mmr(true);
430	    break;
431
432	case OPT_SWAP:
433	    top_prefs_set_swap(true);
434     	    break;
435
436	    /*compat/deprecated options*/
437	case OPT_ACCUM:
438	    if(top_prefs_set_mode("a")) {
439		fprintf(stderr,
440			"An internal top error has occurred unexpectedly!\n");
441		abort();
442	    }
443	    break;
444
445	case OPT_DELTA:
446	    if(top_prefs_set_mode("d")) {
447		fprintf(stderr,
448			"An internal top error has occurred unexpectedly!\n");
449		abort();
450	    }
451	    break;
452
453	case OPT_ABSOLUTE:
454	    if(top_prefs_set_mode("e")) {
455		fprintf(stderr,
456			"An internal top error has occurred unexpectedly!\n");
457		abort();
458	    }
459	    break;
460	} /*end switch*/
461    }
462
463    return false;
464}
465