Reverse some minor changes that got caught up in my big unarchive patch
[oweals/busybox.git] / procps / top.c
1 /*
2  * A tiny 'top' utility.
3  *
4  * This is written specifically for the linux /proc/<PID>/status
5  * file format, but it checks that the file actually conforms to the
6  * format that this utility expects.
7  
8  * This reads the PIDs of all processes at startup and then shows the
9  * status of those processes at given intervals.  User can give
10  * maximum number of processes to show.  If a process exits, it's PID
11  * is shown as 'EXIT'.  If new processes are started while this works,
12  * it doesn't add them to the list of shown processes.
13  * 
14  * NOTES:
15  * - At startup this changes to /proc, all the reads are then
16  *   relative to that.
17  * - Includes code from the scandir() manual page.
18  * 
19  * TODO:
20  * - ppid, uid etc could be read only once when program starts
21  *   and rest of the information could be gotten from the
22  *   /proc/<PID>/statm file.
23  * - Add process CPU and memory usage *percentages*.
24  * 
25  * (C) Eero Tamminen <oak at welho dot com>
26  */
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <dirent.h>
31 #include <string.h>
32 #include <sys/ioctl.h>
33 #include "busybox.h"
34
35
36 /* process information taken from /proc,
37  * The code takes into account how long the fields below are,
38  * starting from copying the file from 'status' file to displaying it!
39  */
40 typedef struct {
41         char uid[6];    /* User ID */
42         char pid[6];    /* Pid */
43         char ppid[6];   /* Parent Pid */
44         char name[12];  /* Name */
45         char cmd[20];   /* command line[read/show size] */
46         char state[2];  /* State: S, W... */
47         char size[9];   /* VmSize */
48         char lck[9];    /* VmLck */
49         char rss[9];    /* VmRSS */
50         char data[9];   /* VmData */
51         char stk[9];    /* VmStk */
52         char exe[9];    /* VmExe */
53         char lib[9];    /* VmLib */
54 } status_t;
55
56 /* display generic info (meminfo / loadavg) */
57 static void display_generic(void)
58 {
59         FILE *fp;
60         char buf[80];
61         float avg1, avg2, avg3;
62         unsigned long total, used, mfree, shared, buffers, cached;
63
64         /* read memory info */
65         fp = fopen("meminfo", "r");
66         if (!fp) {
67                 perror("fopen('meminfo')");
68                 return;
69         }
70         fgets(buf, sizeof(buf), fp);    /* skip first line */
71
72         if (fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu",
73                    &total, &used, &mfree, &shared, &buffers, &cached) != 6) {
74                 fprintf(stderr, "Error: failed to read 'meminfo'");
75                 fclose(fp);
76         }
77         fclose(fp);
78         
79         /* read load average */
80         fp = fopen("loadavg", "r");
81         if (!fp) {
82                 perror("fopen('loadavg')");
83                         return;
84         }
85         if (fscanf(fp, "%f %f %f", &avg1, &avg2, &avg3) != 3) {
86                 fprintf(stderr, "Error: failed to read 'loadavg'");
87                 fclose(fp);
88                 return;
89         }
90         fclose(fp);
91
92         /* convert to kilobytes */
93         if (total) total /= 1024;
94         if (used) used /= 1024;
95         if (mfree) mfree /= 1024;
96         if (shared) shared /= 1024;
97         if (buffers) buffers /= 1024;
98         if (cached) cached /= 1024;
99         
100         /* output memory info and load average */
101         printf("Mem: %ldK, %ldK used, %ldK free, %ldK shrd, %ldK buff, %ldK cached\n",
102                total, used, mfree, shared, buffers, cached);
103         printf("Load average: %.2f, %.2f, %.2f    (State: S=sleeping R=running, W=waiting)\n",
104                avg1, avg2, avg3);
105 }
106
107
108 /* display process statuses */
109 static void display_status(int count, const status_t *s)
110 {
111         const char *fmt, *cmd;
112         
113         /* clear screen & go to top */
114         printf("\e[2J\e[1;1H");
115
116         display_generic();
117         
118         /* what info of the processes is shown */
119         printf("\n%*s %*s %*s %*s %*s %*s  %-*s\n",
120                sizeof(s->pid)-1, "Pid:",
121                sizeof(s->state)-1, "",
122                sizeof(s->ppid)-1, "PPid:",
123                sizeof(s->uid)-1, "UID:",
124                sizeof(s->size)-1, "WmSize:",
125                sizeof(s->rss)-1, "WmRSS:",
126                sizeof(s->cmd)-1, "command line:");
127
128         while (count--) {
129                 if (s->cmd[0]) {
130                         /* normal process, has command line */
131                         cmd = s->cmd;
132                         fmt = "%*s %*s %*s %*s %*s %*s  %s\n";
133                 } else {
134                         /* no command line, show only process name */
135                         cmd = s->name;
136                         fmt = "%*s %*s %*s %*s %*s %*s  [%s]\n";
137                 }
138                 printf(fmt,
139                        sizeof(s->pid)-1, s->pid,
140                        sizeof(s->state)-1, s->state,
141                        sizeof(s->ppid)-1, s->ppid,
142                        sizeof(s->uid)-1, s->uid,
143                        sizeof(s->size)-1, s->size,
144                        sizeof(s->rss)-1, s->rss,
145                        cmd);
146                 s++;
147         }
148 }
149
150
151 /* checks if given 'buf' for process starts with 'id' + ':' + TAB
152  * and stores rest of the buf to 'store' with max size 'size'
153  */
154 static int process_status(const char *buf, const char *id, char *store, size_t size)
155 {
156         int len, i;
157         
158         /* check status field name */
159         len = strlen(id);
160         if (strncmp(buf, id, len) != 0) {
161                 if(store)
162                         error_msg_and_die("ERROR status: line doesn't start with '%s' in:\n%s\n", id, buf);
163                 else
164                         return 0;
165         }
166         if (!store) {
167                 /* ignoring this field */
168                 return 1;
169         }
170         buf += len;
171         
172         /* check status field format */
173         if ((*buf++ != ':') || (*buf++ != '\t')) {
174                 error_msg_and_die("ERROR status: field '%s' not followed with ':' + TAB in:\n%s\n", id, buf);
175         }
176         
177         /* skip whitespace in Wm* fields */
178         if (id[0] == 'V' && id[1] == 'm') {
179                 i = 3;
180                 while (i--) {
181                         if (*buf == ' ') {
182                                 buf++;
183                         } else {
184                                 error_msg_and_die("ERROR status: can't skip whitespace for "
185                                         "'%s' field in:\n%s\n", id, buf);
186                         }
187                 }
188         }
189         
190         /* copy at max (size-1) chars and force '\0' to the end */
191         while (--size) {
192                 if (*buf < ' ') {
193                         break;
194                 }
195                 *store++ = *buf++;
196         }
197         *store = '\0';
198         return 1;
199 }
200
201 /* read process statuses */
202 static void read_status(int num, status_t *s)
203 {
204         char status[20];
205         char buf[80];
206         FILE *fp;
207         
208         while (num--) {
209                 sprintf(status, "%s/status", s->pid);
210
211                 /* read the command line from 'cmdline' in PID dir */
212                 fp = fopen(status, "r");
213                 if (!fp) {
214                         strncpy(s->pid, "EXIT", sizeof(s->pid));
215                         s->pid[sizeof(s->pid)-1] = '\0';
216                         fclose(fp);
217                         continue;
218                 }
219
220                 /* get and process the information */
221                 fgets(buf, sizeof(buf), fp);
222                 process_status(buf, "Name", s->name, sizeof(s->name));
223                 fgets(buf, sizeof(buf), fp);
224                 process_status(buf, "State", s->state, sizeof(s->state));
225                 fgets(buf, sizeof(buf), fp);
226                 if(process_status(buf, "Tgid", NULL, 0))
227                         fgets(buf, sizeof(buf), fp);
228                 process_status(buf, "Pid", NULL, 0);
229                 fgets(buf, sizeof(buf), fp);
230                 process_status(buf, "PPid", s->ppid, sizeof(s->ppid));
231                 fgets(buf, sizeof(buf), fp);
232                 if(process_status(buf, "TracerPid", NULL, 0))
233                         fgets(buf, sizeof(buf), fp);
234                 process_status(buf, "Uid", s->uid, sizeof(s->uid));
235                 fgets(buf, sizeof(buf), fp);
236                 process_status(buf, "Gid", NULL, 0);
237                 fgets(buf, sizeof(buf), fp);
238                 if(process_status(buf, "FDSize", NULL, 0))
239                         fgets(buf, sizeof(buf), fp);
240                 process_status(buf, "Groups", NULL, 0);
241                 fgets(buf, sizeof(buf), fp);
242                 /* only user space processes have command line
243                  * and memory statistics
244                  */
245                 if (s->cmd[0]) {
246                         process_status(buf, "VmSize", s->size, sizeof(s->size));
247                         fgets(buf, sizeof(buf), fp);
248                         process_status(buf, "VmLck", s->lck, sizeof(s->lck));
249                         fgets(buf, sizeof(buf), fp);
250                         process_status(buf, "VmRSS", s->rss, sizeof(s->rss));
251                         fgets(buf, sizeof(buf), fp);
252                         process_status(buf, "VmData", s->data, sizeof(s->data));
253                         fgets(buf, sizeof(buf), fp);
254                         process_status(buf, "VmStk", s->stk, sizeof(s->stk));
255                         fgets(buf, sizeof(buf), fp);
256                         process_status(buf, "VmExe", s->exe, sizeof(s->exe));
257                         fgets(buf, sizeof(buf), fp);
258                         process_status(buf, "VmLib", s->lib, sizeof(s->lib));
259                 }
260                 fclose(fp);
261                 
262                 /* next process */
263                 s++;
264         }
265 }
266
267
268 /* allocs statuslist and reads process command lines, frees namelist,
269  * returns filled statuslist or NULL in case of error.
270  */
271 static status_t *read_info(int num, struct dirent **namelist)
272 {
273         status_t *statuslist, *s;
274         char cmdline[20];
275         FILE *fp;
276         int idx;
277
278         /* allocate & zero status for each of the processes */
279         statuslist = calloc(num, sizeof(status_t));
280         if (!statuslist) {
281                 return NULL;
282         }
283         
284         /* go through the processes */
285         for (idx = 0; idx < num; idx++) {
286
287                 /* copy PID string to status struct and free name */
288                 s = &(statuslist[idx]);
289                 if (strlen(namelist[idx]->d_name) > sizeof(s->pid)-1) {
290                         fprintf(stderr, "PID '%s' too long\n", namelist[idx]->d_name);
291                         return NULL;
292                 }
293                 strncpy(s->pid, namelist[idx]->d_name, sizeof(s->pid));
294                 s->pid[sizeof(s->pid)-1] = '\0';
295                 free(namelist[idx]);
296
297                 /* read the command line from 'cmdline' in PID dir */
298                 sprintf(cmdline, "%s/cmdline", s->pid);
299                 fp = fopen(cmdline, "r");
300                 if (!fp) {
301                         fclose(fp);
302                         perror("fopen('cmdline')");
303                         return NULL;
304                 }
305                 fgets(statuslist[idx].cmd, sizeof(statuslist[idx].cmd), fp);
306                 fclose(fp);
307         }
308         free(namelist);
309         return statuslist;
310 }
311
312
313 /* returns true for file names which are PID dirs
314  * (i.e. start with number)
315  */
316 static int filter_pids(const struct dirent *dir)
317 {
318         status_t dummy;
319         char *name = dir->d_name;
320
321         if (*name >= '0' && *name <= '9') {
322                 if (strlen(name) > sizeof(dummy.pid)-1) {
323                         fprintf(stderr, "PID name '%s' too long\n", name);
324                         return 0;
325                 }
326                 return 1;
327         }
328         return 0;
329 }
330
331
332 /* compares two directory entry names as numeric strings
333  */
334 static int num_sort(const void *a, const void *b)
335 {
336         int ia = atoi((*(struct dirent **)a)->d_name);
337         int ib = atoi((*(struct dirent **)b)->d_name);
338         
339         if (ia == ib) {
340                 return 0;
341         }
342         /* NOTE: by switching the check, you change the process sort order */
343         if (ia < ib) {
344                 return -1;
345         } else {
346                 return 1;
347         }
348 }
349
350 int top_main(int argc, char **argv)
351 {
352         status_t *statuslist;
353         struct dirent **namelist;
354         int opt, num, interval, lines;
355 #if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
356         struct winsize win = { 0, 0, 0, 0 };
357 #endif
358         /* Default update rate is 5 seconds */
359         interval = 5;
360         /* Default to 25 lines - 5 lines for status */
361         lines = 25 - 5;
362
363         /* do normal option parsing */
364         while ((opt = getopt(argc, argv, "d:")) > 0) {
365             switch (opt) {
366                 case 'd':
367                     interval = atoi(optarg);
368                     break;
369                 default:
370                     show_usage();
371             }
372         }
373
374 #if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
375         ioctl(fileno(stdout), TIOCGWINSZ, &win);
376         if (win.ws_row > 4)
377             lines = win.ws_row - 5;
378 #endif
379         
380         /* change to proc */
381         if (chdir("/proc") < 0) {
382                 perror_msg_and_die("chdir('/proc')");
383         }
384         
385         /* read process IDs for all the processes from the procfs */
386         num = scandir(".", &namelist, filter_pids, num_sort);
387         if (num < 0) {
388                 perror_msg_and_die("scandir('/proc')");
389         }
390         if (lines > num) {
391                 lines = num;
392         }
393
394         /* read command line for each of the processes */
395         statuslist = read_info(num, namelist);
396         if (!statuslist) {
397                 return EXIT_FAILURE;
398         }
399
400         while (1) {
401                 /* read status for each of the processes */
402                 read_status(num, statuslist);
403
404                 /* display status */
405                 display_status(lines, statuslist);
406                 
407                 sleep(interval);
408         }
409         
410         free(statuslist);
411         return EXIT_SUCCESS;
412 }