source: MondoRescue/branches/3.3/mindi-busybox/libbb/procps.c@ 3647

Last change on this file since 3647 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 16.6 KB
Line 
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 source tree.
10 */
11
12#include "libbb.h"
13
14
15typedef struct id_to_name_map_t {
16 uid_t id;
17 char name[USERNAME_MAX_SIZE];
18} id_to_name_map_t;
19
20typedef struct cache_t {
21 id_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, uid_t 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, uid_t id,
56 char* FAST_FUNC x2x_utoa(uid_t 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 || ENABLE_PMAP
124static unsigned long fast_strtoul_16(char **endptr)
125{
126 unsigned char c;
127 char *str = *endptr;
128 unsigned long n = 0;
129
130 /* Need to stop on both ' ' and '\n' */
131 while ((c = *str++) > ' ') {
132 c = ((c|0x20) - '0');
133 if (c > 9)
134 /* c = c + '0' - 'a' + 10: */
135 c = c - ('a' - '0' - 10);
136 n = n*16 + c;
137 }
138 *endptr = str; /* We skip trailing space! */
139 return n;
140}
141#endif
142
143#if ENABLE_FEATURE_FAST_TOP || ENABLE_FEATURE_TOPMEM || ENABLE_PMAP
144/* We cut a lot of corners here for speed */
145static unsigned long fast_strtoul_10(char **endptr)
146{
147 unsigned char c;
148 char *str = *endptr;
149 unsigned long n = *str - '0';
150
151 /* Need to stop on both ' ' and '\n' */
152 while ((c = *++str) > ' ')
153 n = n*10 + (c - '0');
154
155 *endptr = str + 1; /* We skip trailing space! */
156 return n;
157}
158
159# if ENABLE_FEATURE_FAST_TOP
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# endif
169
170static char *skip_fields(char *str, int count)
171{
172 do {
173 while (*str++ != ' ')
174 continue;
175 /* we found a space char, str points after it */
176 } while (--count);
177 return str;
178}
179#endif
180
181#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP
182int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total,
183 void (*cb)(struct smaprec *, void *), void *data)
184{
185 FILE *file;
186 struct smaprec currec;
187 char filename[sizeof("/proc/%u/smaps") + sizeof(int)*3];
188 char buf[PROCPS_BUFSIZE];
189#if !ENABLE_PMAP
190 void (*cb)(struct smaprec *, void *) = NULL;
191 void *data = NULL;
192#endif
193
194 sprintf(filename, "/proc/%u/smaps", (int)pid);
195
196 file = fopen_for_read(filename);
197 if (!file)
198 return 1;
199
200 memset(&currec, 0, sizeof(currec));
201 while (fgets(buf, PROCPS_BUFSIZE, file)) {
202 // Each mapping datum has this form:
203 // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME
204 // Size: nnn kB
205 // Rss: nnn kB
206 // .....
207
208 char *tp, *p;
209
210#define SCAN(S, X) \
211 if ((tp = is_prefixed_with(buf, S)) != NULL) { \
212 tp = skip_whitespace(tp); \
213 total->X += currec.X = fast_strtoul_10(&tp); \
214 continue; \
215 }
216 if (cb) {
217 SCAN("Pss:" , smap_pss );
218 SCAN("Swap:" , smap_swap );
219 }
220 SCAN("Private_Dirty:", private_dirty);
221 SCAN("Private_Clean:", private_clean);
222 SCAN("Shared_Dirty:" , shared_dirty );
223 SCAN("Shared_Clean:" , shared_clean );
224#undef SCAN
225 tp = strchr(buf, '-');
226 if (tp) {
227 // We reached next mapping - the line of this form:
228 // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME
229
230 if (cb) {
231 /* If we have a previous record, there's nothing more
232 * for it, call the callback and clear currec
233 */
234 if (currec.smap_size)
235 cb(&currec, data);
236 free(currec.smap_name);
237 }
238 memset(&currec, 0, sizeof(currec));
239
240 *tp = ' ';
241 tp = buf;
242 currec.smap_start = fast_strtoul_16(&tp);
243 currec.smap_size = (fast_strtoul_16(&tp) - currec.smap_start) >> 10;
244
245 strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-1);
246
247 // skipping "rw-s FILEOFS M:m INODE "
248 tp = skip_whitespace(skip_fields(tp, 4));
249 // filter out /dev/something (something != zero)
250 if (!is_prefixed_with(tp, "/dev/") || strcmp(tp, "/dev/zero\n") == 0) {
251 if (currec.smap_mode[1] == 'w') {
252 currec.mapped_rw = currec.smap_size;
253 total->mapped_rw += currec.smap_size;
254 } else if (currec.smap_mode[1] == '-') {
255 currec.mapped_ro = currec.smap_size;
256 total->mapped_ro += currec.smap_size;
257 }
258 }
259
260 if (strcmp(tp, "[stack]\n") == 0)
261 total->stack += currec.smap_size;
262 if (cb) {
263 p = skip_non_whitespace(tp);
264 if (p == tp) {
265 currec.smap_name = xstrdup(" [ anon ]");
266 } else {
267 *p = '\0';
268 currec.smap_name = xstrdup(tp);
269 }
270 }
271 total->smap_size += currec.smap_size;
272 }
273 }
274 fclose(file);
275
276 if (cb) {
277 if (currec.smap_size)
278 cb(&currec, data);
279 free(currec.smap_name);
280 }
281
282 return 0;
283}
284#endif
285
286procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags)
287{
288 if (!sp)
289 sp = alloc_procps_scan();
290
291 for (;;) {
292 struct dirent *entry;
293 char buf[PROCPS_BUFSIZE];
294 long tasknice;
295 unsigned pid;
296 int n;
297 char filename[sizeof("/proc/%u/task/%u/cmdline") + sizeof(int)*3 * 2];
298 char *filename_tail;
299
300#if ENABLE_FEATURE_SHOW_THREADS
301 if (sp->task_dir) {
302 entry = readdir(sp->task_dir);
303 if (entry)
304 goto got_entry;
305 closedir(sp->task_dir);
306 sp->task_dir = NULL;
307 }
308#endif
309 entry = readdir(sp->dir);
310 if (entry == NULL) {
311 free_procps_scan(sp);
312 return NULL;
313 }
314 IF_FEATURE_SHOW_THREADS(got_entry:)
315 pid = bb_strtou(entry->d_name, NULL, 10);
316 if (errno)
317 continue;
318#if ENABLE_FEATURE_SHOW_THREADS
319 if ((flags & PSSCAN_TASKS) && !sp->task_dir) {
320 /* We found another /proc/PID. Do not use it,
321 * there will be /proc/PID/task/PID (same PID!),
322 * so just go ahead and dive into /proc/PID/task. */
323 sprintf(filename, "/proc/%u/task", pid);
324 /* Note: if opendir fails, we just go to next /proc/XXX */
325 sp->task_dir = opendir(filename);
326 sp->main_thread_pid = pid;
327 continue;
328 }
329#endif
330
331 /* After this point we can:
332 * "break": stop parsing, return the data
333 * "continue": try next /proc/XXX
334 */
335
336 memset(&sp->vsz, 0, sizeof(*sp) - offsetof(procps_status_t, vsz));
337
338 sp->pid = pid;
339 if (!(flags & ~PSSCAN_PID))
340 break; /* we needed only pid, we got it */
341
342#if ENABLE_SELINUX
343 if (flags & PSSCAN_CONTEXT) {
344 if (getpidcon(sp->pid, &sp->context) < 0)
345 sp->context = NULL;
346 }
347#endif
348
349#if ENABLE_FEATURE_SHOW_THREADS
350 if (sp->task_dir)
351 filename_tail = filename + sprintf(filename, "/proc/%u/task/%u/", sp->main_thread_pid, pid);
352 else
353#endif
354 filename_tail = filename + sprintf(filename, "/proc/%u/", pid);
355
356 if (flags & PSSCAN_UIDGID) {
357 struct stat sb;
358 if (stat(filename, &sb))
359 continue; /* process probably exited */
360 /* Effective UID/GID, not real */
361 sp->uid = sb.st_uid;
362 sp->gid = sb.st_gid;
363 }
364
365 /* These are all retrieved from proc/NN/stat in one go: */
366 if (flags & (PSSCAN_PPID | PSSCAN_PGID | PSSCAN_SID
367 | PSSCAN_COMM | PSSCAN_STATE
368 | PSSCAN_VSZ | PSSCAN_RSS
369 | PSSCAN_STIME | PSSCAN_UTIME | PSSCAN_START_TIME
370 | PSSCAN_TTY | PSSCAN_NICE
371 | PSSCAN_CPU)
372 ) {
373 char *cp, *comm1;
374 int tty;
375#if !ENABLE_FEATURE_FAST_TOP
376 unsigned long vsz, rss;
377#endif
378 /* see proc(5) for some details on this */
379 strcpy(filename_tail, "stat");
380 n = read_to_buf(filename, buf);
381 if (n < 0)
382 continue; /* process probably exited */
383 cp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */
384 /*if (!cp || cp[1] != ' ')
385 continue;*/
386 cp[0] = '\0';
387 BUILD_BUG_ON(sizeof(sp->comm) < 16);
388 comm1 = strchr(buf, '(');
389 /*if (comm1)*/
390 safe_strncpy(sp->comm, comm1 + 1, sizeof(sp->comm));
391
392#if !ENABLE_FEATURE_FAST_TOP
393 n = sscanf(cp+2,
394 "%c %u " /* state, ppid */
395 "%u %u %d %*s " /* pgid, sid, tty, tpgid */
396 "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
397 "%lu %lu " /* utime, stime */
398 "%*s %*s %*s " /* cutime, cstime, priority */
399 "%ld " /* nice */
400 "%*s %*s " /* timeout, it_real_value */
401 "%lu " /* start_time */
402 "%lu " /* vsize */
403 "%lu " /* rss */
404# if ENABLE_FEATURE_TOP_SMP_PROCESS
405 "%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
406 "%*s %*s %*s %*s " /*signal, blocked, sigignore, sigcatch */
407 "%*s %*s %*s %*s " /*wchan, nswap, cnswap, exit_signal */
408 "%d" /*cpu last seen on*/
409# endif
410 ,
411 sp->state, &sp->ppid,
412 &sp->pgid, &sp->sid, &tty,
413 &sp->utime, &sp->stime,
414 &tasknice,
415 &sp->start_time,
416 &vsz,
417 &rss
418# if ENABLE_FEATURE_TOP_SMP_PROCESS
419 , &sp->last_seen_on_cpu
420# endif
421 );
422
423 if (n < 11)
424 continue; /* bogus data, get next /proc/XXX */
425# if ENABLE_FEATURE_TOP_SMP_PROCESS
426 if (n == 11)
427 sp->last_seen_on_cpu = 0;
428# endif
429
430 /* vsz is in bytes and we want kb */
431 sp->vsz = vsz >> 10;
432 /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
433 sp->rss = rss << sp->shift_pages_to_kb;
434 sp->tty_major = (tty >> 8) & 0xfff;
435 sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
436#else
437/* This costs ~100 bytes more but makes top faster by 20%
438 * If you run 10000 processes, this may be important for you */
439 sp->state[0] = cp[2];
440 cp += 4;
441 sp->ppid = fast_strtoul_10(&cp);
442 sp->pgid = fast_strtoul_10(&cp);
443 sp->sid = fast_strtoul_10(&cp);
444 tty = fast_strtoul_10(&cp);
445 sp->tty_major = (tty >> 8) & 0xfff;
446 sp->tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
447 cp = skip_fields(cp, 6); /* tpgid, flags, min_flt, cmin_flt, maj_flt, cmaj_flt */
448 sp->utime = fast_strtoul_10(&cp);
449 sp->stime = fast_strtoul_10(&cp);
450 cp = skip_fields(cp, 3); /* cutime, cstime, priority */
451 tasknice = fast_strtol_10(&cp);
452 cp = skip_fields(cp, 2); /* timeout, it_real_value */
453 sp->start_time = fast_strtoul_10(&cp);
454 /* vsz is in bytes and we want kb */
455 sp->vsz = fast_strtoul_10(&cp) >> 10;
456 /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */
457 sp->rss = fast_strtoul_10(&cp) << sp->shift_pages_to_kb;
458# if ENABLE_FEATURE_TOP_SMP_PROCESS
459 /* (6): rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
460 /* (4): signal, blocked, sigignore, sigcatch */
461 /* (4): wchan, nswap, cnswap, exit_signal */
462 cp = skip_fields(cp, 14);
463//FIXME: is it safe to assume this field exists?
464 sp->last_seen_on_cpu = fast_strtoul_10(&cp);
465# endif
466#endif /* FEATURE_FAST_TOP */
467
468#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
469 sp->niceness = tasknice;
470#endif
471
472 if (sp->vsz == 0 && sp->state[0] != 'Z')
473 sp->state[1] = 'W';
474 else
475 sp->state[1] = ' ';
476 if (tasknice < 0)
477 sp->state[2] = '<';
478 else if (tasknice) /* > 0 */
479 sp->state[2] = 'N';
480 else
481 sp->state[2] = ' ';
482 }
483
484#if ENABLE_FEATURE_TOPMEM
485 if (flags & PSSCAN_SMAPS)
486 procps_read_smaps(pid, &sp->smaps, NULL, NULL);
487#endif /* TOPMEM */
488#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
489 if (flags & PSSCAN_RUIDGID) {
490 FILE *file;
491
492 strcpy(filename_tail, "status");
493 file = fopen_for_read(filename);
494 if (file) {
495 while (fgets(buf, sizeof(buf), file)) {
496 char *tp;
497#define SCAN_TWO(str, name, statement) \
498 if ((tp = is_prefixed_with(buf, str)) != NULL) { \
499 tp = skip_whitespace(tp); \
500 sscanf(tp, "%u", &sp->name); \
501 statement; \
502 }
503 SCAN_TWO("Uid:", ruid, continue);
504 SCAN_TWO("Gid:", rgid, break);
505#undef SCAN_TWO
506 }
507 fclose(file);
508 }
509 }
510#endif /* PS_ADDITIONAL_COLUMNS */
511 if (flags & PSSCAN_EXE) {
512 strcpy(filename_tail, "exe");
513 free(sp->exe);
514 sp->exe = xmalloc_readlink(filename);
515 }
516 /* Note: if /proc/PID/cmdline is empty,
517 * code below "breaks". Therefore it must be
518 * the last code to parse /proc/PID/xxx data
519 * (we used to have /proc/PID/exe parsing after it
520 * and were getting stale sp->exe).
521 */
522#if 0 /* PSSCAN_CMD is not used */
523 if (flags & (PSSCAN_CMD|PSSCAN_ARGV0)) {
524 free(sp->argv0);
525 sp->argv0 = NULL;
526 free(sp->cmd);
527 sp->cmd = NULL;
528 strcpy(filename_tail, "cmdline");
529 /* TODO: to get rid of size limits, read into malloc buf,
530 * then realloc it down to real size. */
531 n = read_to_buf(filename, buf);
532 if (n <= 0)
533 break;
534 if (flags & PSSCAN_ARGV0)
535 sp->argv0 = xstrdup(buf);
536 if (flags & PSSCAN_CMD) {
537 do {
538 n--;
539 if ((unsigned char)(buf[n]) < ' ')
540 buf[n] = ' ';
541 } while (n);
542 sp->cmd = xstrdup(buf);
543 }
544 }
545#else
546 if (flags & (PSSCAN_ARGV0|PSSCAN_ARGVN)) {
547 free(sp->argv0);
548 sp->argv0 = NULL;
549 strcpy(filename_tail, "cmdline");
550 n = read_to_buf(filename, buf);
551 if (n <= 0)
552 break;
553 if (flags & PSSCAN_ARGVN) {
554 sp->argv_len = n;
555 sp->argv0 = xmemdup(buf, n + 1);
556 /* sp->argv0[n] = '\0'; - buf has it */
557 } else {
558 sp->argv_len = 0;
559 sp->argv0 = xstrdup(buf);
560 }
561 }
562#endif
563 break;
564 } /* for (;;) */
565
566 return sp;
567}
568
569void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm)
570{
571 int sz;
572 char filename[sizeof("/proc/%u/cmdline") + sizeof(int)*3];
573
574 sprintf(filename, "/proc/%u/cmdline", pid);
575 sz = open_read_close(filename, buf, col - 1);
576 if (sz > 0) {
577 const char *base;
578 int comm_len;
579
580 buf[sz] = '\0';
581 while (--sz >= 0 && buf[sz] == '\0')
582 continue;
583 /* Prevent basename("process foo/bar") = "bar" */
584 strchrnul(buf, ' ')[0] = '\0';
585 base = bb_basename(buf); /* before we replace argv0's NUL with space */
586 while (sz >= 0) {
587 if ((unsigned char)(buf[sz]) < ' ')
588 buf[sz] = ' ';
589 sz--;
590 }
591 if (base[0] == '-') /* "-sh" (login shell)? */
592 base++;
593
594 /* If comm differs from argv0, prepend "{comm} ".
595 * It allows to see thread names set by prctl(PR_SET_NAME).
596 */
597 if (!comm)
598 return;
599 comm_len = strlen(comm);
600 /* Why compare up to comm_len, not COMM_LEN-1?
601 * Well, some processes rewrite argv, and use _spaces_ there
602 * while rewriting. (KDE is observed to do it).
603 * I prefer to still treat argv0 "process foo bar"
604 * as 'equal' to comm "process".
605 */
606 if (strncmp(base, comm, comm_len) != 0) {
607 comm_len += 3;
608 if (col > comm_len)
609 memmove(buf + comm_len, buf, col - comm_len);
610 snprintf(buf, col, "{%s}", comm);
611 if (col <= comm_len)
612 return;
613 buf[comm_len - 1] = ' ';
614 buf[col - 1] = '\0';
615 }
616 } else {
617 snprintf(buf, col, "[%s]", comm ? comm : "?");
618 }
619}
620
621/* from kernel:
622 // pid comm S ppid pgid sid tty_nr tty_pgrp flg
623 sprintf(buffer,"%d (%s) %c %d %d %d %d %d %lu %lu \
624%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \
625%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu %llu\n",
626 task->pid,
627 tcomm,
628 state,
629 ppid,
630 pgid,
631 sid,
632 tty_nr,
633 tty_pgrp,
634 task->flags,
635 min_flt,
636 cmin_flt,
637 maj_flt,
638 cmaj_flt,
639 cputime_to_clock_t(utime),
640 cputime_to_clock_t(stime),
641 cputime_to_clock_t(cutime),
642 cputime_to_clock_t(cstime),
643 priority,
644 nice,
645 num_threads,
646 // 0,
647 start_time,
648 vsize,
649 mm ? get_mm_rss(mm) : 0,
650 rsslim,
651 mm ? mm->start_code : 0,
652 mm ? mm->end_code : 0,
653 mm ? mm->start_stack : 0,
654 esp,
655 eip,
656the rest is some obsolete cruft
657*/
Note: See TracBrowser for help on using the repository browser.