ps: implement POSIX-like options, most notably -o (activated by CONFIG_DESKTOP)
No binary (build failed)
—
(log yok)
| @@ -2377,6 +2377,18 @@ USE_FEATURE_MDEV_CONFIG( \ | |
| "$ printf \"Val=%d\\n\" 5\n" \ | |
| "Val=5\n" | |
| + | |
| + | #if ENABLE_DESKTOP |
| + | |
| + | #define ps_trivial_usage \ |
| + | "" |
| + | #define ps_full_usage \ |
| + | "Report process status\n" \ |
| + | "\nOptions:" \ |
| + | "\n\t-o col1,col2=header\tSelect columns for display" \ |
| + | |
| + | #else /* !ENABLE_DESKTOP */ |
| + | |
| #if !defined CONFIG_SELINUX && !ENABLE_FEATURE_PS_WIDE | |
| #define USAGE_PS "\n\tThis version of ps accepts no options." | |
| #else | |
| @@ -2396,6 +2408,8 @@ USE_FEATURE_MDEV_CONFIG( \ | |
| USE_SELINUX("\n\t-c\tshow SE Linux context") \ | |
| USAGE_PS_WIDE("\n\tw\twide output") | |
| + | #endif /* ENABLE_DESKTOP */ |
| + | |
| #define ps_example_usage \ | |
| "$ ps\n" \ | |
| " PID Uid Gid State Command\n" \ | |
| @@ -2409,6 +2423,7 @@ USE_FEATURE_MDEV_CONFIG( \ | |
| " 745 root root S [getty]\n" \ | |
| " 2990 andersen andersen R ps\n" | |
| + | |
| #define pwd_trivial_usage \ | |
| "" | |
| #define pwd_full_usage \ | |
| @@ -9,6 +9,279 @@ | |
| #include "busybox.h" | |
| + | #if ENABLE_DESKTOP |
| + | |
| + | /* Print value to buf, max size+1 chars (including trailing '\0') */ |
| + | |
| + | void func_user(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | safe_strncpy(buf, get_cached_username(ps->uid), size+1); |
| + | } |
| + | |
| + | void func_comm(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | safe_strncpy(buf, ps->comm, size+1); |
| + | } |
| + | |
| + | void func_args(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | buf[0] = '\0'; |
| + | if (ps->cmd) |
| + | safe_strncpy(buf, ps->cmd, size+1); |
| + | else if (size >= 2) |
| + | snprintf(buf, size+1, "[%.*s]", size-2, ps->comm); |
| + | } |
| + | |
| + | void func_pid(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | snprintf(buf, size+1, "%*u", size, ps->pid); |
| + | } |
| + | |
| + | void func_ppid(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | snprintf(buf, size+1, "%*u", size, ps->ppid); |
| + | } |
| + | |
| + | void func_pgid(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | snprintf(buf, size+1, "%*u", size, ps->pgid); |
| + | } |
| + | |
| + | void func_rss(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | char buf5[5]; |
| + | smart_ulltoa5( ((unsigned long long)ps->rss) << 10, buf5); |
| + | snprintf(buf, size+1, "%.*s", size, buf5); |
| + | } |
| + | |
| + | /* |
| + | void func_nice(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | ps->??? |
| + | } |
| + | |
| + | void func_etime(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | elapled time [[dd-]hh:]mm:ss |
| + | } |
| + | |
| + | void func_time(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | cumulative time [[dd-]hh:]mm:ss |
| + | } |
| + | |
| + | void func_pcpu(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | } |
| + | |
| + | void func_tty(char *buf, int size, const procps_status_t *ps) |
| + | { |
| + | } |
| + | */ |
| + | |
| + | typedef struct { |
| + | char name[8]; |
| + | const char *header; |
| + | void (*f)(char *buf, int size, const procps_status_t *ps); |
| + | int ps_flags; |
| + | int width; |
| + | } ps_out_t; |
| + | |
| + | static const ps_out_t out_spec[] = { |
| + | // Mandated by POSIX: |
| + | { "user" ,"USER" ,func_user ,PSSCAN_UIDGID,8 }, |
| + | { "comm" ,"COMMAND",func_comm ,PSSCAN_COMM ,16 }, |
| + | { "args" ,"COMMAND",func_args ,PSSCAN_CMD|PSSCAN_COMM,256 }, |
| + | { "pid" ,"PID" ,func_pid ,PSSCAN_PID ,5 }, |
| + | { "ppid" ,"PPID" ,func_ppid ,PSSCAN_PPID ,5 }, |
| + | { "pgid" ,"PGID" ,func_pgid ,PSSCAN_PGID ,5 }, |
| + | // { "etime" ,"ELAPSED",func_etime ,PSSCAN_ ,sizeof("ELAPSED")-1 }, |
| + | // { "group" ,"GROUP" ,func_group ,PSSCAN_UIDGID,sizeof("GROUP" )-1 }, |
| + | // { "nice" ,"NI" ,func_nice ,PSSCAN_ ,sizeof("NI" )-1 }, |
| + | // { "pcpu" ,"%CPU" ,func_pcpu ,PSSCAN_ ,sizeof("%CPU" )-1 }, |
| + | // { "rgroup","RGROUP" ,func_rgroup,PSSCAN_UIDGID,sizeof("RGROUP" )-1 }, |
| + | // { "ruser" ,"RUSER" ,func_ruser ,PSSCAN_UIDGID,sizeof("RUSER" )-1 }, |
| + | // { "time" ,"TIME" ,func_time ,PSSCAN_ ,sizeof("TIME" )-1 }, |
| + | // { "tty" ,"TT" ,func_tty ,PSSCAN_ ,sizeof("TT" )-1 }, |
| + | // { "vsz" ,"VSZ" ,func_vsz ,PSSCAN_VSZ ,4 }, |
| + | // Not mandated by POSIX: |
| + | { "rss" ,"RSS" ,func_rss ,PSSCAN_RSS ,4 }, |
| + | }; |
| + | |
| + | #define VEC_SIZE(v) ( sizeof(v) / sizeof((v)[0]) ) |
| + | |
| + | static ps_out_t* out; |
| + | static int out_cnt; |
| + | static int print_header; |
| + | static int ps_flags; |
| + | static char *buffer; |
| + | static unsigned terminal_width; |
| + | |
| + | |
| + | static ps_out_t* new_out_t(void) |
| + | { |
| + | int i = out_cnt++; |
| + | out = xrealloc(out, out_cnt * sizeof(*out)); |
| + | return &out[i]; |
| + | } |
| + | |
| + | static const ps_out_t* find_out_spec(const char *name) |
| + | { |
| + | int i; |
| + | for (i = 0; i < VEC_SIZE(out_spec); i++) { |
| + | if (!strcmp(name, out_spec[i].name)) |
| + | return &out_spec[i]; |
| + | } |
| + | bb_error_msg_and_die("bad -o argument '%s'", name); |
| + | } |
| + | |
| + | static void parse_o(char* opt) |
| + | { |
| + | ps_out_t* new; |
| + | // POSIX: "-o is blank- or comma-separated list" (FIXME) |
| + | char *comma, *equal; |
| + | while (1) { |
| + | comma = strchr(opt, ','); |
| + | equal = strchr(opt, '='); |
| + | if (comma && (!equal || equal > comma)) { |
| + | *comma = '\0'; |
| + | *new_out_t() = *find_out_spec(opt); |
| + | *comma = ','; |
| + | opt = comma + 1; |
| + | continue; |
| + | } |
| + | break; |
| + | } |
| + | new = new_out_t(); |
| + | if (equal) |
| + | *equal = '\0'; |
| + | *new = *find_out_spec(opt); |
| + | if (equal) { |
| + | *equal = '='; |
| + | new->header = equal + 1; |
| + | // POSIX: the field widths shall be ... at least as wide as |
| + | // the header text (default or overridden value). |
| + | // If the header text is null, such as -o user=, |
| + | // the field width shall be at least as wide as the |
| + | // default header text |
| + | if (new->header[0]) { |
| + | new->width = strlen(new->header); |
| + | print_header = 1; |
| + | } |
| + | } else |
| + | print_header = 1; |
| + | } |
| + | |
| + | static void post_process(void) |
| + | { |
| + | int i; |
| + | int width = 0; |
| + | for (i = 0; i < out_cnt; i++) { |
| + | ps_flags |= out[i].ps_flags; |
| + | if (out[i].header[0]) { |
| + | print_header = 1; |
| + | } |
| + | width += out[i].width + 1; /* "FIELD " */ |
| + | } |
| + | buffer = xmalloc(width + 1); /* for trailing \0 */ |
| + | } |
| + | |
| + | static void format_header(void) |
| + | { |
| + | int i; |
| + | ps_out_t* op; |
| + | char *p = buffer; |
| + | if (!print_header) |
| + | return; |
| + | i = 0; |
| + | if (out_cnt) { |
| + | while (1) { |
| + | op = &out[i]; |
| + | if (++i == out_cnt) /* do not pad last field */ |
| + | break; |
| + | p += sprintf(p, "%-*s ", op->width, op->header); |
| + | } |
| + | strcpy(p, op->header); |
| + | } |
| + | printf("%.*s\n", terminal_width, buffer); |
| + | } |
| + | |
| + | static void format_process(const procps_status_t *ps) |
| + | { |
| + | int i, len; |
| + | char *p = buffer; |
| + | i = 0; |
| + | if (out_cnt) while (1) { |
| + | out[i].f(p, out[i].width, ps); |
| + | // POSIX: Any field need not be meaningful in all |
| + | // implementations. In such a case a hyphen ( '-' ) |
| + | // should be output in place of the field value. |
| + | if (!*p) { |
| + | *p++ = '-'; |
| + | *p = '\0'; |
| + | } |
| + | len = strlen(p); |
| + | p += len; |
| + | len = out[i].width - len + 1; |
| + | if (++i == out_cnt) /* do not pad last field */ |
| + | break; |
| + | while (len--) |
| + | *p++ = ' '; |
| + | *p = '\0'; |
| + | } |
| + | printf("%.*s\n", terminal_width, buffer); |
| + | } |
| + | |
| + | /* Cannot be const: parse_o() will choke */ |
| + | static char default_o[] = "pid,user" /* TODO: ,vsz,stat */ ",args"; |
| + | |
| + | int ps_main(int argc, char **argv) |
| + | { |
| + | procps_status_t *p; |
| + | llist_t* opt_o = NULL; |
| + | |
| + | // POSIX: |
| + | // -a Write information for all processes associated with terminals |
| + | // Implementations may omit session leaders from this list |
| + | // -A Write information for all processes |
| + | // -d Write information for all processes, except session leaders |
| + | // -e Write information for all processes (equivalent to -A.) |
| + | // -f Generate a full listing |
| + | // -l Generate a long listing |
| + | // -o col1,col2,col3=header |
| + | // Select which columns to distplay |
| + | /* We allow (and ignore) most of the above. FIXME */ |
| + | opt_complementary = "o::"; |
| + | getopt32(argc, argv, "o:aAdefl", &opt_o); |
| + | if (opt_o) { |
| + | opt_o = rev_llist(opt_o); |
| + | do { |
| + | parse_o(opt_o->data); |
| + | opt_o = opt_o->link; |
| + | } while (opt_o); |
| + | } else |
| + | parse_o(default_o); |
| + | post_process(); |
| + | |
| + | terminal_width = INT_MAX; |
| + | if (isatty(1)) { |
| + | get_terminal_width_height(1, &terminal_width, NULL); |
| + | terminal_width--; |
| + | } |
| + | format_header(); |
| + | |
| + | p = NULL; |
| + | while ((p = procps_scan(p, ps_flags))) { |
| + | format_process(p); |
| + | } |
| + | |
| + | return EXIT_SUCCESS; |
| + | } |
| + | |
| + | |
| + | #else /* !ENABLE_DESKTOP */ |
| + | |
| + | |
| int ps_main(int argc, char **argv) | |
| { | |
| procps_status_t *p = NULL; | |
| @@ -111,3 +384,5 @@ int ps_main(int argc, char **argv) | |
| clear_username_cache(); | |
| return EXIT_SUCCESS; | |
| } | |
| + | |
| + | #endif |