1/* vi: set sw=4 ts=4: */
2/*
3 * Mini ps implementation(s) for busybox
4 *
5 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
6 * Fix for SELinux Support:(c)2007 Hiroshi Shinji <shiroshi@my.email.ne.jp>
7                           (c)2007 Yuichi Nakamura <ynakam@hitachisoft.jp>
8 *
9 * Licensed under the GPL version 2, see the file LICENSE in this tarball.
10 */
11
12#include "libbb.h"
13
14/* Absolute maximum on output line length */
15enum { MAX_WIDTH = 2*1024 };
16
17#if ENABLE_DESKTOP
18
19/* Print value to buf, max size+1 chars (including trailing '\0') */
20
21static void func_user(char *buf, int size, const procps_status_t *ps)
22{
23	safe_strncpy(buf, get_cached_username(ps->uid), size+1);
24}
25
26static void func_comm(char *buf, int size, const procps_status_t *ps)
27{
28	safe_strncpy(buf, ps->comm, size+1);
29}
30
31static void func_args(char *buf, int size, const procps_status_t *ps)
32{
33	read_cmdline(buf, size, ps->pid, ps->comm);
34}
35
36static void func_pid(char *buf, int size, const procps_status_t *ps)
37{
38	sprintf(buf, "%*u", size, ps->pid);
39}
40
41static void func_ppid(char *buf, int size, const procps_status_t *ps)
42{
43	sprintf(buf, "%*u", size, ps->ppid);
44}
45
46static void func_pgid(char *buf, int size, const procps_status_t *ps)
47{
48	sprintf(buf, "%*u", size, ps->pgid);
49}
50
51static void put_u(char *buf, int size, unsigned u)
52{
53	char buf5[5];
54	smart_ulltoa5( ((unsigned long long)u) << 10, buf5);
55	sprintf(buf, "%.*s", size, buf5);
56}
57
58static void func_vsz(char *buf, int size, const procps_status_t *ps)
59{
60	put_u(buf, size, ps->vsz);
61}
62
63static void func_rss(char *buf, int size, const procps_status_t *ps)
64{
65	put_u(buf, size, ps->rss);
66}
67
68static void func_tty(char *buf, int size, const procps_status_t *ps)
69{
70	buf[0] = '?';
71	buf[1] = '\0';
72	if (ps->tty_major) /* tty field of "0" means "no tty" */
73		snprintf(buf, size+1, "%u,%u", ps->tty_major, ps->tty_minor);
74}
75
76#if ENABLE_SELINUX
77static void func_label(char *buf, int size, const procps_status_t *ps)
78{
79	safe_strncpy(buf, ps->context ? ps->context : "unknown", size+1);
80}
81#endif
82
83/*
84static void func_nice(char *buf, int size, const procps_status_t *ps)
85{
86	ps->???
87}
88
89static void func_etime(char *buf, int size, const procps_status_t *ps)
90{
91	elapled time [[dd-]hh:]mm:ss
92}
93
94static void func_time(char *buf, int size, const procps_status_t *ps)
95{
96	cumulative time [[dd-]hh:]mm:ss
97}
98
99static void func_pcpu(char *buf, int size, const procps_status_t *ps)
100{
101}
102*/
103
104typedef struct {
105	uint16_t width;
106	char name[6];
107	const char *header;
108	void (*f)(char *buf, int size, const procps_status_t *ps);
109	int ps_flags;
110} ps_out_t;
111
112static const ps_out_t out_spec[] = {
113// Mandated by POSIX:
114	{ 8                  , "user"  ,"USER"   ,func_user  ,PSSCAN_UIDGID  },
115	{ 16                 , "comm"  ,"COMMAND",func_comm  ,PSSCAN_COMM    },
116	{ 256                , "args"  ,"COMMAND",func_args  ,PSSCAN_COMM    },
117	{ 5                  , "pid"   ,"PID"    ,func_pid   ,PSSCAN_PID     },
118	{ 5                  , "ppid"  ,"PPID"   ,func_ppid  ,PSSCAN_PPID    },
119	{ 5                  , "pgid"  ,"PGID"   ,func_pgid  ,PSSCAN_PGID    },
120//	{ sizeof("ELAPSED")-1, "etime" ,"ELAPSED",func_etime ,PSSCAN_        },
121//	{ sizeof("GROUP"  )-1, "group" ,"GROUP"  ,func_group ,PSSCAN_UIDGID  },
122//	{ sizeof("NI"     )-1, "nice"  ,"NI"     ,func_nice  ,PSSCAN_        },
123//	{ sizeof("%CPU"   )-1, "pcpu"  ,"%CPU"   ,func_pcpu  ,PSSCAN_        },
124//	{ sizeof("RGROUP" )-1, "rgroup","RGROUP" ,func_rgroup,PSSCAN_UIDGID  },
125//	{ sizeof("RUSER"  )-1, "ruser" ,"RUSER"  ,func_ruser ,PSSCAN_UIDGID  },
126//	{ sizeof("TIME"   )-1, "time"  ,"TIME"   ,func_time  ,PSSCAN_        },
127	{ 6                  , "tty"   ,"TT"     ,func_tty   ,PSSCAN_TTY     },
128	{ 4                  , "vsz"   ,"VSZ"    ,func_vsz   ,PSSCAN_VSZ     },
129// Not mandated by POSIX, but useful:
130	{ 4                  , "rss"   ,"RSS"    ,func_rss   ,PSSCAN_RSS     },
131#if ENABLE_SELINUX
132	{ 35                 , "label" ,"LABEL"  ,func_label ,PSSCAN_CONTEXT },
133#endif
134};
135
136#if ENABLE_SELINUX
137#define SELINIX_O_PREFIX "label,"
138#define DEFAULT_O_STR    SELINIX_O_PREFIX "pid,user" /* TODO: ,vsz,stat */ ",args"
139#else
140#define DEFAULT_O_STR    "pid,user" /* TODO: ,vsz,stat */ ",args"
141#endif
142
143struct globals {
144	ps_out_t* out;
145	int out_cnt;
146	int print_header;
147	int need_flags;
148	char *buffer;
149	unsigned terminal_width;
150	char default_o[sizeof(DEFAULT_O_STR)];
151};
152#define G (*(struct globals*)&bb_common_bufsiz1)
153#define out            (G.out           )
154#define out_cnt        (G.out_cnt       )
155#define print_header   (G.print_header  )
156#define need_flags     (G.need_flags    )
157#define buffer         (G.buffer        )
158#define terminal_width (G.terminal_width)
159#define default_o      (G.default_o     )
160
161static ps_out_t* new_out_t(void)
162{
163	int i = out_cnt++;
164	out = xrealloc(out, out_cnt * sizeof(*out));
165	return &out[i];
166}
167
168static const ps_out_t* find_out_spec(const char *name)
169{
170	int i;
171	for (i = 0; i < ARRAY_SIZE(out_spec); i++) {
172		if (!strcmp(name, out_spec[i].name))
173			return &out_spec[i];
174	}
175	bb_error_msg_and_die("bad -o argument '%s'", name);
176}
177
178static void parse_o(char* opt)
179{
180	ps_out_t* new;
181	char *comma, *equal;
182	while (1) {
183		comma = strchr(opt, ',');
184		equal = strchr(opt, '=');
185		if (comma && (!equal || equal > comma)) {
186			*comma = '\0';
187			*new_out_t() = *find_out_spec(opt);
188			*comma = ',';
189			opt = comma + 1;
190			continue;
191		}
192		break;
193	}
194	// opt points to last spec in comma separated list.
195	// This one can have =HEADER part.
196	new = new_out_t();
197	if (equal)
198		*equal = '\0';
199	*new = *find_out_spec(opt);
200	if (equal) {
201		*equal = '=';
202		new->header = equal + 1;
203		// POSIX: the field widths shall be ... at least as wide as
204		// the header text (default or overridden value).
205		// If the header text is null, such as -o user=,
206		// the field width shall be at least as wide as the
207		// default header text
208		if (new->header[0]) {
209			new->width = strlen(new->header);
210			print_header = 1;
211		}
212	} else
213		print_header = 1;
214}
215
216static void post_process(void)
217{
218	int i;
219	int width = 0;
220	for (i = 0; i < out_cnt; i++) {
221		need_flags |= out[i].ps_flags;
222		if (out[i].header[0]) {
223			print_header = 1;
224		}
225		width += out[i].width + 1; /* "FIELD " */
226	}
227#if ENABLE_SELINUX
228	if (!is_selinux_enabled())
229		need_flags &= ~PSSCAN_CONTEXT;
230#endif
231	buffer = xmalloc(width + 1); /* for trailing \0 */
232}
233
234static void format_header(void)
235{
236	int i;
237	ps_out_t* op;
238	char *p;
239
240	if (!print_header)
241		return;
242	p = buffer;
243	i = 0;
244	if (out_cnt) {
245		while (1) {
246			op = &out[i];
247			if (++i == out_cnt) /* do not pad last field */
248				break;
249			p += sprintf(p, "%-*s ", op->width, op->header);
250		}
251		strcpy(p, op->header);
252	}
253	printf("%.*s\n", terminal_width, buffer);
254}
255
256static void format_process(const procps_status_t *ps)
257{
258	int i, len;
259	char *p = buffer;
260	i = 0;
261	if (out_cnt) while (1) {
262		out[i].f(p, out[i].width, ps);
263		// POSIX: Any field need not be meaningful in all
264		// implementations. In such a case a hyphen ( '-' )
265		// should be output in place of the field value.
266		if (!p[0]) {
267			p[0] = '-';
268			p[1] = '\0';
269		}
270		len = strlen(p);
271		p += len;
272		len = out[i].width - len + 1;
273		if (++i == out_cnt) /* do not pad last field */
274			break;
275		p += sprintf(p, "%*s", len, "");
276	}
277	printf("%.*s\n", terminal_width, buffer);
278}
279
280int ps_main(int argc, char **argv);
281int ps_main(int argc, char **argv)
282{
283	procps_status_t *p;
284	llist_t* opt_o = NULL;
285	USE_SELINUX(int opt;)
286
287	// POSIX:
288	// -a  Write information for all processes associated with terminals
289	//     Implementations may omit session leaders from this list
290	// -A  Write information for all processes
291	// -d  Write information for all processes, except session leaders
292	// -e  Write information for all processes (equivalent to -A.)
293	// -f  Generate a full listing
294	// -l  Generate a long listing
295	// -o col1,col2,col3=header
296	//     Select which columns to display
297	opt_complementary = "o::";
298	USE_SELINUX(opt =) getopt32(argv, "Zo:aAdefl", &opt_o);
299	if (opt_o) {
300		do {
301			parse_o(opt_o->data);
302			opt_o = opt_o->link;
303		} while (opt_o);
304	} else {
305		/* Below: parse_o() needs char*, NOT const char*... */
306#if ENABLE_SELINUX
307		if (!(opt & 1) || !is_selinux_enabled()) {
308			/* no -Z or no SELinux: do not show LABEL */
309			strcpy(default_o, DEFAULT_O_STR + sizeof(SELINIX_O_PREFIX)-1);
310		} else
311#endif
312		{
313			strcpy(default_o, DEFAULT_O_STR);
314		}
315		parse_o(default_o);
316	}
317	post_process();
318
319	/* Was INT_MAX, but some libc's go belly up with printf("%.*s")
320	 * and such large widths */
321	terminal_width = MAX_WIDTH;
322	if (isatty(1)) {
323		get_terminal_width_height(0, &terminal_width, NULL);
324		if (--terminal_width > MAX_WIDTH)
325			terminal_width = MAX_WIDTH;
326	}
327	format_header();
328
329	p = NULL;
330	while ((p = procps_scan(p, need_flags))) {
331		format_process(p);
332	}
333
334	return EXIT_SUCCESS;
335}
336
337
338#else /* !ENABLE_DESKTOP */
339
340
341int ps_main(int argc, char **argv);
342int ps_main(int argc, char **argv)
343{
344	procps_status_t *p = NULL;
345	int len;
346	SKIP_SELINUX(const) int use_selinux = 0;
347	USE_SELINUX(int i;)
348#if !ENABLE_FEATURE_PS_WIDE
349	enum { terminal_width = 79 };
350#else
351	int terminal_width;
352	int w_count = 0;
353#endif
354
355#if ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX
356#if ENABLE_FEATURE_PS_WIDE
357	opt_complementary = "-:ww";
358	USE_SELINUX(i =) getopt32(argv, USE_SELINUX("Z") "w", &w_count);
359	/* if w is given once, GNU ps sets the width to 132,
360	 * if w is given more than once, it is "unlimited"
361	 */
362	if (w_count) {
363		terminal_width = (w_count==1) ? 132 : MAX_WIDTH;
364	} else {
365		get_terminal_width_height(0, &terminal_width, NULL);
366		/* Go one less... */
367		if (--terminal_width > MAX_WIDTH)
368			terminal_width = MAX_WIDTH;
369	}
370#else /* only ENABLE_SELINUX */
371	i = getopt32(argv, "Z");
372#endif
373#if ENABLE_SELINUX
374	if ((i & 1) && is_selinux_enabled())
375		use_selinux = PSSCAN_CONTEXT;
376#endif
377#endif /* ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX */
378
379	if (use_selinux)
380		puts("  PID Context                          Stat Command");
381	else
382		puts("  PID  Uid        VSZ Stat Command");
383
384	while ((p = procps_scan(p, 0
385			| PSSCAN_PID
386			| PSSCAN_UIDGID
387			| PSSCAN_STATE
388			| PSSCAN_VSZ
389			| PSSCAN_COMM
390			| use_selinux
391	))) {
392#if ENABLE_SELINUX
393		if (use_selinux) {
394			len = printf("%5u %-32s %s ",
395					p->pid,
396					p->context ? p->context : "unknown",
397					p->state);
398		} else
399#endif
400		{
401			const char *user = get_cached_username(p->uid);
402			if (p->vsz == 0)
403				len = printf("%5u %-8s        %s ",
404					p->pid, user, p->state);
405			else
406				len = printf("%5u %-8s %6u %s ",
407					p->pid, user, p->vsz, p->state);
408		}
409
410		{
411			int sz = terminal_width - len;
412			char buf[sz + 1];
413			read_cmdline(buf, sz, p->pid, p->comm);
414			puts(buf);
415		}
416	}
417	if (ENABLE_FEATURE_CLEAN_UP)
418		clear_username_cache();
419	return EXIT_SUCCESS;
420}
421
422#endif /* ENABLE_DESKTOP */
423