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	long 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 FAST_FUNC clear_username_cache(void)
34{
35	clear_cache(&username);
36	clear_cache(&groupname);
37}
38
39#if 0 /* more generic, but we don't need that yet */
40/* Returns -N-1 if not found. */
41/* cp->cache[N] is allocated and must be filled in this case */
42static int get_cached(cache_t *cp, unsigned id)
43{
44	int i;
45	for (i = 0; i < cp->size; i++)
46		if (cp->cache[i].id == id)
47			return i;
48	i = cp->size++;
49	cp->cache = xrealloc_vector(cp->cache, 2, i);
50	cp->cache[i++].id = id;
51	return -i;
52}
53#endif
54
55static char* get_cached(cache_t *cp, long id,
56			char* FAST_FUNC x2x_utoa(long id))
57{
58	int i;
59	for (i = 0; i < cp->size; i++)
60		if (cp->cache[i].id == id)
61			return cp->cache[i].name;
62	i = cp->size++;
63	cp->cache = xrealloc_vector(cp->cache, 2, i);
64	cp->cache[i].id = id;
65	/* Never fails. Generates numeric string if name isn't found */
66	safe_strncpy(cp->cache[i].name, x2x_utoa(id), sizeof(cp->cache[i].name));
67	return cp->cache[i].name;
68}
69const char* FAST_FUNC get_cached_username(uid_t uid)
70{
71	return get_cached(&username, uid, uid2uname_utoa);
72}
73const char* FAST_FUNC get_cached_groupname(gid_t gid)
74{
75	return get_cached(&groupname, gid, gid2group_utoa);
76}
77
78
79#define PROCPS_BUFSIZE 1024
80
81static int read_to_buf(const char *filename, void *buf)
82{
83	int fd;
84	/* open_read_close() would do two reads, checking for EOF.
85	 * When you have 10000 /proc/$NUM/stat to read, it isn't desirable */
86	ssize_t ret = -1;
87	fd = open(filename, O_RDONLY);
88	if (fd >= 0) {
89		ret = read(fd, buf, PROCPS_BUFSIZE-1);
90		close(fd);
91	}
92	((char *)buf)[ret > 0 ? ret : 0] = '\0';
93	return ret;
94}
95
96static procps_status_t* FAST_FUNC alloc_procps_scan(void)
97{
98	unsigned n = getpagesize();
99	procps_status_t* sp = xzalloc(sizeof(procps_status_t));
100	sp->dir = xopendir("/proc");
101	while (1) {
102		n >>= 1;
103		if (!n) break;
104		sp->shift_pages_to_bytes++;
105	}
106	sp->shift_pages_to_kb = sp->shift_pages_to_bytes - 10;
107	return sp;
108}
109
110void FAST_FUNC free_procps_scan(procps_status_t* sp)
111{
112	closedir(sp->dir);
113#if ENABLE_FEATURE_SHOW_THREADS
114	if (sp->task_dir)
115		closedir(sp->task_dir);
116#endif
117	free(sp->argv0);
118	free(sp->exe);
119	IF_SELINUX(free(sp->context);)
120	free(sp);
121}
122
123#if ENABLE_FEATURE_TOPMEM
124static unsigned long fast_strtoul_16(char **endptr)
125{
126	unsigned char c;
127	char *str = *endptr;
128	unsigned long n = 0;
129
130	while ((c = *str++) != ' ') {
131		c = ((c|0x20) - '0');
132		if (c > 9)
133			// c = c + '0' - 'a' + 10:
134			c = c - ('a' - '0' - 10);
135		n = n*16 + c;
136	}
137	*endptr = str; /* We skip trailing space! */
138	return n;
139}
140/* TOPMEM uses fast_strtoul_10, so... */
141# undef ENABLE_FEATURE_FAST_TOP
142# define ENABLE_FEATURE_FAST_TOP 1
143#endif
144
145#if ENABLE_FEATURE_FAST_TOP
146/* We cut a lot of corners here for speed */
147static unsigned long fast_strtoul_10(char **endptr)
148{
149	char c;
150	char *str = *endptr;
151	unsigned long n = *str - '0';
152
153	while ((c = *++str) != ' ')
154		n = n*10 + (c - '0');
155
156	*endptr = str + 1; /* We skip trailing space! */
157	return n;
158}
159
160static long fast_strtol_10(char **endptr)
161{
162	if (**endptr != '-')
163		return fast_strtoul_10(endptr);
164
165	(*endptr)++;
166	return - (long)fast_strtoul_10(endptr);
167}
168
169static char *skip_fields(char *str, int count)
170{
171	do {
172		while (*str++ != ' ')
173			continue;
174		/* we found a space char, str points after it */
175	} while (--count);
176	return str;
177}
178#endif
179
180void BUG_comm_size(void);
181procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags)
182{
183	struct dirent *entry;
184	char buf[PROCPS_BUFSIZE];
185	char filename[sizeof("/proc//cmdline") + sizeof(int)*3];
186	char *filename_tail;
187	long tasknice;
188	unsigned pid;
189	int n;
190	struct stat sb;
191
192	if (!sp)
193		sp = alloc_procps_scan();
194
195	for (;;) {
196#if ENABLE_FEATURE_SHOW_THREADS
197		if ((flags & PSSCAN_TASKS) && sp->task_dir) {
198			entry = readdir(sp->task_dir);
199			if (entry)
200				goto got_entry;
201			closedir(sp->task_dir);
202			sp->task_dir = NULL;
203		}
204#endif
205		entry = readdir(sp->dir);
206		if (entry == NULL) {
207			free_procps_scan(sp);
208			return NULL;
209		}
210 IF_FEATURE_SHOW_THREADS(got_entry:)
211		pid = bb_strtou(entry->d_name, NULL, 10);
212		if (errno)
213			continue;
214#if ENABLE_FEATURE_SHOW_THREADS
215		if ((flags & PSSCAN_TASKS) && !sp->task_dir) {
216			/* We found another /proc/PID. Do not use it,
217			 * there will be /proc/PID/task/PID (same PID!),
218			 * so just go ahead and dive into /proc/PID/task. */
219			char task_dir[sizeof("/proc/%u/task") + sizeof(int)*3];
220			sprintf(task_dir, "/proc/%u/task", pid);
221			sp->task_dir = xopendir(task_dir);
222			continue;
223		}
224#endif
225
226		/* After this point we can:
227		 * "break": stop parsing, return the data
228		 * "continue": try next /proc/XXX
229		 */
230
231		memset(&sp->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz));
232
233		sp->pid = pid;
234		if (!(flags & ~PSSCAN_PID))
235			break; /* we needed only pid, we got it */
236
237#if ENABLE_SELINUX
238		if (flags & PSSCAN_CONTEXT) {
239			if (getpidcon(sp->pid, &sp->context) < 0)
240				sp->context = NULL;
241		}
242#endif
243
244		filename_tail = filename + sprintf(filename, "/proc/%u/", pid);
245
246		if (flags & PSSCAN_UIDGID) {
247			if (stat(filename, &sb))
248				continue; /* process probably exited */
249			/* Effective UID/GID, not real */
250			sp->uid = sb.st_uid;
251			sp->gid = sb.st_gid;
252		}
253
254		if (flags & PSSCAN_STAT) {
255			char *cp, *comm1;
256			int tty;
257#if !ENABLE_FEATURE_FAST_TOP
258			unsigned long vsz, rss;
259#endif
260			/* see proc(5) for some details on this */
261			strcpy(filename_tail, "stat");
262			n = read_to_buf(filename, buf);
263			if (n < 0)
264				continue; /* process probably exited */
265			cp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */
266			/*if (!cp || cp[1] != ' ')
267				continue;*/
268			cp[0] = '\0';
269			if (sizeof(sp->comm) < 16)
270				BUG_comm_size();
271			comm1 = strchr(buf, '(');
272			/*if (comm1)*/
273				safe_strncpy(sp->comm, comm1 + 1, sizeof(sp->comm));
274
275#if !ENABLE_FEATURE_FAST_TOP
276			n = sscanf(cp+2,
277				"%c %u "               /* state, ppid */
278				"%u %u %d %*s "        /* pgid, sid, tty, tpgid */
279				"%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
280				"%lu %lu "             /* utime, stime */
281				"%*s %*s %*s "         /* cutime, cstime, priority */
282				"%ld "                 /* nice */
283				"%*s %*s "             /* timeout, it_real_value */
284				"%lu "                 /* start_time */
285				"%lu "                 /* vsize */
286				"%lu "                 /* rss */
287# if ENABLE_FEATURE_TOP_SMP_PROCESS
288				"%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
289				"%*s %*s %*s %*s "         /*signal, blocked, sigignore, sigcatch */
290				"%*s %*s %*s %*s "         /*wchan, nswap, cnswap, exit_signal */
291				"%d"                       /*cpu last seen on*/
292# endif
293				,
294				sp->state, &sp->ppid,
295				&sp->pgid, &sp->sid, &tty,
296				&sp->utime, &sp->stime,
297				&tasknice,
298				&sp->start_time,
299				&vsz,
300				&rss
301# if ENABLE_FEATURE_TOP_SMP_PROCESS
302				, &sp->last_seen_on_cpu
303# endif
304				);
305
306			if (n < 11)
307				continue; /* bogus data, get next /proc/XXX */
308# if ENABLE_FEATURE_TOP_SMP_PROCESS
309			if (n < 11+15)
310				sp->last_seen_on_cpu = 0;
311# endif
312
313			/* vsz is in bytes and we want kb */
314			sp->vsz = vsz >> 10;
315			/* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
316			sp->rss = rss << sp->shift_pages_to_kb;
317			sp->tty_major = (tty >> 8) & 0xfff;
318			sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
319#else
320/* This costs ~100 bytes more but makes top faster by 20%
321 * If you run 10000 processes, this may be important for you */
322			sp->state[0] = cp[2];
323			cp += 4;
324			sp->ppid = fast_strtoul_10(&cp);
325			sp->pgid = fast_strtoul_10(&cp);
326			sp->sid = fast_strtoul_10(&cp);
327			tty = fast_strtoul_10(&cp);
328			sp->tty_major = (tty >> 8) & 0xfff;
329			sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
330			cp = skip_fields(cp, 6); /* tpgid, flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
331			sp->utime = fast_strtoul_10(&cp);
332			sp->stime = fast_strtoul_10(&cp);
333			cp = skip_fields(cp, 3); /* cutime, cstime, priority */
334			tasknice = fast_strtol_10(&cp);
335			cp = skip_fields(cp, 2); /* timeout, it_real_value */
336			sp->start_time = fast_strtoul_10(&cp);
337			/* vsz is in bytes and we want kb */
338			sp->vsz = fast_strtoul_10(&cp) >> 10;
339			/* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
340			sp->rss = fast_strtoul_10(&cp) << sp->shift_pages_to_kb;
341# if ENABLE_FEATURE_TOP_SMP_PROCESS
342			/* (6): rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
343			/* (4): signal, blocked, sigignore, sigcatch */
344			/* (4): wchan, nswap, cnswap, exit_signal */
345			cp = skip_fields(cp, 14);
346//FIXME: is it safe to assume this field exists?
347			sp->last_seen_on_cpu = fast_strtoul_10(&cp);
348# endif
349#endif /* end of !ENABLE_FEATURE_TOP_SMP_PROCESS */
350
351#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
352			sp->niceness = tasknice;
353#endif
354
355			if (sp->vsz == 0 && sp->state[0] != 'Z')
356				sp->state[1] = 'W';
357			else
358				sp->state[1] = ' ';
359			if (tasknice < 0)
360				sp->state[2] = '<';
361			else if (tasknice) /* > 0 */
362				sp->state[2] = 'N';
363			else
364				sp->state[2] = ' ';
365		}
366
367#if ENABLE_FEATURE_TOPMEM
368		if (flags & (PSSCAN_SMAPS)) {
369			FILE *file;
370
371			strcpy(filename_tail, "smaps");
372			file = fopen_for_read(filename);
373			if (file) {
374				while (fgets(buf, sizeof(buf), file)) {
375					unsigned long sz;
376					char *tp;
377					char w;
378#define SCAN(str, name) \
379	if (strncmp(buf, str, sizeof(str)-1) == 0) { \
380		tp = skip_whitespace(buf + sizeof(str)-1); \
381		sp->name += fast_strtoul_10(&tp); \
382		continue; \
383	}
384					SCAN("Shared_Clean:" , shared_clean );
385					SCAN("Shared_Dirty:" , shared_dirty );
386					SCAN("Private_Clean:", private_clean);
387					SCAN("Private_Dirty:", private_dirty);
388#undef SCAN
389					// f7d29000-f7d39000 rw-s ADR M:m OFS FILE
390					tp = strchr(buf, '-');
391					if (tp) {
392						*tp = ' ';
393						tp = buf;
394						sz = fast_strtoul_16(&tp); /* start */
395						sz = (fast_strtoul_16(&tp) - sz) >> 10; /* end - start */
396						// tp -> "rw-s" string
397						w = tp[1];
398						// skipping "rw-s ADR M:m OFS "
399						tp = skip_whitespace(skip_fields(tp, 4));
400						// filter out /dev/something (something != zero)
401						if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) {
402							if (w == 'w') {
403								sp->mapped_rw += sz;
404							} else if (w == '-') {
405								sp->mapped_ro += sz;
406							}
407						}
408//else printf("DROPPING %s (%s)\n", buf, tp);
409						if (strcmp(tp, "[stack]\n") == 0)
410							sp->stack += sz;
411					}
412				}
413				fclose(file);
414			}
415		}
416#endif /* TOPMEM */
417#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
418		if (flags & PSSCAN_RUIDGID) {
419			FILE *file;
420
421			strcpy(filename_tail, "status");
422			file = fopen_for_read(filename);
423			if (file) {
424				while (fgets(buf, sizeof(buf), file)) {
425					char *tp;
426#define SCAN_TWO(str, name, statement) \
427	if (strncmp(buf, str, sizeof(str)-1) == 0) { \
428		tp = skip_whitespace(buf + sizeof(str)-1); \
429		sscanf(tp, "%u", &sp->name); \
430		statement; \
431	}
432					SCAN_TWO("Uid:", ruid, continue);
433					SCAN_TWO("Gid:", rgid, break);
434#undef SCAN_TWO
435				}
436				fclose(file);
437			}
438		}
439#endif /* PS_ADDITIONAL_COLUMNS */
440		if (flags & PSSCAN_EXE) {
441			strcpy(filename_tail, "exe");
442			free(sp->exe);
443			sp->exe = xmalloc_readlink(filename);
444		}
445		/* Note: if /proc/PID/cmdline is empty,
446		 * code below "breaks". Therefore it must be
447		 * the last code to parse /proc/PID/xxx data
448		 * (we used to have /proc/PID/exe parsing after it
449		 * and were getting stale sp->exe).
450		 */
451#if 0 /* PSSCAN_CMD is not used */
452		if (flags & (PSSCAN_CMD|PSSCAN_ARGV0)) {
453			free(sp->argv0);
454			sp->argv0 = NULL;
455			free(sp->cmd);
456			sp->cmd = NULL;
457			strcpy(filename_tail, "cmdline");
458			/* TODO: to get rid of size limits, read into malloc buf,
459			 * then realloc it down to real size. */
460			n = read_to_buf(filename, buf);
461			if (n <= 0)
462				break;
463			if (flags & PSSCAN_ARGV0)
464				sp->argv0 = xstrdup(buf);
465			if (flags & PSSCAN_CMD) {
466				do {
467					n--;
468					if ((unsigned char)(buf[n]) < ' ')
469						buf[n] = ' ';
470				} while (n);
471				sp->cmd = xstrdup(buf);
472			}
473		}
474#else
475		if (flags & (PSSCAN_ARGV0|PSSCAN_ARGVN)) {
476			free(sp->argv0);
477			sp->argv0 = NULL;
478			strcpy(filename_tail, "cmdline");
479			n = read_to_buf(filename, buf);
480			if (n <= 0)
481				break;
482			if (flags & PSSCAN_ARGVN) {
483				sp->argv_len = n;
484				sp->argv0 = xmalloc(n + 1);
485				memcpy(sp->argv0, buf, n + 1);
486				/* sp->argv0[n] = '\0'; - buf has it */
487			} else {
488				sp->argv_len = 0;
489				sp->argv0 = xstrdup(buf);
490			}
491		}
492#endif
493		break;
494	} /* for (;;) */
495
496	return sp;
497}
498
499void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm)
500{
501	int sz;
502	char filename[sizeof("/proc//cmdline") + sizeof(int)*3];
503
504	sprintf(filename, "/proc/%u/cmdline", pid);
505	sz = open_read_close(filename, buf, col - 1);
506	if (sz > 0) {
507		buf[sz] = '\0';
508		while (--sz >= 0 && buf[sz] == '\0')
509			continue;
510		do {
511			if ((unsigned char)(buf[sz]) < ' ')
512				buf[sz] = ' ';
513		} while (--sz >= 0);
514	} else {
515		snprintf(buf, col, "[%s]", comm);
516	}
517}
518
519/* from kernel:
520	//             pid comm S ppid pgid sid tty_nr tty_pgrp flg
521	sprintf(buffer,"%d (%s) %c %d  %d   %d  %d     %d       %lu %lu \
522%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \
523%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu %llu\n",
524		task->pid,
525		tcomm,
526		state,
527		ppid,
528		pgid,
529		sid,
530		tty_nr,
531		tty_pgrp,
532		task->flags,
533		min_flt,
534		cmin_flt,
535		maj_flt,
536		cmaj_flt,
537		cputime_to_clock_t(utime),
538		cputime_to_clock_t(stime),
539		cputime_to_clock_t(cutime),
540		cputime_to_clock_t(cstime),
541		priority,
542		nice,
543		num_threads,
544		// 0,
545		start_time,
546		vsize,
547		mm ? get_mm_rss(mm) : 0,
548		rsslim,
549		mm ? mm->start_code : 0,
550		mm ? mm->end_code : 0,
551		mm ? mm->start_stack : 0,
552		esp,
553		eip,
554the rest is some obsolete cruft
555*/
556