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