1/* vi: set sw=4 ts=4: */
2/*
3 * Utility routines.
4 *
5 * Copyright 1998 by Albert Cahalan; all rights reserved.
6 * Copyright (C) 2002 by Vladimir Oleynik <dzo@simtreas.ru>
7 * SELinux support: (c) 2007 by Yuichi Nakamura <ynakam@hitachisoft.jp>
8 *
9 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
10 */
11
12#include "libbb.h"
13
14
15typedef struct unsigned_to_name_map_t {
16	unsigned id;
17	char name[USERNAME_MAX_SIZE];
18} unsigned_to_name_map_t;
19
20typedef struct cache_t {
21	unsigned_to_name_map_t *cache;
22	int size;
23} cache_t;
24
25static cache_t username, groupname;
26
27static void clear_cache(cache_t *cp)
28{
29	free(cp->cache);
30	cp->cache = NULL;
31	cp->size = 0;
32}
33void clear_username_cache(void)
34{
35	clear_cache(&username);
36	clear_cache(&groupname);
37}
38
39
40typedef char* ug_func(char *name, int bufsize, long uid);
41static char* get_cached(cache_t *cp, unsigned id, ug_func* fp)
42{
43	int i;
44	for (i = 0; i < cp->size; i++)
45		if (cp->cache[i].id == id)
46			return cp->cache[i].name;
47	i = cp->size++;
48	cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache));
49	cp->cache[i].id = id;
50	/* Never fails. Generates numeric string if name isn't found */
51	fp(cp->cache[i].name, sizeof(cp->cache[i].name), id);
52	return cp->cache[i].name;
53}
54const char* get_cached_username(uid_t uid)
55{
56	return get_cached(&username, uid, bb_getpwuid);
57}
58const char* get_cached_groupname(gid_t gid)
59{
60	return get_cached(&groupname, gid, bb_getgrgid);
61}
62
63
64#define PROCPS_BUFSIZE 1024
65
66static int read_to_buf(const char *filename, void *buf)
67{
68	int fd;
69	/* open_read_close() would do two reads, checking for EOF.
70	 * When you have 10000 /proc/$NUM/stat to read, it isn't desirable */
71	ssize_t ret = -1;
72	fd = open(filename, O_RDONLY);
73	if (fd >= 0) {
74		ret = read(fd, buf, PROCPS_BUFSIZE-1);
75		close(fd);
76	}
77	((char *)buf)[ret > 0 ? ret : 0] = '\0';
78	return ret;
79}
80
81procps_status_t *alloc_procps_scan(int flags)
82{
83	procps_status_t* sp = xzalloc(sizeof(procps_status_t));
84	sp->dir = xopendir("/proc");
85	return sp;
86}
87
88void free_procps_scan(procps_status_t* sp)
89{
90	closedir(sp->dir);
91	free(sp->argv0);
92	USE_SELINUX(free(sp->context);)
93	free(sp);
94}
95
96#if ENABLE_FEATURE_FAST_TOP
97/* We cut a lot of corners here for speed */
98static unsigned long fast_strtoul_10(char **endptr)
99{
100	char c;
101	char *str = *endptr;
102	unsigned long n = *str - '0';
103
104	while ((c = *++str) != ' ')
105		n = n*10 + (c - '0');
106
107	*endptr = str + 1; /* We skip trailing space! */
108	return n;
109}
110static char *skip_fields(char *str, int count)
111{
112	do {
113		while (*str++ != ' ')
114			continue;
115		/* we found a space char, str points after it */
116	} while (--count);
117	return str;
118}
119#endif
120
121void BUG_comm_size(void);
122procps_status_t *procps_scan(procps_status_t* sp, int flags)
123{
124	struct dirent *entry;
125	char buf[PROCPS_BUFSIZE];
126	char filename[sizeof("/proc//cmdline") + sizeof(int)*3];
127	char *filename_tail;
128	long tasknice;
129	unsigned pid;
130	int n;
131	struct stat sb;
132
133	if (!sp)
134		sp = alloc_procps_scan(flags);
135
136	for (;;) {
137		entry = readdir(sp->dir);
138		if (entry == NULL) {
139			free_procps_scan(sp);
140			return NULL;
141		}
142		pid = bb_strtou(entry->d_name, NULL, 10);
143		if (errno)
144			continue;
145
146		/* After this point we have to break, not continue
147		 * ("continue" would mean that current /proc/NNN
148		 * is not a valid process info) */
149
150		memset(&sp->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz));
151
152		sp->pid = pid;
153		if (!(flags & ~PSSCAN_PID)) break;
154
155#if ENABLE_SELINUX
156		if (flags & PSSCAN_CONTEXT) {
157			if (getpidcon(sp->pid, &sp->context) < 0)
158				sp->context = NULL;
159		}
160#endif
161
162		filename_tail = filename + sprintf(filename, "/proc/%d", pid);
163
164		if (flags & PSSCAN_UIDGID) {
165			if (stat(filename, &sb))
166				break;
167			/* Need comment - is this effective or real UID/GID? */
168			sp->uid = sb.st_uid;
169			sp->gid = sb.st_gid;
170		}
171
172		if (flags & PSSCAN_STAT) {
173			char *cp, *comm1;
174			int tty;
175#if !ENABLE_FEATURE_FAST_TOP
176			unsigned long vsz, rss;
177#endif
178
179			/* see proc(5) for some details on this */
180			strcpy(filename_tail, "/stat");
181			n = read_to_buf(filename, buf);
182			if (n < 0)
183				break;
184			cp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */
185			/*if (!cp || cp[1] != ' ')
186				break;*/
187			cp[0] = '\0';
188			if (sizeof(sp->comm) < 16)
189				BUG_comm_size();
190			comm1 = strchr(buf, '(');
191			/*if (comm1)*/
192				safe_strncpy(sp->comm, comm1 + 1, sizeof(sp->comm));
193
194#if !ENABLE_FEATURE_FAST_TOP
195			n = sscanf(cp+2,
196				"%c %u "               /* state, ppid */
197				"%u %u %d %*s "        /* pgid, sid, tty, tpgid */
198				"%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
199				"%lu %lu "             /* utime, stime */
200				"%*s %*s %*s "         /* cutime, cstime, priority */
201				"%ld "                 /* nice */
202				"%*s %*s %*s "         /* timeout, it_real_value, start_time */
203				"%lu "                 /* vsize */
204				"%lu "                 /* rss */
205			/*	"%lu %lu %lu %lu %lu %lu " rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
206			/*	"%u %u %u %u "         signal, blocked, sigignore, sigcatch */
207			/*	"%lu %lu %lu"          wchan, nswap, cnswap */
208				,
209				sp->state, &sp->ppid,
210				&sp->pgid, &sp->sid, &tty,
211				&sp->utime, &sp->stime,
212				&tasknice,
213				&vsz,
214				&rss);
215			if (n != 10)
216				break;
217			sp->vsz = vsz >> 10; /* vsize is in bytes and we want kb */
218			sp->rss = rss >> 10;
219			sp->tty_major = (tty >> 8) & 0xfff;
220			sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
221#else
222/* This costs ~100 bytes more but makes top faster by 20%
223 * If you run 10000 processes, this may be important for you */
224			sp->state[0] = cp[2];
225			cp += 4;
226			sp->ppid = fast_strtoul_10(&cp);
227			sp->pgid = fast_strtoul_10(&cp);
228			sp->sid = fast_strtoul_10(&cp);
229			tty = fast_strtoul_10(&cp);
230			sp->tty_major = (tty >> 8) & 0xfff;
231			sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
232			cp = skip_fields(cp, 6); /* tpgid, flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
233			sp->utime = fast_strtoul_10(&cp);
234			sp->stime = fast_strtoul_10(&cp);
235			cp = skip_fields(cp, 3); /* cutime, cstime, priority */
236			tasknice = fast_strtoul_10(&cp);
237			cp = skip_fields(cp, 3); /* timeout, it_real_value, start_time */
238			sp->vsz = fast_strtoul_10(&cp) >> 10; /* vsize is in bytes and we want kb */
239			sp->rss = fast_strtoul_10(&cp) >> 10;
240#endif
241
242			if (sp->vsz == 0 && sp->state[0] != 'Z')
243				sp->state[1] = 'W';
244			else
245				sp->state[1] = ' ';
246			if (tasknice < 0)
247				sp->state[2] = '<';
248			else if (tasknice) /* > 0 */
249				sp->state[2] = 'N';
250			else
251				sp->state[2] = ' ';
252
253		}
254
255		if (flags & PSSCAN_ARGV0) {
256			if (sp->argv0) {
257				free(sp->argv0);
258				sp->argv0 = NULL;
259			}
260			strcpy(filename_tail, "/cmdline");
261			n = read_to_buf(filename, buf);
262			if (n <= 0)
263				break;
264			if (flags & PSSCAN_ARGV0)
265				sp->argv0 = xstrdup(buf);
266		}
267		break;
268	}
269	return sp;
270}
271
272void read_cmdline(char *buf, int col, unsigned pid, const char *comm)
273{
274	ssize_t sz;
275	char filename[sizeof("/proc//cmdline") + sizeof(int)*3];
276
277	sprintf(filename, "/proc/%u/cmdline", pid);
278	sz = open_read_close(filename, buf, col);
279	if (sz > 0) {
280		buf[sz] = '\0';
281		while (--sz >= 0)
282			if ((unsigned char)(buf[sz]) < ' ')
283				buf[sz] = ' ';
284	} else {
285		snprintf(buf, col, "[%s]", comm);
286	}
287}
288
289/* from kernel:
290	//             pid comm S ppid pgid sid tty_nr tty_pgrp flg
291	sprintf(buffer,"%d (%s) %c %d  %d   %d  %d     %d       %lu %lu \
292%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \
293%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu %llu\n",
294		task->pid,
295		tcomm,
296		state,
297		ppid,
298		pgid,
299		sid,
300		tty_nr,
301		tty_pgrp,
302		task->flags,
303		min_flt,
304		cmin_flt,
305		maj_flt,
306		cmaj_flt,
307		cputime_to_clock_t(utime),
308		cputime_to_clock_t(stime),
309		cputime_to_clock_t(cutime),
310		cputime_to_clock_t(cstime),
311		priority,
312		nice,
313		num_threads,
314		// 0,
315		start_time,
316		vsize,
317		mm ? get_mm_rss(mm) : 0,
318		rsslim,
319		mm ? mm->start_code : 0,
320		mm ? mm->end_code : 0,
321		mm ? mm->start_stack : 0,
322		esp,
323		eip,
324the rest is some obsolete cruft
325*/
326