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