options.c revision c5c744a3b5a6e0d1f4826fabb31ffab2ec0170f3
1#if HAVE_CONFIG_H
2#include "config.h"
3#endif
4
5#include <string.h>
6#include <stdlib.h>
7#include <unistd.h>
8#include <fcntl.h>
9#include <errno.h>
10#include <limits.h>
11#include <sys/ioctl.h>
12
13#if HAVE_GETOPT_H
14#include <getopt.h>
15#endif
16
17#include "common.h"
18
19#ifndef SYSCONFDIR
20#define SYSCONFDIR "/etc"
21#endif
22
23#define SYSTEM_CONFIG_FILE SYSCONFDIR "/ltrace.conf"
24#define USER_CONFIG_FILE "~/.ltrace.conf"
25
26struct options_t options = {
27	.align    = DEFAULT_ALIGN,    /* alignment column for results */
28	.user     = NULL,             /* username to run command as */
29	.syscalls = 0,                /* display syscalls */
30	.libcalls = 1,                /* display library calls */
31#ifdef USE_DEMANGLE
32	.demangle = 0,                /* Demangle low-level symbol names */
33#endif
34	.indent = 0,                  /* indent output according to program flow */
35	.output = NULL,               /* output to a specific file */
36	.summary = 0,                 /* Report a summary on program exit */
37	.debug = 0,                   /* debug */
38	.arraylen = DEFAULT_ARRAYLEN, /* maximum # array elements to print */
39	.strlen = DEFAULT_STRLEN,     /* maximum # of bytes printed in strings */
40	.follow = 0,                  /* trace child processes */
41};
42
43char *library[MAX_LIBRARIES];
44int library_num = 0;
45static char *progname;		/* Program name (`ltrace') */
46int opt_i = 0;			/* instruction pointer */
47int opt_r = 0;			/* print relative timestamp */
48int opt_t = 0;			/* print absolute timestamp */
49int opt_T = 0;			/* show the time spent inside each call */
50
51/* List of pids given to option -p: */
52struct opt_p_t *opt_p = NULL;	/* attach to process with a given pid */
53
54/* List of function names given to option -e: */
55struct opt_e_t *opt_e = NULL;
56int opt_e_enable = 1;
57
58/* List of global function names given to -x: */
59struct opt_x_t *opt_x = NULL;
60
61/* List of filenames give to option -F: */
62struct opt_F_t *opt_F = NULL;	/* alternate configuration file(s) */
63
64#ifdef PLT_REINITALISATION_BP
65/* Set a break on the routine named here in order to re-initialize breakpoints
66   after all the PLTs have been initialzed */
67char *PLTs_initialized_by_here = PLT_REINITALISATION_BP;
68#endif
69
70static void
71err_usage(void) {
72#if HAVE_GETOPT_LONG
73	fprintf(stderr, "Try `%s --help' for more information\n", progname);
74#else
75	fprintf(stderr, "Try `%s -h' for more information\n", progname);
76#endif
77	exit(1);
78}
79
80static void
81usage(void) {
82#if !(HAVE_GETOPT || HAVE_GETOPT_LONG)
83	fprintf(stdout, "Usage: %s [command [arg ...]]\n"
84		"Trace library calls of a given program.\n\n", progname);
85#else
86	fprintf(stdout, "Usage: %s [option ...] [command [arg ...]]\n"
87		"Trace library calls of a given program.\n\n"
88# if HAVE_GETOPT_LONG
89		"  -a, --align=COLUMN  align return values in a secific column.\n"
90# else
91		"  -a COLUMN           align return values in a secific column.\n"
92# endif
93		"  -A ARRAYLEN         maximum number of array elements to print.\n"
94		"  -c                  count time and calls, and report a summary on exit.\n"
95# ifdef USE_DEMANGLE
96#  if HAVE_GETOPT_LONG
97		"  -C, --demangle      decode low-level symbol names into user-level names.\n"
98#  else
99		"  -C                  decode low-level symbol names into user-level names.\n"
100#  endif
101# endif
102# if HAVE_GETOPT_LONG
103		"  -D, --debug=LEVEL   enable debugging (see -Dh or --debug=help).\n"
104# else
105		"  -d LEVEL            enable debugging (see -Dh).\n"
106# endif
107# if HAVE_GETOPT_LONG
108		"  -Dh, --debug=help   show help on debugging.\n"
109# else
110		"  -Dh                 show help on debugging.\n"
111# endif
112		"  -e expr             modify which events to trace.\n"
113		"  -f                  trace children (fork() and clone()).\n"
114# if HAVE_GETOPT_LONG
115		"  -F, --config=FILE   load alternate configuration file (may be repeated).\n"
116# else
117		"  -F FILE             load alternate configuration file (may be repeated).\n"
118# endif
119# if HAVE_GETOPT_LONG
120		"  -h, --help          display this help and exit.\n"
121# else
122		"  -h                  display this help and exit.\n"
123# endif
124		"  -i                  print instruction pointer at time of library call.\n"
125#  if HAVE_GETOPT_LONG
126		"  -l, --library=FILE  print library calls from this library only.\n"
127#  else
128		"  -l FILE             print library calls from this library only.\n"
129#  endif
130		"  -L                  do NOT display library calls.\n"
131# if HAVE_GETOPT_LONG
132		"  -n, --indent=NR     indent output by NR spaces for each call level nesting.\n"
133# else
134		"  -n NR               indent output by NR spaces for each call level nesting.\n"
135# endif
136# if HAVE_GETOPT_LONG
137		"  -o, --output=FILE   write the trace output to that file.\n"
138# else
139		"  -o FILE             write the trace output to that file.\n"
140# endif
141		"  -p PID              attach to the process with the process ID pid.\n"
142		"  -r                  print relative timestamps.\n"
143		"  -s STRLEN           specify the maximum string size to print.\n"
144		"  -S                  display system calls.\n"
145		"  -t, -tt, -ttt       print absolute timestamps.\n"
146		"  -T                  show the time spent inside each call.\n"
147		"  -u USERNAME         run command with the userid, groupid of username.\n"
148# if HAVE_GETOPT_LONG
149		"  -V, --version       output version information and exit.\n"
150# else
151		"  -V                  output version information and exit.\n"
152# endif
153		"  -x NAME             treat the global NAME like a library subroutine.\n"
154#ifdef PLT_REINITALISATION_BP
155		"  -X NAME             same as -x; and PLT's will be initialized by here.\n"
156#endif
157		"\nReport bugs to ltrace-devel@lists.alioth.debian.org\n",
158		progname);
159#endif
160}
161
162static void
163usage_debug(void) {
164	fprintf(stdout, "%s debugging option, --debug=<octal> or -D<octal>:\n", progname);
165	fprintf(stdout,
166			"\n"
167			" number  ref. in source   description\n"
168			"      1   general           Generally helpful progress information\n"
169			"     10   event             Shows every event received by a traced process\n"
170			"     20   process           Shows actions carried upon a traced processes\n"
171			"     40   function          Shows every entry to internal functions\n"
172			"\n"
173			"Debugging options are mixed using bitwise-or.\n"
174			"Note that the meanings and values are subject to change.\n"
175		   );
176}
177
178static char *
179search_for_command(char *filename) {
180	static char pathname[PATH_MAX];
181	char *path;
182	int m, n;
183
184	if (strchr(filename, '/')) {
185		return filename;
186	}
187	for (path = getenv("PATH"); path && *path; path += m) {
188		if (strchr(path, ':')) {
189			n = strchr(path, ':') - path;
190			m = n + 1;
191		} else {
192			m = n = strlen(path);
193		}
194		if (n + strlen(filename) + 1 >= PATH_MAX) {
195			fprintf(stderr, "Error: filename too long\n");
196			exit(1);
197		}
198		strncpy(pathname, path, n);
199		if (n && pathname[n - 1] != '/') {
200			pathname[n++] = '/';
201		}
202		strcpy(pathname + n, filename);
203		if (!access(pathname, X_OK)) {
204			return pathname;
205		}
206	}
207	return filename;
208}
209
210static void
211guess_cols(void) {
212	struct winsize ws;
213	char *c;
214
215	options.align = DEFAULT_ALIGN;
216	c = getenv("COLUMNS");
217	if (c && *c) {
218		char *endptr;
219		int cols;
220		cols = strtol(c, &endptr, 0);
221		if (cols > 0 && !*endptr) {
222			options.align = cols * 5 / 8;
223		}
224	} else if (ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col > 0) {
225		options.align = ws.ws_col * 5 / 8;
226	} else if (ioctl(2, TIOCGWINSZ, &ws) != -1 && ws.ws_col > 0) {
227		options.align = ws.ws_col * 5 / 8;
228	}
229}
230
231char **
232process_options(int argc, char **argv) {
233	progname = argv[0];
234	options.output = stderr;
235
236	guess_cols();
237
238#if HAVE_GETOPT || HAVE_GETOPT_LONG
239	while (1) {
240		int c;
241		char *p;
242#if HAVE_GETOPT_LONG
243		int option_index = 0;
244		static struct option long_options[] = {
245			{"align", 1, 0, 'a'},
246			{"config", 1, 0, 'F'},
247			{"debug", 1, 0, 'D'},
248# ifdef USE_DEMANGLE
249			{"demangle", 0, 0, 'C'},
250#endif
251			{"indent", 1, 0, 'n'},
252			{"help", 0, 0, 'h'},
253			{"library", 1, 0, 'l'},
254			{"output", 1, 0, 'o'},
255			{"version", 0, 0, 'V'},
256			{0, 0, 0, 0}
257		};
258		c = getopt_long(argc, argv, "+cfhiLrStTV"
259# ifdef USE_DEMANGLE
260				"C"
261# endif
262				"a:A:D:e:F:l:n:o:p:s:u:x:X:", long_options,
263				&option_index);
264#else
265		c = getopt(argc, argv, "+cfhiLrStTV"
266# ifdef USE_DEMANGLE
267				"C"
268# endif
269				"a:A:D:e:F:l:n:o:p:s:u:x:X:");
270#endif
271		if (c == -1) {
272			break;
273		}
274		switch (c) {
275		case 'a':
276			options.align = atoi(optarg);
277			break;
278		case 'A':
279			options.arraylen = atoi(optarg);
280			break;
281		case 'c':
282			options.summary++;
283			break;
284#ifdef USE_DEMANGLE
285		case 'C':
286			options.demangle++;
287			break;
288#endif
289		case 'D':
290			if (optarg[0]=='h') {
291				usage_debug();
292				exit(0);
293			}
294			options.debug = strtoul(optarg,&p,8);
295			if (*p) {
296				fprintf(stderr, "%s: --debug requires an octal argument\n", progname);
297				err_usage();
298			}
299			break;
300		case 'e':
301			{
302				char *str_e = strdup(optarg);
303				if (!str_e) {
304					perror("ltrace: strdup");
305					exit(1);
306				}
307				if (str_e[0] == '!') {
308					opt_e_enable = 0;
309					str_e++;
310				}
311				while (*str_e) {
312					struct opt_e_t *tmp;
313					char *str2 = strchr(str_e, ',');
314					if (str2) {
315						*str2 = '\0';
316					}
317					tmp = malloc(sizeof(struct opt_e_t));
318					if (!tmp) {
319						perror("ltrace: malloc");
320						exit(1);
321					}
322					tmp->name = str_e;
323					tmp->next = opt_e;
324					opt_e = tmp;
325					if (str2) {
326						str_e = str2 + 1;
327					} else {
328						break;
329					}
330				}
331				break;
332			}
333		case 'f':
334			options.follow = 1;
335			break;
336		case 'F':
337			{
338				struct opt_F_t *tmp = malloc(sizeof(struct opt_F_t));
339				if (!tmp) {
340					perror("ltrace: malloc");
341					exit(1);
342				}
343				tmp->filename = strdup(optarg);
344				tmp->next = opt_F;
345				opt_F = tmp;
346				break;
347			}
348		case 'h':
349			usage();
350			exit(0);
351		case 'i':
352			opt_i++;
353			break;
354		case 'l':
355			if (library_num == MAX_LIBRARIES) {
356				fprintf(stderr,
357					"Too many libraries.  Maximum is %i.\n",
358					MAX_LIBRARIES);
359				exit(1);
360			}
361			library[library_num++] = optarg;
362			break;
363		case 'L':
364			options.libcalls = 0;
365			break;
366		case 'n':
367			options.indent = atoi(optarg);
368			break;
369		case 'o':
370			options.output = fopen(optarg, "w");
371			if (!options.output) {
372				fprintf(stderr,
373					"Can't open %s for output: %s\n",
374					optarg, strerror(errno));
375				exit(1);
376			}
377			setvbuf(options.output, (char *)NULL, _IOLBF, 0);
378			fcntl(fileno(options.output), F_SETFD, FD_CLOEXEC);
379			break;
380		case 'p':
381			{
382				struct opt_p_t *tmp = malloc(sizeof(struct opt_p_t));
383				if (!tmp) {
384					perror("ltrace: malloc");
385					exit(1);
386				}
387				tmp->pid = atoi(optarg);
388				tmp->next = opt_p;
389				opt_p = tmp;
390				break;
391			}
392		case 'r':
393			opt_r++;
394			break;
395		case 's':
396			options.strlen = atoi(optarg);
397			break;
398		case 'S':
399			options.syscalls = 1;
400			break;
401		case 't':
402			opt_t++;
403			break;
404		case 'T':
405			opt_T++;
406			break;
407		case 'u':
408			options.user = optarg;
409			break;
410		case 'V':
411			printf("ltrace version " PACKAGE_VERSION ".\n"
412					"Copyright (C) 1997-2009 Juan Cespedes <cespedes@debian.org>.\n"
413					"This is free software; see the GNU General Public Licence\n"
414					"version 2 or later for copying conditions.  There is NO warranty.\n");
415			exit(0);
416		case 'X':
417#ifdef PLT_REINITALISATION_BP
418			PLTs_initialized_by_here = optarg;
419#else
420			fprintf(stderr, "WARNING: \"-X\" not used for this "
421				"architecture: assuming you meant \"-x\"\n");
422#endif
423			/* Fall Thru */
424
425		case 'x':
426			{
427				struct opt_x_t *p = opt_x;
428
429				/* First, check for duplicate. */
430				while (p && strcmp(p->name, optarg)) {
431					p = p->next;
432				}
433				if (p) {
434					break;
435				}
436
437				/* If not duplicate, add to list. */
438				p = malloc(sizeof(struct opt_x_t));
439				if (!p) {
440					perror("ltrace: malloc");
441					exit(1);
442				}
443				p->name = optarg;
444				p->found = 0;
445				p->next = opt_x;
446				opt_x = p;
447				break;
448			}
449
450		default:
451			err_usage();
452		}
453	}
454	argc -= optind;
455	argv += optind;
456#endif
457
458	if (!opt_F) {
459		opt_F = malloc(sizeof(struct opt_F_t));
460		opt_F->next = malloc(sizeof(struct opt_F_t));
461		opt_F->next->next = NULL;
462		opt_F->filename = USER_CONFIG_FILE;
463		opt_F->next->filename = SYSTEM_CONFIG_FILE;
464	}
465	/* Reverse the config file list since it was built by
466	 * prepending, and it would make more sense to process the
467	 * files in the order they were given. Probably it would make
468	 * more sense to keep a tail pointer instead? */
469	{
470		struct opt_F_t *egg = NULL;
471		struct opt_F_t *chicken;
472		while (opt_F) {
473			chicken = opt_F->next;
474			opt_F->next = egg;
475			egg = opt_F;
476			opt_F = chicken;
477		}
478		opt_F = egg;
479	}
480
481	if (!opt_p && argc < 1) {
482		fprintf(stderr, "%s: too few arguments\n", progname);
483		err_usage();
484	}
485	if (opt_r && opt_t) {
486		fprintf(stderr, "%s: Incompatible options -r and -t\n",
487			progname);
488		err_usage();
489	}
490	if (argc > 0) {
491		command = search_for_command(argv[0]);
492	}
493	return &argv[0];
494}
495