1/* vi: set sw=4 ts=4: */ 2/* 3 * Mini ps implementation(s) for busybox 4 * 5 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> 6 * Fix for SELinux Support:(c)2007 Hiroshi Shinji <shiroshi@my.email.ne.jp> 7 (c)2007 Yuichi Nakamura <ynakam@hitachisoft.jp> 8 * 9 * Licensed under the GPL version 2, see the file LICENSE in this tarball. 10 */ 11 12#include "libbb.h" 13 14/* Absolute maximum on output line length */ 15enum { MAX_WIDTH = 2*1024 }; 16 17#if ENABLE_DESKTOP 18 19/* Print value to buf, max size+1 chars (including trailing '\0') */ 20 21static void func_user(char *buf, int size, const procps_status_t *ps) 22{ 23 safe_strncpy(buf, get_cached_username(ps->uid), size+1); 24} 25 26static void func_comm(char *buf, int size, const procps_status_t *ps) 27{ 28 safe_strncpy(buf, ps->comm, size+1); 29} 30 31static void func_args(char *buf, int size, const procps_status_t *ps) 32{ 33 read_cmdline(buf, size, ps->pid, ps->comm); 34} 35 36static void func_pid(char *buf, int size, const procps_status_t *ps) 37{ 38 sprintf(buf, "%*u", size, ps->pid); 39} 40 41static void func_ppid(char *buf, int size, const procps_status_t *ps) 42{ 43 sprintf(buf, "%*u", size, ps->ppid); 44} 45 46static void func_pgid(char *buf, int size, const procps_status_t *ps) 47{ 48 sprintf(buf, "%*u", size, ps->pgid); 49} 50 51static void put_u(char *buf, int size, unsigned u) 52{ 53 char buf5[5]; 54 smart_ulltoa5( ((unsigned long long)u) << 10, buf5); 55 sprintf(buf, "%.*s", size, buf5); 56} 57 58static void func_vsz(char *buf, int size, const procps_status_t *ps) 59{ 60 put_u(buf, size, ps->vsz); 61} 62 63static void func_rss(char *buf, int size, const procps_status_t *ps) 64{ 65 put_u(buf, size, ps->rss); 66} 67 68static void func_tty(char *buf, int size, const procps_status_t *ps) 69{ 70 buf[0] = '?'; 71 buf[1] = '\0'; 72 if (ps->tty_major) /* tty field of "0" means "no tty" */ 73 snprintf(buf, size+1, "%u,%u", ps->tty_major, ps->tty_minor); 74} 75 76#if ENABLE_SELINUX 77static void func_label(char *buf, int size, const procps_status_t *ps) 78{ 79 safe_strncpy(buf, ps->context ? ps->context : "unknown", size+1); 80} 81#endif 82 83/* 84static void func_nice(char *buf, int size, const procps_status_t *ps) 85{ 86 ps->??? 87} 88 89static void func_etime(char *buf, int size, const procps_status_t *ps) 90{ 91 elapled time [[dd-]hh:]mm:ss 92} 93 94static void func_time(char *buf, int size, const procps_status_t *ps) 95{ 96 cumulative time [[dd-]hh:]mm:ss 97} 98 99static void func_pcpu(char *buf, int size, const procps_status_t *ps) 100{ 101} 102*/ 103 104typedef struct { 105 uint16_t width; 106 char name[6]; 107 const char *header; 108 void (*f)(char *buf, int size, const procps_status_t *ps); 109 int ps_flags; 110} ps_out_t; 111 112static const ps_out_t out_spec[] = { 113// Mandated by POSIX: 114 { 8 , "user" ,"USER" ,func_user ,PSSCAN_UIDGID }, 115 { 16 , "comm" ,"COMMAND",func_comm ,PSSCAN_COMM }, 116 { 256 , "args" ,"COMMAND",func_args ,PSSCAN_COMM }, 117 { 5 , "pid" ,"PID" ,func_pid ,PSSCAN_PID }, 118 { 5 , "ppid" ,"PPID" ,func_ppid ,PSSCAN_PPID }, 119 { 5 , "pgid" ,"PGID" ,func_pgid ,PSSCAN_PGID }, 120// { sizeof("ELAPSED")-1, "etime" ,"ELAPSED",func_etime ,PSSCAN_ }, 121// { sizeof("GROUP" )-1, "group" ,"GROUP" ,func_group ,PSSCAN_UIDGID }, 122// { sizeof("NI" )-1, "nice" ,"NI" ,func_nice ,PSSCAN_ }, 123// { sizeof("%CPU" )-1, "pcpu" ,"%CPU" ,func_pcpu ,PSSCAN_ }, 124// { sizeof("RGROUP" )-1, "rgroup","RGROUP" ,func_rgroup,PSSCAN_UIDGID }, 125// { sizeof("RUSER" )-1, "ruser" ,"RUSER" ,func_ruser ,PSSCAN_UIDGID }, 126// { sizeof("TIME" )-1, "time" ,"TIME" ,func_time ,PSSCAN_ }, 127 { 6 , "tty" ,"TT" ,func_tty ,PSSCAN_TTY }, 128 { 4 , "vsz" ,"VSZ" ,func_vsz ,PSSCAN_VSZ }, 129// Not mandated by POSIX, but useful: 130 { 4 , "rss" ,"RSS" ,func_rss ,PSSCAN_RSS }, 131#if ENABLE_SELINUX 132 { 35 , "label" ,"LABEL" ,func_label ,PSSCAN_CONTEXT }, 133#endif 134}; 135 136#if ENABLE_SELINUX 137#define SELINIX_O_PREFIX "label," 138#define DEFAULT_O_STR SELINIX_O_PREFIX "pid,user" /* TODO: ,vsz,stat */ ",args" 139#else 140#define DEFAULT_O_STR "pid,user" /* TODO: ,vsz,stat */ ",args" 141#endif 142 143struct globals { 144 ps_out_t* out; 145 int out_cnt; 146 int print_header; 147 int need_flags; 148 char *buffer; 149 unsigned terminal_width; 150 char default_o[sizeof(DEFAULT_O_STR)]; 151}; 152#define G (*(struct globals*)&bb_common_bufsiz1) 153#define out (G.out ) 154#define out_cnt (G.out_cnt ) 155#define print_header (G.print_header ) 156#define need_flags (G.need_flags ) 157#define buffer (G.buffer ) 158#define terminal_width (G.terminal_width) 159#define default_o (G.default_o ) 160 161static ps_out_t* new_out_t(void) 162{ 163 int i = out_cnt++; 164 out = xrealloc(out, out_cnt * sizeof(*out)); 165 return &out[i]; 166} 167 168static const ps_out_t* find_out_spec(const char *name) 169{ 170 int i; 171 for (i = 0; i < ARRAY_SIZE(out_spec); i++) { 172 if (!strcmp(name, out_spec[i].name)) 173 return &out_spec[i]; 174 } 175 bb_error_msg_and_die("bad -o argument '%s'", name); 176} 177 178static void parse_o(char* opt) 179{ 180 ps_out_t* new; 181 char *comma, *equal; 182 while (1) { 183 comma = strchr(opt, ','); 184 equal = strchr(opt, '='); 185 if (comma && (!equal || equal > comma)) { 186 *comma = '\0'; 187 *new_out_t() = *find_out_spec(opt); 188 *comma = ','; 189 opt = comma + 1; 190 continue; 191 } 192 break; 193 } 194 // opt points to last spec in comma separated list. 195 // This one can have =HEADER part. 196 new = new_out_t(); 197 if (equal) 198 *equal = '\0'; 199 *new = *find_out_spec(opt); 200 if (equal) { 201 *equal = '='; 202 new->header = equal + 1; 203 // POSIX: the field widths shall be ... at least as wide as 204 // the header text (default or overridden value). 205 // If the header text is null, such as -o user=, 206 // the field width shall be at least as wide as the 207 // default header text 208 if (new->header[0]) { 209 new->width = strlen(new->header); 210 print_header = 1; 211 } 212 } else 213 print_header = 1; 214} 215 216static void post_process(void) 217{ 218 int i; 219 int width = 0; 220 for (i = 0; i < out_cnt; i++) { 221 need_flags |= out[i].ps_flags; 222 if (out[i].header[0]) { 223 print_header = 1; 224 } 225 width += out[i].width + 1; /* "FIELD " */ 226 } 227#if ENABLE_SELINUX 228 if (!is_selinux_enabled()) 229 need_flags &= ~PSSCAN_CONTEXT; 230#endif 231 buffer = xmalloc(width + 1); /* for trailing \0 */ 232} 233 234static void format_header(void) 235{ 236 int i; 237 ps_out_t* op; 238 char *p; 239 240 if (!print_header) 241 return; 242 p = buffer; 243 i = 0; 244 if (out_cnt) { 245 while (1) { 246 op = &out[i]; 247 if (++i == out_cnt) /* do not pad last field */ 248 break; 249 p += sprintf(p, "%-*s ", op->width, op->header); 250 } 251 strcpy(p, op->header); 252 } 253 printf("%.*s\n", terminal_width, buffer); 254} 255 256static void format_process(const procps_status_t *ps) 257{ 258 int i, len; 259 char *p = buffer; 260 i = 0; 261 if (out_cnt) while (1) { 262 out[i].f(p, out[i].width, ps); 263 // POSIX: Any field need not be meaningful in all 264 // implementations. In such a case a hyphen ( '-' ) 265 // should be output in place of the field value. 266 if (!p[0]) { 267 p[0] = '-'; 268 p[1] = '\0'; 269 } 270 len = strlen(p); 271 p += len; 272 len = out[i].width - len + 1; 273 if (++i == out_cnt) /* do not pad last field */ 274 break; 275 p += sprintf(p, "%*s", len, ""); 276 } 277 printf("%.*s\n", terminal_width, buffer); 278} 279 280int ps_main(int argc, char **argv); 281int ps_main(int argc, char **argv) 282{ 283 procps_status_t *p; 284 llist_t* opt_o = NULL; 285 USE_SELINUX(int opt;) 286 287 // POSIX: 288 // -a Write information for all processes associated with terminals 289 // Implementations may omit session leaders from this list 290 // -A Write information for all processes 291 // -d Write information for all processes, except session leaders 292 // -e Write information for all processes (equivalent to -A.) 293 // -f Generate a full listing 294 // -l Generate a long listing 295 // -o col1,col2,col3=header 296 // Select which columns to display 297 opt_complementary = "o::"; 298 USE_SELINUX(opt =) getopt32(argv, "Zo:aAdefl", &opt_o); 299 if (opt_o) { 300 do { 301 parse_o(opt_o->data); 302 opt_o = opt_o->link; 303 } while (opt_o); 304 } else { 305 /* Below: parse_o() needs char*, NOT const char*... */ 306#if ENABLE_SELINUX 307 if (!(opt & 1) || !is_selinux_enabled()) { 308 /* no -Z or no SELinux: do not show LABEL */ 309 strcpy(default_o, DEFAULT_O_STR + sizeof(SELINIX_O_PREFIX)-1); 310 } else 311#endif 312 { 313 strcpy(default_o, DEFAULT_O_STR); 314 } 315 parse_o(default_o); 316 } 317 post_process(); 318 319 /* Was INT_MAX, but some libc's go belly up with printf("%.*s") 320 * and such large widths */ 321 terminal_width = MAX_WIDTH; 322 if (isatty(1)) { 323 get_terminal_width_height(0, &terminal_width, NULL); 324 if (--terminal_width > MAX_WIDTH) 325 terminal_width = MAX_WIDTH; 326 } 327 format_header(); 328 329 p = NULL; 330 while ((p = procps_scan(p, need_flags))) { 331 format_process(p); 332 } 333 334 return EXIT_SUCCESS; 335} 336 337 338#else /* !ENABLE_DESKTOP */ 339 340 341int ps_main(int argc, char **argv); 342int ps_main(int argc, char **argv) 343{ 344 procps_status_t *p = NULL; 345 int len; 346 SKIP_SELINUX(const) int use_selinux = 0; 347 USE_SELINUX(int i;) 348#if !ENABLE_FEATURE_PS_WIDE 349 enum { terminal_width = 79 }; 350#else 351 int terminal_width; 352 int w_count = 0; 353#endif 354 355#if ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX 356#if ENABLE_FEATURE_PS_WIDE 357 opt_complementary = "-:ww"; 358 USE_SELINUX(i =) getopt32(argv, USE_SELINUX("Z") "w", &w_count); 359 /* if w is given once, GNU ps sets the width to 132, 360 * if w is given more than once, it is "unlimited" 361 */ 362 if (w_count) { 363 terminal_width = (w_count==1) ? 132 : MAX_WIDTH; 364 } else { 365 get_terminal_width_height(0, &terminal_width, NULL); 366 /* Go one less... */ 367 if (--terminal_width > MAX_WIDTH) 368 terminal_width = MAX_WIDTH; 369 } 370#else /* only ENABLE_SELINUX */ 371 i = getopt32(argv, "Z"); 372#endif 373#if ENABLE_SELINUX 374 if ((i & 1) && is_selinux_enabled()) 375 use_selinux = PSSCAN_CONTEXT; 376#endif 377#endif /* ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX */ 378 379 if (use_selinux) 380 puts(" PID Context Stat Command"); 381 else 382 puts(" PID Uid VSZ Stat Command"); 383 384 while ((p = procps_scan(p, 0 385 | PSSCAN_PID 386 | PSSCAN_UIDGID 387 | PSSCAN_STATE 388 | PSSCAN_VSZ 389 | PSSCAN_COMM 390 | use_selinux 391 ))) { 392#if ENABLE_SELINUX 393 if (use_selinux) { 394 len = printf("%5u %-32s %s ", 395 p->pid, 396 p->context ? p->context : "unknown", 397 p->state); 398 } else 399#endif 400 { 401 const char *user = get_cached_username(p->uid); 402 if (p->vsz == 0) 403 len = printf("%5u %-8s %s ", 404 p->pid, user, p->state); 405 else 406 len = printf("%5u %-8s %6u %s ", 407 p->pid, user, p->vsz, p->state); 408 } 409 410 { 411 int sz = terminal_width - len; 412 char buf[sz + 1]; 413 read_cmdline(buf, sz, p->pid, p->comm); 414 puts(buf); 415 } 416 } 417 if (ENABLE_FEATURE_CLEAN_UP) 418 clear_username_cache(); 419 return EXIT_SUCCESS; 420} 421 422#endif /* ENABLE_DESKTOP */ 423