From 73b16af8feec390afbabd9356d6e5e83c0390838 Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Fri, 15 May 2015 10:20:47 +0200 Subject: busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bjørn Mork --- procps/Config.in | 200 ++++++++++ procps/Kbuild | 21 + procps/free.c | 68 ++++ procps/fuser.c | 346 +++++++++++++++++ procps/kill.c | 184 +++++++++ procps/nmeter.c | 911 +++++++++++++++++++++++++++++++++++++++++++ procps/pgrep.c | 144 +++++++ procps/pidof.c | 86 +++++ procps/ps.c | 570 +++++++++++++++++++++++++++ procps/ps.posix | 175 +++++++++ procps/renice.c | 128 +++++++ procps/sysctl.c | 268 +++++++++++++ procps/top.c | 1126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ procps/uptime.c | 60 +++ procps/watch.c | 75 ++++ 15 files changed, 4362 insertions(+) create mode 100644 procps/Config.in create mode 100644 procps/Kbuild create mode 100644 procps/free.c create mode 100644 procps/fuser.c create mode 100644 procps/kill.c create mode 100644 procps/nmeter.c create mode 100644 procps/pgrep.c create mode 100644 procps/pidof.c create mode 100644 procps/ps.c create mode 100644 procps/ps.posix create mode 100644 procps/renice.c create mode 100644 procps/sysctl.c create mode 100644 procps/top.c create mode 100644 procps/uptime.c create mode 100644 procps/watch.c (limited to 'procps') diff --git a/procps/Config.in b/procps/Config.in new file mode 100644 index 0000000..702442a --- /dev/null +++ b/procps/Config.in @@ -0,0 +1,200 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Process Utilities" + +config FREE + bool "free" + default n + help + free displays the total amount of free and used physical and swap + memory in the system, as well as the buffers used by the kernel. + The shared memory column should be ignored; it is obsolete. + +config FUSER + bool "fuser" + default n + help + fuser lists all PIDs (Process IDs) that currently have a given + file open. fuser can also list all PIDs that have a given network + (TCP or UDP) port open. + +config KILL + bool "kill" + default n + help + The command kill sends the specified signal to the specified + process or process group. If no signal is specified, the TERM + signal is sent. + +config KILLALL + bool "killall" + default n + depends on KILL + help + killall sends a signal to all processes running any of the + specified commands. If no signal name is specified, SIGTERM is + sent. + +config KILLALL5 + bool "killall5" + default n + depends on KILL + +config NMETER + bool "nmeter" + default n + help + Prints selected system stats continuously, one line per update. + +config PGREP + bool "pgrep" + default n + help + Look for processes by name. + +config PIDOF + bool "pidof" + default n + help + Pidof finds the process id's (pids) of the named programs. It prints + those id's on the standard output. + +config FEATURE_PIDOF_SINGLE + bool "Enable argument for single shot (-s)" + default n + depends on PIDOF + help + Support argument '-s' for returning only the first pid found. + +config FEATURE_PIDOF_OMIT + bool "Enable argument for omitting pids (-o)" + default n + depends on PIDOF + help + Support argument '-o' for omitting the given pids in output. + The special pid %PPID can be used to name the parent process + of the pidof, in other words the calling shell or shell script. + +config PKILL + bool "pkill" + default n + help + Send signals to processes by name. + +config PS + bool "ps" + default n + help + ps gives a snapshot of the current processes. + +config FEATURE_PS_WIDE + bool "Enable argument for wide output (-w)" + default n + depends on PS + help + Support argument 'w' for wide output. + If given once, 132 chars are printed and given more than + one, the length is unlimited. + +config FEATURE_PS_TIME + bool "Enable time and elapsed time output" + default n + depends on PS && DESKTOP + help + Support -o time and -o etime output specifiers. + +config FEATURE_PS_UNUSUAL_SYSTEMS + bool "Support Linux prior to 2.4.0 and non-ELF systems" + default n + depends on FEATURE_PS_TIME + help + Include support for measuring HZ on old kernels and non-ELF systems + (if you are on Linux 2.4.0+ and use ELF, you don't need this) + +config RENICE + bool "renice" + default n + help + Renice alters the scheduling priority of one or more running + processes. + +config BB_SYSCTL + bool "sysctl" + default n + help + Configure kernel parameters at runtime. + +config TOP + bool "top" + default n + help + The top program provides a dynamic real-time view of a running + system. + +config FEATURE_TOP_CPU_USAGE_PERCENTAGE + bool "Show CPU per-process usage percentage" + default y + depends on TOP + help + Make top display CPU usage for each process. + This adds about 2k. + +config FEATURE_TOP_CPU_GLOBAL_PERCENTS + bool "Show CPU global usage percentage" + default y + depends on FEATURE_TOP_CPU_USAGE_PERCENTAGE + help + Makes top display "CPU: NN% usr NN% sys..." line. + This adds about 0.5k. + +config FEATURE_TOP_SMP_CPU + bool "SMP CPU usage display ('c' key)" + default n + depends on FEATURE_TOP_CPU_GLOBAL_PERCENTS + help + Allow 'c' key to switch between individual/cumulative CPU stats + This adds about 0.5k. + +config FEATURE_TOP_DECIMALS + bool "Show 1/10th of a percent in CPU/mem statistics" + default n + depends on FEATURE_TOP_CPU_USAGE_PERCENTAGE + help + Show 1/10th of a percent in CPU/mem statistics. + This adds about 0.3k. + +config FEATURE_TOP_SMP_PROCESS + bool "Show CPU process runs on ('j' field)" + default n + depends on TOP + help + Show CPU where process was last found running on. + This is the 'j' field. + +config FEATURE_TOPMEM + bool "Topmem command ('s' key)" + default n + depends on TOP + help + Enable 's' in top (gives lots of memory info). + +config UPTIME + bool "uptime" + default n + help + uptime gives a one line display of the current time, how long + the system has been running, how many users are currently logged + on, and the system load averages for the past 1, 5, and 15 minutes. + +config WATCH + bool "watch" + default n + help + watch is used to execute a program periodically, showing + output to the screen. + + +endmenu diff --git a/procps/Kbuild b/procps/Kbuild new file mode 100644 index 0000000..8e62fdf --- /dev/null +++ b/procps/Kbuild @@ -0,0 +1,21 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_FREE) += free.o +lib-$(CONFIG_FUSER) += fuser.o +lib-$(CONFIG_KILL) += kill.o +lib-$(CONFIG_ASH) += kill.o # used for built-in kill by ash +lib-$(CONFIG_NMETER) += nmeter.o +lib-$(CONFIG_PGREP) += pgrep.o +lib-$(CONFIG_PKILL) += pgrep.o +lib-$(CONFIG_PIDOF) += pidof.o +lib-$(CONFIG_PS) += ps.o +lib-$(CONFIG_RENICE) += renice.o +lib-$(CONFIG_BB_SYSCTL) += sysctl.o +lib-$(CONFIG_TOP) += top.o +lib-$(CONFIG_UPTIME) += uptime.o +lib-$(CONFIG_WATCH) += watch.o diff --git a/procps/free.c b/procps/free.c new file mode 100644 index 0000000..e76dd21 --- /dev/null +++ b/procps/free.c @@ -0,0 +1,68 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini free implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +/* getopt not needed */ + +#include "libbb.h" + +int free_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int free_main(int argc, char **argv) +{ + struct sysinfo info; + sysinfo(&info); + + /* Kernels prior to 2.4.x will return info.mem_unit==0, so cope... */ + if (info.mem_unit == 0) { + info.mem_unit=1; + } + if (info.mem_unit == 1) { + info.mem_unit=1024; + + /* TODO: Make all this stuff not overflow when mem >= 4 Gib */ + info.totalram/=info.mem_unit; + info.freeram/=info.mem_unit; +#ifndef __uClinux__ + info.totalswap/=info.mem_unit; + info.freeswap/=info.mem_unit; +#endif + info.sharedram/=info.mem_unit; + info.bufferram/=info.mem_unit; + } else { + info.mem_unit/=1024; + /* TODO: Make all this stuff not overflow when mem >= 4 Gib */ + info.totalram*=info.mem_unit; + info.freeram*=info.mem_unit; +#ifndef __uClinux__ + info.totalswap*=info.mem_unit; + info.freeswap*=info.mem_unit; +#endif + info.sharedram*=info.mem_unit; + info.bufferram*=info.mem_unit; + } + + if (argc > 1 && *argv[1] == '-') + bb_show_usage(); + + printf("%6s%13s%13s%13s%13s%13s\n", "", "total", "used", "free", + "shared", "buffers"); + + printf("%6s%13ld%13ld%13ld%13ld%13ld\n", "Mem:", info.totalram, + info.totalram-info.freeram, info.freeram, + info.sharedram, info.bufferram); + +#ifndef __uClinux__ + printf("%6s%13ld%13ld%13ld\n", "Swap:", info.totalswap, + info.totalswap-info.freeswap, info.freeswap); + + printf("%6s%13ld%13ld%13ld\n", "Total:", info.totalram+info.totalswap, + (info.totalram-info.freeram)+(info.totalswap-info.freeswap), + info.freeram+info.freeswap); +#endif + return EXIT_SUCCESS; +} diff --git a/procps/fuser.c b/procps/fuser.c new file mode 100644 index 0000000..d2ac9eb --- /dev/null +++ b/procps/fuser.c @@ -0,0 +1,346 @@ +/* vi: set sw=4 ts=4: */ +/* + * tiny fuser implementation + * + * Copyright 2004 Tony J. White + * + * May be distributed under the conditions of the + * GNU Library General Public License + */ + +#include "libbb.h" + +#define MAX_LINE 255 + +#define OPTION_STRING "mks64" +enum { + OPT_MOUNT = (1 << 0), + OPT_KILL = (1 << 1), + OPT_SILENT = (1 << 2), + OPT_IP6 = (1 << 3), + OPT_IP4 = (1 << 4), +}; + +typedef struct inode_list { + struct inode_list *next; + ino_t inode; + dev_t dev; +} inode_list; + +typedef struct pid_list { + struct pid_list *next; + pid_t pid; +} pid_list; + +static dev_t find_socket_dev(void) +{ + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd >= 0) { + struct stat buf; + int r = fstat(fd, &buf); + close(fd); + if (r == 0) + return buf.st_dev; + } + return 0; +} + +static int file_to_dev_inode(const char *filename, dev_t *dev, ino_t *inode) +{ + struct stat f_stat; + if (stat(filename, &f_stat)) + return 0; + *inode = f_stat.st_ino; + *dev = f_stat.st_dev; + return 1; +} + +static char *parse_net_arg(const char *arg, unsigned *port) +{ + char path[20], tproto[5]; + + if (sscanf(arg, "%u/%4s", port, tproto) != 2) + return NULL; + sprintf(path, "/proc/net/%s", tproto); + if (access(path, R_OK) != 0) + return NULL; + return xstrdup(tproto); +} + +static pid_list *add_pid(pid_list *plist, pid_t pid) +{ + pid_list *curr = plist; + while (curr != NULL) { + if (curr->pid == pid) + return plist; + curr = curr->next; + } + curr = xmalloc(sizeof(pid_list)); + curr->pid = pid; + curr->next = plist; + return curr; +} + +static inode_list *add_inode(inode_list *ilist, dev_t dev, ino_t inode) +{ + inode_list *curr = ilist; + while (curr != NULL) { + if (curr->inode == inode && curr->dev == dev) + return ilist; + curr = curr->next; + } + curr = xmalloc(sizeof(inode_list)); + curr->dev = dev; + curr->inode = inode; + curr->next = ilist; + return curr; +} + +static inode_list *scan_proc_net(const char *proto, + unsigned port, inode_list *ilist) +{ + char path[20], line[MAX_LINE + 1]; + ino_t tmp_inode; + dev_t tmp_dev; + long long uint64_inode; + unsigned tmp_port; + FILE *f; + + tmp_dev = find_socket_dev(); + + sprintf(path, "/proc/net/%s", proto); + f = fopen_for_read(path); + if (!f) + return ilist; + + while (fgets(line, MAX_LINE, f)) { + char addr[68]; + if (sscanf(line, "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x " + "%*x:%*x %*x %*d %*d %llu", + addr, &tmp_port, &uint64_inode) == 3 + ) { + int len = strlen(addr); + if (len == 8 && (option_mask32 & OPT_IP6)) + continue; + if (len > 8 && (option_mask32 & OPT_IP4)) + continue; + if (tmp_port == port) { + tmp_inode = uint64_inode; + ilist = add_inode(ilist, tmp_dev, tmp_inode); + } + } + } + fclose(f); + return ilist; +} + +static int search_dev_inode(inode_list *ilist, dev_t dev, ino_t inode) +{ + while (ilist) { + if (ilist->dev == dev) { + if (option_mask32 & OPT_MOUNT) + return 1; + if (ilist->inode == inode) + return 1; + } + ilist = ilist->next; + } + return 0; +} + +static pid_list *scan_pid_maps(const char *fname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + FILE *file; + char line[MAX_LINE + 1]; + int major, minor; + ino_t inode; + long long uint64_inode; + dev_t dev; + + file = fopen_for_read(fname); + if (!file) + return plist; + while (fgets(line, MAX_LINE, file)) { + if (sscanf(line, "%*s %*s %*s %x:%x %llu", &major, &minor, &uint64_inode) != 3) + continue; + inode = uint64_inode; + if (major == 0 && minor == 0 && inode == 0) + continue; + dev = makedev(major, minor); + if (search_dev_inode(ilist, dev, inode)) + plist = add_pid(plist, pid); + } + fclose(file); + return plist; +} + +static pid_list *scan_link(const char *lname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + ino_t inode; + dev_t dev; + + if (!file_to_dev_inode(lname, &dev, &inode)) + return plist; + if (search_dev_inode(ilist, dev, inode)) + plist = add_pid(plist, pid); + return plist; +} + +static pid_list *scan_dir_links(const char *dname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + DIR *d; + struct dirent *de; + char *lname; + + d = opendir(dname); + if (!d) + return plist; + while ((de = readdir(d)) != NULL) { + lname = concat_subpath_file(dname, de->d_name); + if (lname == NULL) + continue; + plist = scan_link(lname, pid, ilist, plist); + free(lname); + } + closedir(d); + return plist; +} + +/* NB: does chdir internally */ +static pid_list *scan_proc_pids(inode_list *ilist) +{ + DIR *d; + struct dirent *de; + pid_t pid; + pid_list *plist; + + xchdir("/proc"); + d = opendir("/proc"); + if (!d) + return NULL; + + plist = NULL; + while ((de = readdir(d)) != NULL) { + pid = (pid_t)bb_strtou(de->d_name, NULL, 10); + if (errno) + continue; + if (chdir(de->d_name) < 0) + continue; + plist = scan_link("cwd", pid, ilist, plist); + plist = scan_link("exe", pid, ilist, plist); + plist = scan_link("root", pid, ilist, plist); + plist = scan_dir_links("fd", pid, ilist, plist); + plist = scan_dir_links("lib", pid, ilist, plist); + plist = scan_dir_links("mmap", pid, ilist, plist); + plist = scan_pid_maps("maps", pid, ilist, plist); + xchdir("/proc"); + } + closedir(d); + return plist; +} + +static int print_pid_list(pid_list *plist) +{ + while (plist != NULL) { + printf("%u ", (unsigned)plist->pid); + plist = plist->next; + } + bb_putchar('\n'); + return 1; +} + +static int kill_pid_list(pid_list *plist, int sig) +{ + pid_t mypid = getpid(); + int success = 1; + + while (plist != NULL) { + if (plist->pid != mypid) { + if (kill(plist->pid, sig) != 0) { + bb_perror_msg("kill pid %u", (unsigned)plist->pid); + success = 0; + } + } + plist = plist->next; + } + return success; +} + +int fuser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int fuser_main(int argc UNUSED_PARAM, char **argv) +{ + pid_list *plist; + inode_list *ilist; + char **pp; + dev_t dev; + ino_t inode; + unsigned port; + int opt; + int success; + int killsig; +/* +fuser [options] FILEs or PORT/PROTOs +Find processes which use FILEs or PORTs + -m Find processes which use same fs as FILEs + -4 Search only IPv4 space + -6 Search only IPv6 space + -s Silent: just exit with 0 if any processes are found + -k Kill found processes (otherwise display PIDs) + -SIGNAL Signal to send (default: TERM) +*/ + /* Handle -SIGNAL. Oh my... */ + killsig = SIGTERM; + pp = argv; + while (*++pp) { + char *arg = *pp; + if (arg[0] != '-') + continue; + if (arg[1] == '-' && arg[2] == '\0') /* "--" */ + break; + if ((arg[1] == '4' || arg[1] == '6') && arg[2] == '\0') + continue; /* it's "-4" or "-6" */ + opt = get_signum(&arg[1]); + if (opt < 0) + continue; + /* "-SIGNAL" option found. Remove it and bail out */ + killsig = opt; + do { + pp[0] = arg = pp[1]; + pp++; + } while (arg); + break; + } + + opt = getopt32(argv, OPTION_STRING); + argv += optind; + + ilist = NULL; + pp = argv; + while (*pp) { + char *proto = parse_net_arg(*pp, &port); + if (proto) { /* PORT/PROTO */ + ilist = scan_proc_net(proto, port, ilist); + free(proto); + } else { /* FILE */ + if (!file_to_dev_inode(*pp, &dev, &inode)) + bb_perror_msg_and_die("can't open %s", *pp); + ilist = add_inode(ilist, dev, inode); + } + pp++; + } + + plist = scan_proc_pids(ilist); /* changes dir to "/proc" */ + + if (!plist) + return EXIT_FAILURE; + success = 1; + if (opt & OPT_KILL) { + success = kill_pid_list(plist, killsig); + } else if (!(opt & OPT_SILENT)) { + success = print_pid_list(plist); + } + return (success != 1); /* 0 == success */ +} diff --git a/procps/kill.c b/procps/kill.c new file mode 100644 index 0000000..1405500 --- /dev/null +++ b/procps/kill.c @@ -0,0 +1,184 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini kill/killall[5] implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +/* Note: kill_main is directly called from shell in order to implement + * kill built-in. Shell substitutes job ids with process groups first. + * + * This brings some complications: + * + * + we can't use xfunc here + * + we can't use applet_name + * + we can't use bb_show_usage + * (Above doesn't apply for killall[5] cases) + * + * kill %n gets translated into kill ' -' by shell (note space!) + * This is needed to avoid collision with kill -9 ... syntax + */ + +int kill_main(int argc, char **argv) +{ + char *arg; + pid_t pid; + int signo = SIGTERM, errors = 0, quiet = 0; +#if !ENABLE_KILLALL && !ENABLE_KILLALL5 +#define killall 0 +#define killall5 0 +#else +/* How to determine who we are? find 3rd char from the end: + * kill, killall, killall5 + * ^i ^a ^l - it's unique + * (checking from the start is complicated by /bin/kill... case) */ + const char char3 = argv[0][strlen(argv[0]) - 3]; +#define killall (ENABLE_KILLALL && char3 == 'a') +#define killall5 (ENABLE_KILLALL5 && char3 == 'l') +#endif + + /* Parse any options */ + argc--; + arg = *++argv; + + if (argc < 1 || arg[0] != '-') { + goto do_it_now; + } + + /* The -l option, which prints out signal names. + * Intended usage in shell: + * echo "Died of SIG`kill -l $?`" + * We try to mimic what kill from coreutils-6.8 does */ + if (arg[1] == 'l' && arg[2] == '\0') { + if (argc == 1) { + /* Print the whole signal list */ + print_signames(); + return 0; + } + /* -l */ + while ((arg = *++argv)) { + if (isdigit(arg[0])) { + signo = bb_strtou(arg, NULL, 10); + if (errno) { + bb_error_msg("unknown signal '%s'", arg); + return EXIT_FAILURE; + } + /* Exitcodes >= 0x80 are to be treated + * as "killed by signal (exitcode & 0x7f)" */ + puts(get_signame(signo & 0x7f)); + /* TODO: 'bad' signal# - coreutils says: + * kill: 127: invalid signal + * we just print "127" instead */ + } else { + signo = get_signum(arg); + if (signo < 0) { + bb_error_msg("unknown signal '%s'", arg); + return EXIT_FAILURE; + } + printf("%d\n", signo); + } + } + /* If they specified -l, we are all done */ + return EXIT_SUCCESS; + } + + /* The -q quiet option */ + if (killall && arg[1] == 'q' && arg[2] == '\0') { + quiet = 1; + arg = *++argv; + argc--; + if (argc < 1) bb_show_usage(); + if (arg[0] != '-') goto do_it_now; + } + + arg++; /* skip '-' */ + if (argc > 1 && arg[0] == 's' && arg[1] == '\0') { /* -s SIG? */ + argc--; + arg = *++argv; + } /* else it must be -SIG */ + signo = get_signum(arg); + if (signo < 0) { /* || signo > MAX_SIGNUM ? */ + bb_error_msg("bad signal name '%s'", arg); + return EXIT_FAILURE; + } + arg = *++argv; + argc--; + +do_it_now: + pid = getpid(); + + if (killall5) { + pid_t sid; + procps_status_t* p = NULL; + + /* Find out our own session id */ + sid = getsid(pid); + /* Now stop all processes */ + kill(-1, SIGSTOP); + /* Now kill all processes except our session */ + while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_SID))) { + if (p->sid != (unsigned)sid && p->pid != (unsigned)pid && p->pid != 1) + kill(p->pid, signo); + } + /* And let them continue */ + kill(-1, SIGCONT); + return 0; + } + + /* Pid or name is required for kill/killall */ + if (argc < 1) { + bb_error_msg("you need to specify whom to kill"); + return EXIT_FAILURE; + } + + if (killall) { + /* Looks like they want to do a killall. Do that */ + while (arg) { + pid_t* pidList; + + pidList = find_pid_by_name(arg); + if (*pidList == 0) { + errors++; + if (!quiet) + bb_error_msg("%s: no process killed", arg); + } else { + pid_t *pl; + + for (pl = pidList; *pl; pl++) { + if (*pl == pid) + continue; + if (kill(*pl, signo) == 0) + continue; + errors++; + if (!quiet) + bb_perror_msg("cannot kill pid %u", (unsigned)*pl); + } + } + free(pidList); + arg = *++argv; + } + return errors; + } + + /* Looks like they want to do a kill. Do that */ + while (arg) { + /* Support shell 'space' trick */ + if (arg[0] == ' ') + arg++; + pid = bb_strtoi(arg, NULL, 10); + if (errno) { + bb_error_msg("bad pid '%s'", arg); + errors++; + } else if (kill(pid, signo) != 0) { + bb_perror_msg("cannot kill pid %d", (int)pid); + errors++; + } + arg = *++argv; + } + return errors; +} diff --git a/procps/nmeter.c b/procps/nmeter.c new file mode 100644 index 0000000..068c739 --- /dev/null +++ b/procps/nmeter.c @@ -0,0 +1,911 @@ +/* +** Licensed under the GPL v2, see the file LICENSE in this tarball +** +** Based on nanotop.c from floppyfw project +** +** Contact me: vda.linux@googlemail.com */ + +//TODO: +// simplify code +// /proc/locks +// /proc/stat: +// disk_io: (3,0):(22272,17897,410702,4375,54750) +// btime 1059401962 +//TODO: use sysinfo libc call/syscall, if appropriate +// (faster than open/read/close): +// sysinfo({uptime=15017, loads=[5728, 15040, 16480] +// totalram=2107416576, freeram=211525632, sharedram=0, bufferram=157204480} +// totalswap=134209536, freeswap=134209536, procs=157}) + +#include +#include "libbb.h" + +typedef unsigned long long ullong; + +enum { /* Preferably use powers of 2 */ + PROC_MIN_FILE_SIZE = 256, + PROC_MAX_FILE_SIZE = 16 * 1024, +}; + +typedef struct proc_file { + char *file; + int file_sz; + smallint last_gen; +} proc_file; + +static const char *const proc_name[] = { + "stat", // Must match the order of proc_file's! + "loadavg", + "net/dev", + "meminfo", + "diskstats", + "sys/fs/file-nr" +}; + +struct globals { + // Sample generation flip-flop + smallint gen; + // Linux 2.6? (otherwise assumes 2.4) + smallint is26; + // 1 if sample delay is not an integer fraction of a second + smallint need_seconds; + char *cur_outbuf; + const char *final_str; + int delta; + int deltanz; + struct timeval tv; +#define first_proc_file proc_stat + proc_file proc_stat; // Must match the order of proc_name's! + proc_file proc_loadavg; + proc_file proc_net_dev; + proc_file proc_meminfo; + proc_file proc_diskstats; + proc_file proc_sys_fs_filenr; +}; +#define G (*ptr_to_globals) +#define gen (G.gen ) +#define is26 (G.is26 ) +#define need_seconds (G.need_seconds ) +#define cur_outbuf (G.cur_outbuf ) +#define final_str (G.final_str ) +#define delta (G.delta ) +#define deltanz (G.deltanz ) +#define tv (G.tv ) +#define proc_stat (G.proc_stat ) +#define proc_loadavg (G.proc_loadavg ) +#define proc_net_dev (G.proc_net_dev ) +#define proc_meminfo (G.proc_meminfo ) +#define proc_diskstats (G.proc_diskstats ) +#define proc_sys_fs_filenr (G.proc_sys_fs_filenr) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ + cur_outbuf = outbuf; \ + final_str = "\n"; \ + deltanz = delta = 1000000; \ +} while (0) + +// We depend on this being a char[], not char* - we take sizeof() of it +#define outbuf bb_common_bufsiz1 + +static inline void reset_outbuf(void) +{ + cur_outbuf = outbuf; +} + +static inline int outbuf_count(void) +{ + return cur_outbuf - outbuf; +} + +static void print_outbuf(void) +{ + int sz = cur_outbuf - outbuf; + if (sz > 0) { + xwrite(STDOUT_FILENO, outbuf, sz); + cur_outbuf = outbuf; + } +} + +static void put(const char *s) +{ + int sz = strlen(s); + if (sz > outbuf + sizeof(outbuf) - cur_outbuf) + sz = outbuf + sizeof(outbuf) - cur_outbuf; + memcpy(cur_outbuf, s, sz); + cur_outbuf += sz; +} + +static void put_c(char c) +{ + if (cur_outbuf < outbuf + sizeof(outbuf)) + *cur_outbuf++ = c; +} + +static void put_question_marks(int count) +{ + while (count--) + put_c('?'); +} + +static void readfile_z(proc_file *pf, const char* fname) +{ +// open_read_close() will do two reads in order to be sure we are at EOF, +// and we don't need/want that. + int fd; + int sz, rdsz; + char *buf; + + sz = pf->file_sz; + buf = pf->file; + if (!buf) { + buf = xmalloc(PROC_MIN_FILE_SIZE); + sz = PROC_MIN_FILE_SIZE; + } + again: + fd = xopen(fname, O_RDONLY); + buf[0] = '\0'; + rdsz = read(fd, buf, sz-1); + close(fd); + if (rdsz > 0) { + if (rdsz == sz-1 && sz < PROC_MAX_FILE_SIZE) { + sz *= 2; + buf = xrealloc(buf, sz); + goto again; + } + buf[rdsz] = '\0'; + } + pf->file_sz = sz; + pf->file = buf; +} + +static const char* get_file(proc_file *pf) +{ + if (pf->last_gen != gen) { + pf->last_gen = gen; + readfile_z(pf, proc_name[pf - &first_proc_file]); + } + return pf->file; +} + +static ullong read_after_slash(const char *p) +{ + p = strchr(p, '/'); + if (!p) return 0; + return strtoull(p+1, NULL, 10); +} + +enum conv_type { conv_decimal, conv_slash }; + +// Reads decimal values from line. Values start after key, for example: +// "cpu 649369 0 341297 4336769..." - key is "cpu" here. +// Values are stored in vec[]. arg_ptr has list of positions +// we are interested in: for example: 1,2,5 - we want 1st, 2nd and 5th value. +static int vrdval(const char* p, const char* key, + enum conv_type conv, ullong *vec, va_list arg_ptr) +{ + int indexline; + int indexnext; + + p = strstr(p, key); + if (!p) return 1; + + p += strlen(key); + indexline = 1; + indexnext = va_arg(arg_ptr, int); + while (1) { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\n' || *p == '\0') break; + + if (indexline == indexnext) { // read this value + *vec++ = conv==conv_decimal ? + strtoull(p, NULL, 10) : + read_after_slash(p); + indexnext = va_arg(arg_ptr, int); + } + while (*p > ' ') p++; // skip over value + indexline++; + } + return 0; +} + +// Parses files with lines like "cpu0 21727 0 15718 1813856 9461 10485 0 0": +// rdval(file_contents, "string_to_find", result_vector, value#, value#...) +// value# start with 1 +static int rdval(const char* p, const char* key, ullong *vec, ...) +{ + va_list arg_ptr; + int result; + + va_start(arg_ptr, vec); + result = vrdval(p, key, conv_decimal, vec, arg_ptr); + va_end(arg_ptr); + + return result; +} + +// Parses files with lines like "... ... ... 3/148 ...." +static int rdval_loadavg(const char* p, ullong *vec, ...) +{ + va_list arg_ptr; + int result; + + va_start(arg_ptr, vec); + result = vrdval(p, "", conv_slash, vec, arg_ptr); + va_end(arg_ptr); + + return result; +} + +// Parses /proc/diskstats +// 1 2 3 4 5 6(rd) 7 8 9 10(wr) 11 12 13 14 +// 3 0 hda 51292 14441 841783 926052 25717 79650 843256 3029804 0 148459 3956933 +// 3 1 hda1 0 0 0 0 <- ignore if only 4 fields +static int rdval_diskstats(const char* p, ullong *vec) +{ + ullong rd = rd; // for compiler + int indexline = 0; + vec[0] = 0; + vec[1] = 0; + while (1) { + indexline++; + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + if (*p == '\n') { + indexline = 0; + p++; + continue; + } + if (indexline == 6) { + rd = strtoull(p, NULL, 10); + } else if (indexline == 10) { + vec[0] += rd; // TODO: *sectorsize (don't know how to find out sectorsize) + vec[1] += strtoull(p, NULL, 10); + while (*p != '\n' && *p != '\0') p++; + continue; + } + while (*p > ' ') p++; // skip over value + } + return 0; +} + +static void scale(ullong ul) +{ + char buf[5]; + + /* see http://en.wikipedia.org/wiki/Tera */ + smart_ulltoa4(ul, buf, " kmgtpezy"); + buf[4] = '\0'; + put(buf); +} + + +#define S_STAT(a) \ +typedef struct a { \ + struct s_stat *next; \ + void (*collect)(struct a *s); \ + const char *label; +#define S_STAT_END(a) } a; + +S_STAT(s_stat) +S_STAT_END(s_stat) + +static void collect_literal(s_stat *s UNUSED_PARAM) +{ +} + +static s_stat* init_literal(void) +{ + s_stat *s = xzalloc(sizeof(*s)); + s->collect = collect_literal; + return (s_stat*)s; +} + +static s_stat* init_delay(const char *param) +{ + delta = strtoul(param, NULL, 0) * 1000; /* param can be "" */ + deltanz = delta > 0 ? delta : 1; + need_seconds = (1000000%deltanz) != 0; + return NULL; +} + +static s_stat* init_cr(const char *param UNUSED_PARAM) +{ + final_str = "\r"; + return (s_stat*)0; +} + + +// user nice system idle iowait irq softirq (last 3 only in 2.6) +//cpu 649369 0 341297 4336769 11640 7122 1183 +//cpuN 649369 0 341297 4336769 11640 7122 1183 +enum { CPU_FIELDCNT = 7 }; +S_STAT(cpu_stat) + ullong old[CPU_FIELDCNT]; + int bar_sz; + char *bar; +S_STAT_END(cpu_stat) + + +static void collect_cpu(cpu_stat *s) +{ + ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 }; + unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 }; + ullong all = 0; + int norm_all = 0; + int bar_sz = s->bar_sz; + char *bar = s->bar; + int i; + + if (rdval(get_file(&proc_stat), "cpu ", data, 1, 2, 3, 4, 5, 6, 7)) { + put_question_marks(bar_sz); + return; + } + + for (i = 0; i < CPU_FIELDCNT; i++) { + ullong old = s->old[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + all += (data[i] -= old); + } + + if (all) { + for (i = 0; i < CPU_FIELDCNT; i++) { + ullong t = bar_sz * data[i]; + norm_all += data[i] = t / all; + frac[i] = t % all; + } + + while (norm_all < bar_sz) { + unsigned max = frac[0]; + int pos = 0; + for (i = 1; i < CPU_FIELDCNT; i++) { + if (frac[i] > max) max = frac[i], pos = i; + } + frac[pos] = 0; //avoid bumping up same value twice + data[pos]++; + norm_all++; + } + + memset(bar, '.', bar_sz); + memset(bar, 'S', data[2]); bar += data[2]; //sys + memset(bar, 'U', data[0]); bar += data[0]; //usr + memset(bar, 'N', data[1]); bar += data[1]; //nice + memset(bar, 'D', data[4]); bar += data[4]; //iowait + memset(bar, 'I', data[5]); bar += data[5]; //irq + memset(bar, 'i', data[6]); bar += data[6]; //softirq + } else { + memset(bar, '?', bar_sz); + } + put(s->bar); +} + + +static s_stat* init_cpu(const char *param) +{ + int sz; + cpu_stat *s = xzalloc(sizeof(*s)); + s->collect = collect_cpu; + sz = strtoul(param, NULL, 0); /* param can be "" */ + if (sz < 10) sz = 10; + if (sz > 1000) sz = 1000; + s->bar = xzalloc(sz+1); + /*s->bar[sz] = '\0'; - xzalloc did it */ + s->bar_sz = sz; + return (s_stat*)s; +} + + +S_STAT(int_stat) + ullong old; + int no; +S_STAT_END(int_stat) + +static void collect_int(int_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "intr", data, s->no)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_int(const char *param) +{ + int_stat *s = xzalloc(sizeof(*s)); + s->collect = collect_int; + if (param[0] == '\0') { + s->no = 1; + } else { + int n = xatoi_u(param); + s->no = n + 2; + } + return (s_stat*)s; +} + + +S_STAT(ctx_stat) + ullong old; +S_STAT_END(ctx_stat) + +static void collect_ctx(ctx_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "ctxt", data, 1)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_ctx(const char *param UNUSED_PARAM) +{ + ctx_stat *s = xzalloc(sizeof(*s)); + s->collect = collect_ctx; + return (s_stat*)s; +} + + +S_STAT(blk_stat) + const char* lookfor; + ullong old[2]; +S_STAT_END(blk_stat) + +static void collect_blk(blk_stat *s) +{ + ullong data[2]; + int i; + + if (is26) { + i = rdval_diskstats(get_file(&proc_diskstats), data); + } else { + i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2); + // Linux 2.4 reports bio in Kbytes, convert to sectors: + data[0] *= 2; + data[1] *= 2; + } + if (i) { + put_question_marks(9); + return; + } + + for (i=0; i<2; i++) { + ullong old = s->old[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + data[i] -= old; + } + scale(data[0]*512); // TODO: *sectorsize + put_c(' '); + scale(data[1]*512); +} + +static s_stat* init_blk(const char *param UNUSED_PARAM) +{ + blk_stat *s = xzalloc(sizeof(*s)); + s->collect = collect_blk; + s->lookfor = "page"; + return (s_stat*)s; +} + + +S_STAT(fork_stat) + ullong old; +S_STAT_END(fork_stat) + +static void collect_thread_nr(fork_stat *s UNUSED_PARAM) +{ + ullong data[1]; + + if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) { + put_question_marks(4); + return; + } + scale(data[0]); +} + +static void collect_fork(fork_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "processes", data, 1)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_fork(const char *param) +{ + fork_stat *s = xzalloc(sizeof(*s)); + if (*param == 'n') { + s->collect = collect_thread_nr; + } else { + s->collect = collect_fork; + } + return (s_stat*)s; +} + + +S_STAT(if_stat) + ullong old[4]; + const char *device; + char *device_colon; +S_STAT_END(if_stat) + +static void collect_if(if_stat *s) +{ + ullong data[4]; + int i; + + if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) { + put_question_marks(10); + return; + } + + for (i=0; i<4; i++) { + ullong old = s->old[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + data[i] -= old; + } + put_c(data[1] ? '*' : ' '); + scale(data[0]); + put_c(data[3] ? '*' : ' '); + scale(data[2]); +} + +static s_stat* init_if(const char *device) +{ + if_stat *s = xzalloc(sizeof(*s)); + + if (!device || !device[0]) + bb_show_usage(); + s->collect = collect_if; + + s->device = device; + s->device_colon = xasprintf("%s:", device); + return (s_stat*)s; +} + + +S_STAT(mem_stat) + char opt; +S_STAT_END(mem_stat) + +// "Memory" value should not include any caches. +// IOW: neither "ls -laR /" nor heavy read/write activity +// should affect it. We'd like to also include any +// long-term allocated kernel-side mem, but it is hard +// to figure out. For now, bufs, cached & slab are +// counted as "free" memory +//2.6.16: +//MemTotal: 773280 kB +//MemFree: 25912 kB - genuinely free +//Buffers: 320672 kB - cache +//Cached: 146396 kB - cache +//SwapCached: 0 kB +//Active: 183064 kB +//Inactive: 356892 kB +//HighTotal: 0 kB +//HighFree: 0 kB +//LowTotal: 773280 kB +//LowFree: 25912 kB +//SwapTotal: 131064 kB +//SwapFree: 131064 kB +//Dirty: 48 kB +//Writeback: 0 kB +//Mapped: 96620 kB +//Slab: 200668 kB - takes 7 Mb on my box fresh after boot, +// but includes dentries and inodes +// (== can take arbitrary amount of mem) +//CommitLimit: 517704 kB +//Committed_AS: 236776 kB +//PageTables: 1248 kB +//VmallocTotal: 516052 kB +//VmallocUsed: 3852 kB +//VmallocChunk: 512096 kB +//HugePages_Total: 0 +//HugePages_Free: 0 +//Hugepagesize: 4096 kB +static void collect_mem(mem_stat *s) +{ + ullong m_total = 0; + ullong m_free = 0; + ullong m_bufs = 0; + ullong m_cached = 0; + ullong m_slab = 0; + + if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) { + put_question_marks(4); + return; + } + if (s->opt == 't') { + scale(m_total << 10); + return; + } + + if (rdval(proc_meminfo.file, "MemFree:", &m_free , 1) + || rdval(proc_meminfo.file, "Buffers:", &m_bufs , 1) + || rdval(proc_meminfo.file, "Cached:", &m_cached, 1) + || rdval(proc_meminfo.file, "Slab:", &m_slab , 1) + ) { + put_question_marks(4); + return; + } + + m_free += m_bufs + m_cached + m_slab; + switch (s->opt) { + case 'f': + scale(m_free << 10); break; + default: + scale((m_total - m_free) << 10); break; + } +} + +static s_stat* init_mem(const char *param) +{ + mem_stat *s = xzalloc(sizeof(*s)); + s->collect = collect_mem; + s->opt = param[0]; + return (s_stat*)s; +} + + +S_STAT(swp_stat) +S_STAT_END(swp_stat) + +static void collect_swp(swp_stat *s UNUSED_PARAM) +{ + ullong s_total[1]; + ullong s_free[1]; + if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1) + || rdval(proc_meminfo.file, "SwapFree:" , s_free, 1) + ) { + put_question_marks(4); + return; + } + scale((s_total[0]-s_free[0]) << 10); +} + +static s_stat* init_swp(const char *param UNUSED_PARAM) +{ + swp_stat *s = xzalloc(sizeof(*s)); + s->collect = collect_swp; + return (s_stat*)s; +} + + +S_STAT(fd_stat) +S_STAT_END(fd_stat) + +static void collect_fd(fd_stat *s UNUSED_PARAM) +{ + ullong data[2]; + + if (rdval(get_file(&proc_sys_fs_filenr), "", data, 1, 2)) { + put_question_marks(4); + return; + } + + scale(data[0] - data[1]); +} + +static s_stat* init_fd(const char *param UNUSED_PARAM) +{ + fd_stat *s = xzalloc(sizeof(*s)); + s->collect = collect_fd; + return (s_stat*)s; +} + + +S_STAT(time_stat) + int prec; + int scale; +S_STAT_END(time_stat) + +static void collect_time(time_stat *s) +{ + char buf[sizeof("12:34:56.123456")]; + struct tm* tm; + int us = tv.tv_usec + s->scale/2; + time_t t = tv.tv_sec; + + if (us >= 1000000) { + t++; + us -= 1000000; + } + tm = localtime(&t); + + sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); + if (s->prec) + sprintf(buf+8, ".%0*d", s->prec, us / s->scale); + put(buf); +} + +static s_stat* init_time(const char *param) +{ + int prec; + time_stat *s = xzalloc(sizeof(*s)); + + s->collect = collect_time; + prec = param[0] - '0'; + if (prec < 0) prec = 0; + else if (prec > 6) prec = 6; + s->prec = prec; + s->scale = 1; + while (prec++ < 6) + s->scale *= 10; + return (s_stat*)s; +} + +static void collect_info(s_stat *s) +{ + gen ^= 1; + while (s) { + put(s->label); + s->collect(s); + s = s->next; + } +} + + +typedef s_stat* init_func(const char *param); + +static const char options[] ALIGN1 = "ncmsfixptbdr"; +static init_func *const init_functions[] = { + init_if, + init_cpu, + init_mem, + init_swp, + init_fd, + init_int, + init_ctx, + init_fork, + init_time, + init_blk, + init_delay, + init_cr +}; + +int nmeter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int nmeter_main(int argc, char **argv) +{ + char buf[32]; + s_stat *first = NULL; + s_stat *last = NULL; + s_stat *s; + char *cur, *prev; + + INIT_G(); + + xchdir("/proc"); + + if (argc != 2) + bb_show_usage(); + + if (open_read_close("version", buf, sizeof(buf)-1) > 0) { + buf[sizeof(buf)-1] = '\0'; + is26 = (strstr(buf, " 2.4.") == NULL); + } + + // Can use argv[1] directly, but this will mess up + // parameters as seen by e.g. ps. Making a copy... + cur = xstrdup(argv[1]); + while (1) { + char *param, *p; + prev = cur; + again: + cur = strchr(cur, '%'); + if (!cur) + break; + if (cur[1] == '%') { // %% + overlapping_strcpy(cur, cur + 1); + cur++; + goto again; + } + *cur++ = '\0'; // overwrite % + if (cur[0] == '[') { + // format: %[foptstring] + cur++; + p = strchr(options, cur[0]); + param = cur+1; + while (cur[0] != ']') { + if (!cur[0]) + bb_show_usage(); + cur++; + } + *cur++ = '\0'; // overwrite [ + } else { + // format: %NNNNNNf + param = cur; + while (cur[0] >= '0' && cur[0] <= '9') + cur++; + if (!cur[0]) + bb_show_usage(); + p = strchr(options, cur[0]); + *cur++ = '\0'; // overwrite format char + } + if (!p) + bb_show_usage(); + s = init_functions[p-options](param); + if (s) { + s->label = prev; + /*s->next = NULL; - all initXXX funcs use xzalloc */ + if (!first) + first = s; + else + last->next = s; + last = s; + } else { + // %NNNNd or %r option. remove it from string + strcpy(prev + strlen(prev), cur); + cur = prev; + } + } + if (prev[0]) { + s = init_literal(); + s->label = prev; + /*s->next = NULL; - all initXXX funcs use xzalloc */ + if (!first) + first = s; + else + last->next = s; + last = s; + } + + // Generate first samples but do not print them, they're bogus + collect_info(first); + reset_outbuf(); + if (delta >= 0) { + gettimeofday(&tv, NULL); + usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz); + } + + while (1) { + gettimeofday(&tv, NULL); + collect_info(first); + put(final_str); + print_outbuf(); + + // Negative delta -> no usleep at all + // This will hog the CPU but you can have REALLY GOOD + // time resolution ;) + // TODO: detect and avoid useless updates + // (like: nothing happens except time) + if (delta >= 0) { + int rem; + // can be commented out, will sacrifice sleep time precision a bit + gettimeofday(&tv, NULL); + if (need_seconds) + rem = delta - ((ullong)tv.tv_sec*1000000 + tv.tv_usec) % deltanz; + else + rem = delta - tv.tv_usec%deltanz; + // Sometimes kernel wakes us up just a tiny bit earlier than asked + // Do not go to very short sleep in this case + if (rem < delta/128) { + rem += delta; + } + usleep(rem); + } + } + + /*return 0;*/ +} diff --git a/procps/pgrep.c b/procps/pgrep.c new file mode 100644 index 0000000..0e8e529 --- /dev/null +++ b/procps/pgrep.c @@ -0,0 +1,144 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini pgrep/pkill implementation for busybox + * + * Copyright (C) 2007 Loic Grenie + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" +#include "xregex.h" + +/* Idea taken from kill.c */ +#define pgrep (ENABLE_PGREP && applet_name[1] == 'g') +#define pkill (ENABLE_PKILL && applet_name[1] == 'k') + +enum { + /* "vlfxon" */ + PGREPOPTBIT_V = 0, /* must be first, we need OPT_INVERT = 0/1 */ + PGREPOPTBIT_L, + PGREPOPTBIT_F, + PGREPOPTBIT_X, + PGREPOPTBIT_O, + PGREPOPTBIT_N, +}; + +#define OPT_INVERT (opt & (1 << PGREPOPTBIT_V)) +#define OPT_LIST (opt & (1 << PGREPOPTBIT_L)) +#define OPT_FULL (opt & (1 << PGREPOPTBIT_F)) +#define OPT_ANCHOR (opt & (1 << PGREPOPTBIT_X)) +#define OPT_FIRST (opt & (1 << PGREPOPTBIT_O)) +#define OPT_LAST (opt & (1 << PGREPOPTBIT_N)) + +static void act(unsigned pid, char *cmd, int signo, unsigned opt) +{ + if (pgrep) { + if (OPT_LIST) + printf("%d %s\n", pid, cmd); + else + printf("%d\n", pid); + } else + kill(pid, signo); +} + +int pgrep_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int pgrep_main(int argc UNUSED_PARAM, char **argv) +{ + unsigned pid = getpid(); + int signo = SIGTERM; + unsigned opt; + int scan_mask = PSSCAN_COMM; + char *first_arg; + int first_arg_idx; + int matched_pid; + char *cmd_last; + procps_status_t *proc; + /* These are initialized to 0 */ + struct { + regex_t re_buffer; + regmatch_t re_match[1]; + } Z; +#define re_buffer (Z.re_buffer) +#define re_match (Z.re_match ) + + memset(&Z, 0, sizeof(Z)); + + /* We must avoid interpreting -NUM (signal num) as an option */ + first_arg_idx = 1; + while (1) { + first_arg = argv[first_arg_idx]; + if (!first_arg) + break; + /* not "-..."? */ + if (first_arg[0] != '-' || first_arg[1] < 'a' || first_arg[1] > 'z') { + argv[first_arg_idx] = NULL; /* terminate argv here */ + break; + } + first_arg_idx++; + } + opt = getopt32(argv, "vlfxon"); + argv[first_arg_idx] = first_arg; + + argv += optind; + //argc -= optind; - unused anyway + if (OPT_FULL) + scan_mask |= PSSCAN_ARGVN; + + if (pkill) { + if (OPT_LIST) { /* -l: print the whole signal list */ + print_signames(); + return 0; + } + if (first_arg && first_arg[0] == '-') { + signo = get_signum(&first_arg[1]); + if (signo < 0) /* || signo > MAX_SIGNUM ? */ + bb_error_msg_and_die("bad signal name '%s'", &first_arg[1]); + argv++; + } + } + + /* One pattern is required */ + if (!argv[0] || argv[1]) + bb_show_usage(); + + xregcomp(&re_buffer, argv[0], 0); + matched_pid = 0; + cmd_last = NULL; + proc = NULL; + while ((proc = procps_scan(proc, scan_mask)) != NULL) { + char *cmd; + if (proc->pid == pid) + continue; + cmd = proc->argv0; + if (!cmd) { + cmd = proc->comm; + } else { + int i = proc->argv_len; + while (i) { + if (!cmd[i]) cmd[i] = ' '; + i--; + } + } + /* NB: OPT_INVERT is always 0 or 1 */ + if ((regexec(&re_buffer, cmd, 1, re_match, 0) == 0 /* match found */ + && (!OPT_ANCHOR || (re_match[0].rm_so == 0 && re_match[0].rm_eo == (regoff_t)strlen(cmd)))) ^ OPT_INVERT + ) { + matched_pid = proc->pid; + if (OPT_LAST) { + free(cmd_last); + cmd_last = xstrdup(cmd); + continue; + } + act(proc->pid, cmd, signo, opt); + if (OPT_FIRST) + break; + } + } + if (cmd_last) { + act(matched_pid, cmd_last, signo, opt); + if (ENABLE_FEATURE_CLEAN_UP) + free(cmd_last); + } + return matched_pid == 0; /* return 1 if no processes listed/signaled */ +} diff --git a/procps/pidof.c b/procps/pidof.c new file mode 100644 index 0000000..7805044 --- /dev/null +++ b/procps/pidof.c @@ -0,0 +1,86 @@ +/* vi: set sw=4 ts=4: */ +/* + * pidof implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +enum { + USE_FEATURE_PIDOF_SINGLE(OPTBIT_SINGLE,) + USE_FEATURE_PIDOF_OMIT( OPTBIT_OMIT ,) + OPT_SINGLE = USE_FEATURE_PIDOF_SINGLE((1<data, "%PPID") == 0) { + omits_p->data = utoa((unsigned)getppid()); + } + omits_p = omits_p->link; + } + } +#endif + /* Looks like everything is set to go. */ + argv += optind; + while (*argv) { + pid_t *pidList; + pid_t *pl; + + /* reverse the pidlist like GNU pidof does. */ + pidList = pidlist_reverse(find_pid_by_name(*argv)); + for (pl = pidList; *pl; pl++) { +#if ENABLE_FEATURE_PIDOF_OMIT + if (opt & OPT_OMIT) { + llist_t *omits_p = omits; + while (omits_p) { + if (xatoul(omits_p->data) == (unsigned long)(*pl)) { + goto omitting; + } + omits_p = omits_p->link; + } + } +#endif + printf(" %u" + first, (unsigned)*pl); + first = 0; + if (ENABLE_FEATURE_PIDOF_SINGLE && (opt & OPT_SINGLE)) + break; +#if ENABLE_FEATURE_PIDOF_OMIT + omitting: ; +#endif + } + free(pidList); + argv++; + } + if (!first) + bb_putchar('\n'); + +#if ENABLE_FEATURE_PIDOF_OMIT + if (ENABLE_FEATURE_CLEAN_UP) + llist_free(omits, NULL); +#endif + return first; /* 1 (failure) - no processes found */ +} diff --git a/procps/ps.c b/procps/ps.c new file mode 100644 index 0000000..395cfcf --- /dev/null +++ b/procps/ps.c @@ -0,0 +1,570 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ps implementation(s) for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * Fix for SELinux Support:(c)2007 Hiroshi Shinji + * (c)2007 Yuichi Nakamura + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +/* Absolute maximum on output line length */ +enum { MAX_WIDTH = 2*1024 }; + +#if ENABLE_DESKTOP + +#include /* for times() */ +//#include /* for sysinfo() */ +#ifndef AT_CLKTCK +#define AT_CLKTCK 17 +#endif + + +#if ENABLE_SELINUX +#define SELINUX_O_PREFIX "label," +#define DEFAULT_O_STR (SELINUX_O_PREFIX "pid,user" USE_FEATURE_PS_TIME(",time") ",args") +#else +#define DEFAULT_O_STR ("pid,user" USE_FEATURE_PS_TIME(",time") ",args") +#endif + +typedef struct { + uint16_t width; + char name[6]; + const char *header; + void (*f)(char *buf, int size, const procps_status_t *ps); + int ps_flags; +} ps_out_t; + +struct globals { + ps_out_t* out; + int out_cnt; + int print_header; + int need_flags; + char *buffer; + unsigned terminal_width; +#if ENABLE_FEATURE_PS_TIME + unsigned kernel_HZ; + unsigned long long seconds_since_boot; +#endif + char default_o[sizeof(DEFAULT_O_STR)]; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define out (G.out ) +#define out_cnt (G.out_cnt ) +#define print_header (G.print_header ) +#define need_flags (G.need_flags ) +#define buffer (G.buffer ) +#define terminal_width (G.terminal_width ) +#define kernel_HZ (G.kernel_HZ ) +#define seconds_since_boot (G.seconds_since_boot) +#define default_o (G.default_o ) + +#if ENABLE_FEATURE_PS_TIME +/* for ELF executables, notes are pushed before environment and args */ +static ptrdiff_t find_elf_note(ptrdiff_t findme) +{ + ptrdiff_t *ep = (ptrdiff_t *) environ; + + while (*ep++); + while (*ep) { + if (ep[0] == findme) { + return ep[1]; + } + ep += 2; + } + return -1; +} + +#if ENABLE_FEATURE_PS_UNUSUAL_SYSTEMS +static unsigned get_HZ_by_waiting(void) +{ + struct timeval tv1, tv2; + unsigned t1, t2, r, hz; + unsigned cnt = cnt; /* for compiler */ + int diff; + + r = 0; + + /* Wait for times() to reach new tick */ + t1 = times(NULL); + do { + t2 = times(NULL); + } while (t2 == t1); + gettimeofday(&tv2, NULL); + + do { + t1 = t2; + tv1.tv_usec = tv2.tv_usec; + + /* Wait exactly one times() tick */ + do { + t2 = times(NULL); + } while (t2 == t1); + gettimeofday(&tv2, NULL); + + /* Calculate ticks per sec, rounding up to even */ + diff = tv2.tv_usec - tv1.tv_usec; + if (diff <= 0) diff += 1000000; + hz = 1000000u / (unsigned)diff; + hz = (hz+1) & ~1; + + /* Count how many same hz values we saw */ + if (r != hz) { + r = hz; + cnt = 0; + } + cnt++; + } while (cnt < 3); /* exit if saw 3 same values */ + + return r; +} +#else +static inline unsigned get_HZ_by_waiting(void) +{ + /* Better method? */ + return 100; +} +#endif + +static unsigned get_kernel_HZ(void) +{ + //char buf[64]; + struct sysinfo info; + + if (kernel_HZ) + return kernel_HZ; + + /* Works for ELF only, Linux 2.4.0+ */ + kernel_HZ = find_elf_note(AT_CLKTCK); + if (kernel_HZ == (unsigned)-1) + kernel_HZ = get_HZ_by_waiting(); + + //if (open_read_close("/proc/uptime", buf, sizeof(buf) <= 0) + // bb_perror_msg_and_die("cannot read %s", "/proc/uptime"); + //buf[sizeof(buf)-1] = '\0'; + ///sscanf(buf, "%llu", &seconds_since_boot); + sysinfo(&info); + seconds_since_boot = info.uptime; + + return kernel_HZ; +} +#endif + +/* Print value to buf, max size+1 chars (including trailing '\0') */ + +static void func_user(char *buf, int size, const procps_status_t *ps) +{ +#if 1 + safe_strncpy(buf, get_cached_username(ps->uid), size+1); +#else + /* "compatible" version, but it's larger */ + /* procps 2.18 shows numeric UID if name overflows the field */ + /* TODO: get_cached_username() returns numeric string if + * user has no passwd record, we will display it + * left-justified here; too long usernames are shown + * as _right-justified_ IDs. Is it worth fixing? */ + const char *user = get_cached_username(ps->uid); + if (strlen(user) <= size) + safe_strncpy(buf, user, size+1); + else + sprintf(buf, "%*u", size, (unsigned)ps->uid); +#endif +} + +static void func_comm(char *buf, int size, const procps_status_t *ps) +{ + safe_strncpy(buf, ps->comm, size+1); +} + +static void func_args(char *buf, int size, const procps_status_t *ps) +{ + read_cmdline(buf, size, ps->pid, ps->comm); +} + +static void func_pid(char *buf, int size, const procps_status_t *ps) +{ + sprintf(buf, "%*u", size, ps->pid); +} + +static void func_ppid(char *buf, int size, const procps_status_t *ps) +{ + sprintf(buf, "%*u", size, ps->ppid); +} + +static void func_pgid(char *buf, int size, const procps_status_t *ps) +{ + sprintf(buf, "%*u", size, ps->pgid); +} + +static void put_lu(char *buf, int size, unsigned long u) +{ + char buf4[5]; + + /* see http://en.wikipedia.org/wiki/Tera */ + smart_ulltoa4(u, buf4, " mgtpezy"); + buf4[4] = '\0'; + sprintf(buf, "%.*s", size, buf4); +} + +static void func_vsz(char *buf, int size, const procps_status_t *ps) +{ + put_lu(buf, size, ps->vsz); +} + +static void func_rss(char *buf, int size, const procps_status_t *ps) +{ + put_lu(buf, size, ps->rss); +} + +static void func_tty(char *buf, int size, const procps_status_t *ps) +{ + buf[0] = '?'; + buf[1] = '\0'; + if (ps->tty_major) /* tty field of "0" means "no tty" */ + snprintf(buf, size+1, "%u,%u", ps->tty_major, ps->tty_minor); +} + +#if ENABLE_FEATURE_PS_TIME +static void func_etime(char *buf, int size, const procps_status_t *ps) +{ + /* elapsed time [[dd-]hh:]mm:ss; here only mm:ss */ + unsigned long mm; + unsigned ss; + + mm = ps->start_time / get_kernel_HZ(); + /* must be after get_kernel_HZ()! */ + mm = seconds_since_boot - mm; + ss = mm % 60; + mm /= 60; + snprintf(buf, size+1, "%3lu:%02u", mm, ss); +} + +static void func_time(char *buf, int size, const procps_status_t *ps) +{ + /* cumulative time [[dd-]hh:]mm:ss; here only mm:ss */ + unsigned long mm; + unsigned ss; + + mm = (ps->utime + ps->stime) / get_kernel_HZ(); + ss = mm % 60; + mm /= 60; + snprintf(buf, size+1, "%3lu:%02u", mm, ss); +} +#endif + +#if ENABLE_SELINUX +static void func_label(char *buf, int size, const procps_status_t *ps) +{ + safe_strncpy(buf, ps->context ? ps->context : "unknown", size+1); +} +#endif + +/* +static void func_nice(char *buf, int size, const procps_status_t *ps) +{ + ps->??? +} + +static void func_pcpu(char *buf, int size, const procps_status_t *ps) +{ +} +*/ + +static const ps_out_t out_spec[] = { +// Mandated by POSIX: + { 8 , "user" ,"USER" ,func_user ,PSSCAN_UIDGID }, + { 16 , "comm" ,"COMMAND",func_comm ,PSSCAN_COMM }, + { 256 , "args" ,"COMMAND",func_args ,PSSCAN_COMM }, + { 5 , "pid" ,"PID" ,func_pid ,PSSCAN_PID }, + { 5 , "ppid" ,"PPID" ,func_ppid ,PSSCAN_PPID }, + { 5 , "pgid" ,"PGID" ,func_pgid ,PSSCAN_PGID }, +#if ENABLE_FEATURE_PS_TIME + { sizeof("ELAPSED")-1, "etime" ,"ELAPSED",func_etime ,PSSCAN_START_TIME }, +#endif +// { sizeof("GROUP" )-1, "group" ,"GROUP" ,func_group ,PSSCAN_UIDGID }, +// { sizeof("NI" )-1, "nice" ,"NI" ,func_nice ,PSSCAN_ }, +// { sizeof("%CPU" )-1, "pcpu" ,"%CPU" ,func_pcpu ,PSSCAN_ }, +// { sizeof("RGROUP" )-1, "rgroup","RGROUP" ,func_rgroup,PSSCAN_UIDGID }, +// { sizeof("RUSER" )-1, "ruser" ,"RUSER" ,func_ruser ,PSSCAN_UIDGID }, +#if ENABLE_FEATURE_PS_TIME + { 6 , "time" ,"TIME" ,func_time ,PSSCAN_STIME | PSSCAN_UTIME }, +#endif + { 6 , "tty" ,"TT" ,func_tty ,PSSCAN_TTY }, + { 4 , "vsz" ,"VSZ" ,func_vsz ,PSSCAN_VSZ }, +// Not mandated by POSIX, but useful: + { 4 , "rss" ,"RSS" ,func_rss ,PSSCAN_RSS }, +#if ENABLE_SELINUX + { 35 , "label" ,"LABEL" ,func_label ,PSSCAN_CONTEXT }, +#endif +}; + +static ps_out_t* new_out_t(void) +{ + out = xrealloc_vector(out, 2, out_cnt); + return &out[out_cnt++]; +} + +static const ps_out_t* find_out_spec(const char *name) +{ + unsigned i; + for (i = 0; i < ARRAY_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; + } + // opt points to last spec in comma separated list. + // This one can have =HEADER part. + 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++) { + need_flags |= out[i].ps_flags; + if (out[i].header[0]) { + print_header = 1; + } + width += out[i].width + 1; /* "FIELD " */ + } +#if ENABLE_SELINUX + if (!is_selinux_enabled()) + need_flags &= ~PSSCAN_CONTEXT; +#endif + buffer = xmalloc(width + 1); /* for trailing \0 */ +} + +static void format_header(void) +{ + int i; + ps_out_t* op; + char *p; + + if (!print_header) + return; + p = buffer; + 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[0]) { + p[0] = '-'; + p[1] = '\0'; + } + len = strlen(p); + p += len; + len = out[i].width - len + 1; + if (++i == out_cnt) /* do not pad last field */ + break; + p += sprintf(p, "%*s", len, ""); + } + printf("%.*s\n", terminal_width, buffer); +} + +int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ps_main(int argc UNUSED_PARAM, char **argv) +{ + procps_status_t *p; + llist_t* opt_o = NULL; + USE_SELINUX(int opt;) + + // 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 display + /* We allow (and ignore) most of the above. FIXME */ + opt_complementary = "o::"; + USE_SELINUX(opt =) getopt32(argv, "Zo:aAdefl", &opt_o); + if (opt_o) { + do { + parse_o(llist_pop(&opt_o)); + } while (opt_o); + } else { + /* Below: parse_o() needs char*, NOT const char*... */ +#if ENABLE_SELINUX + if (!(opt & 1) || !is_selinux_enabled()) { + /* no -Z or no SELinux: do not show LABEL */ + strcpy(default_o, DEFAULT_O_STR + sizeof(SELINUX_O_PREFIX)-1); + } else +#endif + { + strcpy(default_o, DEFAULT_O_STR); + } + parse_o(default_o); + } + post_process(); + + /* Was INT_MAX, but some libc's go belly up with printf("%.*s") + * and such large widths */ + terminal_width = MAX_WIDTH; + if (isatty(1)) { + get_terminal_width_height(0, &terminal_width, NULL); + if (--terminal_width > MAX_WIDTH) + terminal_width = MAX_WIDTH; + } + format_header(); + + p = NULL; + while ((p = procps_scan(p, need_flags))) { + format_process(p); + } + + return EXIT_SUCCESS; +} + + +#else /* !ENABLE_DESKTOP */ + + +int ps_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ps_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + procps_status_t *p = NULL; + int len; + SKIP_SELINUX(const) int use_selinux = 0; + USE_SELINUX(int i;) +#if !ENABLE_FEATURE_PS_WIDE + enum { terminal_width = 79 }; +#else + unsigned terminal_width; + int w_count = 0; +#endif + +#if ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX +#if ENABLE_FEATURE_PS_WIDE + opt_complementary = "-:ww"; + USE_SELINUX(i =) getopt32(argv, USE_SELINUX("Z") "w", &w_count); + /* if w is given once, GNU ps sets the width to 132, + * if w is given more than once, it is "unlimited" + */ + if (w_count) { + terminal_width = (w_count==1) ? 132 : MAX_WIDTH; + } else { + get_terminal_width_height(0, &terminal_width, NULL); + /* Go one less... */ + if (--terminal_width > MAX_WIDTH) + terminal_width = MAX_WIDTH; + } +#else /* only ENABLE_SELINUX */ + i = getopt32(argv, "Z"); +#endif +#if ENABLE_SELINUX + if ((i & 1) && is_selinux_enabled()) + use_selinux = PSSCAN_CONTEXT; +#endif +#endif /* ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX */ + + if (use_selinux) + puts(" PID CONTEXT STAT COMMAND"); + else + puts(" PID USER VSZ STAT COMMAND"); + + while ((p = procps_scan(p, 0 + | PSSCAN_PID + | PSSCAN_UIDGID + | PSSCAN_STATE + | PSSCAN_VSZ + | PSSCAN_COMM + | use_selinux + ))) { +#if ENABLE_SELINUX + if (use_selinux) { + len = printf("%5u %-32.32s %s ", + p->pid, + p->context ? p->context : "unknown", + p->state); + } else +#endif + { + const char *user = get_cached_username(p->uid); + //if (p->vsz == 0) + // len = printf("%5u %-8.8s %s ", + // p->pid, user, p->state); + //else + { + char buf6[6]; + smart_ulltoa5(p->vsz, buf6, " mgtpezy"); + buf6[5] = '\0'; + len = printf("%5u %-8.8s %s %s ", + p->pid, user, buf6, p->state); + } + } + + { + int sz = terminal_width - len; + char buf[sz + 1]; + read_cmdline(buf, sz, p->pid, p->comm); + puts(buf); + } + } + if (ENABLE_FEATURE_CLEAN_UP) + clear_username_cache(); + return EXIT_SUCCESS; +} + +#endif /* ENABLE_DESKTOP */ diff --git a/procps/ps.posix b/procps/ps.posix new file mode 100644 index 0000000..57f4fa8 --- /dev/null +++ b/procps/ps.posix @@ -0,0 +1,175 @@ +This is what POSIX 2003 says about ps: + +By default, ps shall select all processes with the same effective user +ID as the current user and the same controlling terminal as the invoker + +ps [-aA][-defl][-G grouplist][-o format]...[-p proclist][-t termlist] +[-U userlist][-g grouplist][-n namelist][-u userlist] + +-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. (See the STDOUT section for the con- + tents of a full listing.) + +-g grouplist + Write information for processes whose session leaders are given + in grouplist. The application shall ensure that the grouplist is + a single argument in the form of a or comma-separated + list. + +-G grouplist + Write information for processes whose real group ID numbers are + given in grouplist. The application shall ensure that the grou- + plist is a single argument in the form of a or comma- + separated list. + +-l Generate a long listing. (See STDOUT for the contents of a long + listing.) + +-n namelist + Specify the name of an alternative system namelist file in place + of the default. The name of the default file and the format of a + namelist file are unspecified. + +-o format + Write information according to the format specification given in + format. Multiple -o options can be specified; the format speci- + fication shall be interpreted as the -separated concate- + nation of all the format option-arguments. + +-p proclist + Write information for processes whose process ID numbers are + given in proclist. The application shall ensure that the pro- + clist is a single argument in the form of a or comma- + separated list. + +-t termlist + Write information for processes associated with terminals given + in termlist. The application shall ensure that the termlist is a + single argument in the form of a or comma-separated + list. Terminal identifiers shall be given in an implementation- + defined format. On XSI-conformant systems, they shall be + given in one of two forms: the device's filename (for example, + tty04) or, if the device's filename starts with tty, just the + identifier following the characters tty (for example, "04" ). + +-u userlist + Write information for processes whose user ID numbers or login + names are given in userlist. The application shall ensure that + the userlist is a single argument in the form of a or + comma-separated list. In the listing, the numerical user ID + shall be written unless the -f option is used, in which case the + login name shall be written. + +-U userlist + Write information for processes whose real user ID numbers or + login names are given in userlist. The application shall ensure + that the userlist is a single argument in the form of a + or comma-separated list. + +With the exception of -o format, all of the options shown are used to +select processes. If any are specified, the default list shall be +ignored and ps shall select the processes represented by the inclusive +OR of all the selection-criteria options. + +The -o option allows the output format to be specified under user con- +trol. + +The application shall ensure that the format specification is a list of +names presented as a single argument, or comma-separated. Each +variable has a default header. The default header can be overridden by +appending an equals sign and the new text of the header. The rest of +the characters in the argument shall be used as the header text. The +fields specified shall be written in the order specified on the command +line, and should be arranged in columns in the output. The field widths +shall be selected by the system to 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 all header text fields are null, no header line shall +be written. + +ruser The real user ID of the process. This shall be the textual user + ID, if it can be obtained and the field width permits, or a dec- + imal representation otherwise. + +user The effective user ID of the process. This shall be the textual + user ID, if it can be obtained and the field width permits, or a + decimal representation otherwise. + +rgroup The real group ID of the process. This shall be the textual + group ID, if it can be obtained and the field width permits, or + a decimal representation otherwise. + +group The effective group ID of the process. This shall be the textual + group ID, if it can be obtained and the field width permits, or + a decimal representation otherwise. + +pid The decimal value of the process ID. + +ppid The decimal value of the parent process ID. + +pgid The decimal value of the process group ID. + +pcpu The ratio of CPU time used recently to CPU time available in the + same period, expressed as a percentage. The meaning of + "recently" in this context is unspecified. The CPU time avail- + able is determined in an unspecified manner. + +vsz The size of the process in (virtual) memory in 1024 byte units + as a decimal integer. + +nice The decimal value of the nice value of the process; see nice() . + +etime In the POSIX locale, the elapsed time since the process was + started, in the form: [[dd-]hh:]mm:ss + +time In the POSIX locale, the cumulative CPU time of the process in + the form: [dd-]hh:mm:ss + +tty The name of the controlling terminal of the process (if any) in + the same format used by the who utility. + +comm The name of the command being executed ( argv[0] value) as a + string. + +args The command with all its arguments as a string. The implementa- + tion may truncate this value to the field width; it is implemen- + tation-defined whether any further truncation occurs. It is + unspecified whether the string represented is a version of the + argument list as it was passed to the command when it started, + or is a version of the arguments as they may have been modified + by the application. Applications cannot depend on being able to + modify their argument list and having that modification be + reflected in the output of ps. + +Any field need not be meaningful in all implementations. In such a case +a hyphen ( '-' ) should be output in place of the field value. + +Only comm and args shall be allowed to contain s; all others +shall not. + +The following table specifies the default header to be used in the +POSIX locale corresponding to each format specifier. + + Format Specifier Default Header Format Specifier Default Header + args COMMAND ppid PPID + comm COMMAND rgroup RGROUP + etime ELAPSED ruser RUSER + group GROUP time TIME + nice NI tty TT + pcpu %CPU user USER + pgid PGID vsz VSZ + pid PID + +There is no special quoting mechanism for header text. The header text +is the rest of the argument. If multiple header changes are needed, +multiple -o options can be used, such as: + + ps -o "user=User Name" -o pid=Process\ ID diff --git a/procps/renice.c b/procps/renice.c new file mode 100644 index 0000000..ea5fc70 --- /dev/null +++ b/procps/renice.c @@ -0,0 +1,128 @@ +/* vi: set sw=4 ts=4: */ +/* + * renice implementation for busybox + * + * Copyright (C) 2005 Manuel Novoa III + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* Notes: + * Setting an absolute priority was obsoleted in SUSv2 and removed + * in SUSv3. However, the common linux version of renice does + * absolute and not relative. So we'll continue supporting absolute, + * although the stdout logging has been removed since both SUSv2 and + * SUSv3 specify that stdout isn't used. + * + * This version is lenient in that it doesn't require any IDs. The + * options -p, -g, and -u are treated as mode switches for the + * following IDs (if any). Multiple switches are allowed. + */ + +#include "libbb.h" +#include + +void BUG_bad_PRIO_PROCESS(void); +void BUG_bad_PRIO_PGRP(void); +void BUG_bad_PRIO_USER(void); + +int renice_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int renice_main(int argc UNUSED_PARAM, char **argv) +{ + static const char Xetpriority_msg[] ALIGN1 = "%cetpriority"; + + int retval = EXIT_SUCCESS; + int which = PRIO_PROCESS; /* Default 'which' value. */ + int use_relative = 0; + int adjustment, new_priority; + unsigned who; + char *arg; + + /* Yes, they are not #defines in glibc 2.4! #if won't work */ + if (PRIO_PROCESS < CHAR_MIN || PRIO_PROCESS > CHAR_MAX) + BUG_bad_PRIO_PROCESS(); + if (PRIO_PGRP < CHAR_MIN || PRIO_PGRP > CHAR_MAX) + BUG_bad_PRIO_PGRP(); + if (PRIO_USER < CHAR_MIN || PRIO_USER > CHAR_MAX) + BUG_bad_PRIO_USER(); + + arg = *++argv; + + /* Check if we are using a relative adjustment. */ + if (arg && arg[0] == '-' && arg[1] == 'n') { + use_relative = 1; + if (!arg[2]) + arg = *++argv; + else + arg += 2; + } + + if (!arg) { /* No args? Then show usage. */ + bb_show_usage(); + } + + /* Get the priority adjustment (absolute or relative). */ + adjustment = xatoi_range(arg, INT_MIN/2, INT_MAX/2); + + while ((arg = *++argv) != NULL) { + /* Check for a mode switch. */ + if (arg[0] == '-' && arg[1]) { + static const char opts[] ALIGN1 = { + 'p', 'g', 'u', 0, PRIO_PROCESS, PRIO_PGRP, PRIO_USER + }; + const char *p = strchr(opts, arg[1]); + if (p) { + which = p[4]; + if (!arg[2]) + continue; + arg += 2; + } + } + + /* Process an ID arg. */ + if (which == PRIO_USER) { + struct passwd *p; + p = getpwnam(arg); + if (!p) { + bb_error_msg("unknown user %s", arg); + goto HAD_ERROR; + } + who = p->pw_uid; + } else { + who = bb_strtou(arg, NULL, 10); + if (errno) { + bb_error_msg("bad value: %s", arg); + goto HAD_ERROR; + } + } + + /* Get priority to use, and set it. */ + if (use_relative) { + int old_priority; + + errno = 0; /* Needed for getpriority error detection. */ + old_priority = getpriority(which, who); + if (errno) { + bb_perror_msg(Xetpriority_msg, 'g'); + goto HAD_ERROR; + } + + new_priority = old_priority + adjustment; + } else { + new_priority = adjustment; + } + + if (setpriority(which, who, new_priority) == 0) { + continue; + } + + bb_perror_msg(Xetpriority_msg, 's'); + HAD_ERROR: + retval = EXIT_FAILURE; + } + + /* No need to check for errors outputing to stderr since, if it + * was used, the HAD_ERROR label was reached and retval was set. */ + + return retval; +} diff --git a/procps/sysctl.c b/procps/sysctl.c new file mode 100644 index 0000000..860c840 --- /dev/null +++ b/procps/sysctl.c @@ -0,0 +1,268 @@ +/* vi: set sw=4 ts=4: */ +/* + * Sysctl 1.01 - A utility to read and manipulate the sysctl parameters + * + * Copyright 1999 George Staikos + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Changelog: + * v1.01: + * - added -p to preload values from a file + * v1.01.1 + * - busybox applet aware by + * + */ + +#include "libbb.h" + +static int sysctl_read_setting(const char *setting); +static int sysctl_write_setting(const char *setting); +static int sysctl_display_all(const char *path); +static int sysctl_preload_file_and_exit(const char *filename); +static void sysctl_dots_to_slashes(char *name); + +static const char ETC_SYSCTL_CONF[] ALIGN1 = "/etc/sysctl.conf"; +static const char PROC_SYS[] ALIGN1 = "/proc/sys/"; +enum { strlen_PROC_SYS = sizeof(PROC_SYS) - 1 }; + +static const char msg_unknown_key[] ALIGN1 = + "error: '%s' is an unknown key"; + +static void dwrite_str(int fd, const char *buf) +{ + write(fd, buf, strlen(buf)); +} + +enum { + FLAG_SHOW_KEYS = 1 << 0, + FLAG_SHOW_KEY_ERRORS = 1 << 1, + FLAG_TABLE_FORMAT = 1 << 2, /* not implemented */ + FLAG_SHOW_ALL = 1 << 3, + FLAG_PRELOAD_FILE = 1 << 4, + FLAG_WRITE = 1 << 5, +}; + +int sysctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int sysctl_main(int argc UNUSED_PARAM, char **argv) +{ + int retval; + int opt; + + opt = getopt32(argv, "+neAapw"); /* '+' - stop on first non-option */ + argv += optind; + opt ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS); + option_mask32 ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS); + + if (opt & (FLAG_TABLE_FORMAT | FLAG_SHOW_ALL)) + return sysctl_display_all(PROC_SYS); + if (opt & FLAG_PRELOAD_FILE) + return sysctl_preload_file_and_exit(*argv ? *argv : ETC_SYSCTL_CONF); + + retval = 0; + while (*argv) { + if (opt & FLAG_WRITE) + retval |= sysctl_write_setting(*argv); + else + retval |= sysctl_read_setting(*argv); + argv++; + } + + return retval; +} /* end sysctl_main() */ + +/* Set sysctl's from a conf file. Format example: + * # Controls IP packet forwarding + * net.ipv4.ip_forward = 0 + */ +static int sysctl_preload_file_and_exit(const char *filename) +{ + char *token[2]; + parser_t *parser; + + parser = config_open(filename); +// TODO: ';' is comment char too + while (config_read(parser, token, 2, 2, "# \t=", PARSE_NORMAL)) { +#if 0 + char *s = xasprintf("%s=%s", token[0], token[1]); + sysctl_write_setting(s); + free(s); +#else /* Save ~4 bytes by using parser internals */ + sprintf(parser->line, "%s=%s", token[0], token[1]); // must have room by definition + sysctl_write_setting(parser->line); +#endif + } + if (ENABLE_FEATURE_CLEAN_UP) + config_close(parser); + return 0; +} /* end sysctl_preload_file_and_exit() */ + +static int sysctl_write_setting(const char *setting) +{ + int retval; + const char *name; + const char *value; + const char *equals; + char *tmpname, *outname, *cptr; + int fd; + + name = setting; + equals = strchr(setting, '='); + if (!equals) { + bb_error_msg("error: '%s' must be of the form name=value", setting); + return EXIT_FAILURE; + } + + value = equals + 1; /* point to the value in name=value */ + if (name == equals || !*value) { + bb_error_msg("error: malformed setting '%s'", setting); + return EXIT_FAILURE; + } + + tmpname = xasprintf("%s%.*s", PROC_SYS, (int)(equals - name), name); + outname = xstrdup(tmpname + strlen_PROC_SYS); + + sysctl_dots_to_slashes(tmpname); + + while ((cptr = strchr(outname, '/')) != NULL) + *cptr = '.'; + + fd = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + switch (errno) { + case ENOENT: + if (option_mask32 & FLAG_SHOW_KEY_ERRORS) + bb_error_msg(msg_unknown_key, outname); + break; + default: + bb_perror_msg("error setting key '%s'", outname); + break; + } + retval = EXIT_FAILURE; + } else { + dwrite_str(fd, value); + close(fd); + if (option_mask32 & FLAG_SHOW_KEYS) { + printf("%s = ", outname); + } + puts(value); + retval = EXIT_SUCCESS; + } + + free(tmpname); + free(outname); + return retval; +} /* end sysctl_write_setting() */ + +static int sysctl_read_setting(const char *name) +{ + int retval; + char *tmpname, *outname, *cptr; + char inbuf[1025]; + FILE *fp; + + if (!*name) { + if (option_mask32 & FLAG_SHOW_KEY_ERRORS) + bb_error_msg(msg_unknown_key, name); + return -1; + } + + tmpname = concat_path_file(PROC_SYS, name); + outname = xstrdup(tmpname + strlen_PROC_SYS); + + sysctl_dots_to_slashes(tmpname); + + while ((cptr = strchr(outname, '/')) != NULL) + *cptr = '.'; + + fp = fopen_for_read(tmpname); + if (fp == NULL) { + switch (errno) { + case ENOENT: + if (option_mask32 & FLAG_SHOW_KEY_ERRORS) + bb_error_msg(msg_unknown_key, outname); + break; + default: + bb_perror_msg("error reading key '%s'", outname); + break; + } + retval = EXIT_FAILURE; + } else { + while (fgets(inbuf, sizeof(inbuf) - 1, fp)) { + if (option_mask32 & FLAG_SHOW_KEYS) { + printf("%s = ", outname); + } + fputs(inbuf, stdout); + } + fclose(fp); + retval = EXIT_SUCCESS; + } + + free(tmpname); + free(outname); + return retval; +} /* end sysctl_read_setting() */ + +static int sysctl_display_all(const char *path) +{ + int retval = EXIT_SUCCESS; + DIR *dp; + struct dirent *de; + char *tmpdir; + struct stat ts; + + dp = opendir(path); + if (!dp) { + return EXIT_FAILURE; + } + while ((de = readdir(dp)) != NULL) { + tmpdir = concat_subpath_file(path, de->d_name); + if (tmpdir == NULL) + continue; /* . or .. */ + if (stat(tmpdir, &ts) != 0) { + bb_perror_msg(tmpdir); + } else if (S_ISDIR(ts.st_mode)) { + retval |= sysctl_display_all(tmpdir); + } else { + retval |= sysctl_read_setting(tmpdir + strlen_PROC_SYS); + } + free(tmpdir); + } /* end while */ + closedir(dp); + + return retval; +} /* end sysctl_display_all() */ + +static void sysctl_dots_to_slashes(char *name) +{ + char *cptr, *last_good, *end; + + /* It can be good as-is! */ + if (access(name, F_OK) == 0) + return; + + /* Example from bug 3894: + * net.ipv4.conf.eth0.100.mc_forwarding -> + * net/ipv4/conf/eth0.100/mc_forwarding. NB: + * net/ipv4/conf/eth0/mc_forwarding *also exists*, + * therefore we must start from the end, and if + * we replaced even one . -> /, start over again, + * but never replace dots before the position + * where replacement occurred. */ + end = name + strlen(name) - 1; + last_good = name - 1; + again: + cptr = end; + while (cptr > last_good) { + if (*cptr == '.') { + *cptr = '\0'; + if (access(name, F_OK) == 0) { + *cptr = '/'; + last_good = cptr; + goto again; + } + *cptr = '.'; + } + cptr--; + } +} /* end sysctl_dots_to_slashes() */ diff --git a/procps/top.c b/procps/top.c new file mode 100644 index 0000000..7342dac --- /dev/null +++ b/procps/top.c @@ -0,0 +1,1126 @@ +/* vi: set sw=4 ts=4: */ +/* + * A tiny 'top' utility. + * + * This is written specifically for the linux /proc//stat(m) + * files format. + + * This reads the PIDs of all processes and their status and shows + * the status of processes (first ones that fit to screen) at given + * intervals. + * + * NOTES: + * - At startup this changes to /proc, all the reads are then + * relative to that. + * + * (C) Eero Tamminen + * + * Rewritten by Vladimir Oleynik (C) 2002 + * + * Sept 2008: Vineet Gupta + * Added Support for reporting SMP Information + * - CPU where Process was last seen running + * (to see effect of sched_setaffinity() etc) + * - CPU Time Split (idle/IO/wait etc) PER CPU + */ + +/* Original code Copyrights */ +/* + * Copyright (c) 1992 Branko Lankester + * Copyright (c) 1992 Roger Binns + * Copyright (C) 1994-1996 Charles L. Blake. + * Copyright (C) 1992-1998 Michael K. Johnson + * May be distributed under the conditions of the + * GNU Library General Public License + */ + +#include "libbb.h" + + +typedef struct top_status_t { + unsigned long vsz; +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + unsigned long ticks; + unsigned pcpu; /* delta of ticks */ +#endif + unsigned pid, ppid; + unsigned uid; + char state[4]; + char comm[COMM_LEN]; +#if ENABLE_FEATURE_TOP_SMP_PROCESS + int last_seen_on_cpu; +#endif +} top_status_t; + +typedef struct jiffy_counts_t { + unsigned long long usr,nic,sys,idle,iowait,irq,softirq,steal; + unsigned long long total; + unsigned long long busy; +} jiffy_counts_t; + +/* This structure stores some critical information from one frame to + the next. Used for finding deltas. */ +typedef struct save_hist { + unsigned long ticks; + pid_t pid; +} save_hist; + +typedef int (*cmp_funcp)(top_status_t *P, top_status_t *Q); + + +enum { SORT_DEPTH = 3 }; + + +struct globals { + top_status_t *top; + int ntop; +#if ENABLE_FEATURE_TOPMEM + smallint sort_field; + smallint inverted; +#endif +#if ENABLE_FEATURE_TOP_SMP_CPU + smallint smp_cpu_info; /* one/many cpu info lines? */ +#endif +#if ENABLE_FEATURE_USE_TERMIOS + struct termios initial_settings; +#endif +#if !ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + cmp_funcp sort_function[1]; +#else + cmp_funcp sort_function[SORT_DEPTH]; + struct save_hist *prev_hist; + int prev_hist_count; + jiffy_counts_t cur_jif, prev_jif; + /* int hist_iterations; */ + unsigned total_pcpu; + /* unsigned long total_vsz; */ +#endif +#if ENABLE_FEATURE_TOP_SMP_CPU + /* Per CPU samples: current and last */ + jiffy_counts_t *cpu_jif, *cpu_prev_jif; + int num_cpus; +#endif + char line_buf[80]; +}; + +enum { LINE_BUF_SIZE = COMMON_BUFSIZE - offsetof(struct globals, line_buf) }; + +#define G (*(struct globals*)&bb_common_bufsiz1) +#define INIT_G() do { \ + struct G_sizecheck { \ + char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \ + }; \ +} while (0) +#define top (G.top ) +#define ntop (G.ntop ) +#define sort_field (G.sort_field ) +#define inverted (G.inverted ) +#define smp_cpu_info (G.smp_cpu_info ) +#define initial_settings (G.initial_settings ) +#define sort_function (G.sort_function ) +#define prev_hist (G.prev_hist ) +#define prev_hist_count (G.prev_hist_count ) +#define cur_jif (G.cur_jif ) +#define prev_jif (G.prev_jif ) +#define cpu_jif (G.cpu_jif ) +#define cpu_prev_jif (G.cpu_prev_jif ) +#define num_cpus (G.num_cpus ) +#define total_pcpu (G.total_pcpu ) +#define line_buf (G.line_buf ) + +enum { + OPT_d = (1 << 0), + OPT_n = (1 << 1), + OPT_b = (1 << 2), + OPT_EOF = (1 << 3), /* pseudo: "we saw EOF in stdin" */ +}; +#define OPT_BATCH_MODE (option_mask32 & OPT_b) + + +#if ENABLE_FEATURE_USE_TERMIOS +static int pid_sort(top_status_t *P, top_status_t *Q) +{ + /* Buggy wrt pids with high bit set */ + /* (linux pids are in [1..2^15-1]) */ + return (Q->pid - P->pid); +} +#endif + +static int mem_sort(top_status_t *P, top_status_t *Q) +{ + /* We want to avoid unsigned->signed and truncation errors */ + if (Q->vsz < P->vsz) return -1; + return Q->vsz != P->vsz; /* 0 if ==, 1 if > */ +} + + +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + +static int pcpu_sort(top_status_t *P, top_status_t *Q) +{ + /* Buggy wrt ticks with high bit set */ + /* Affects only processes for which ticks overflow */ + return (int)Q->pcpu - (int)P->pcpu; +} + +static int time_sort(top_status_t *P, top_status_t *Q) +{ + /* We want to avoid unsigned->signed and truncation errors */ + if (Q->ticks < P->ticks) return -1; + return Q->ticks != P->ticks; /* 0 if ==, 1 if > */ +} + +static int mult_lvl_cmp(void* a, void* b) +{ + int i, cmp_val; + + for (i = 0; i < SORT_DEPTH; i++) { + cmp_val = (*sort_function[i])(a, b); + if (cmp_val != 0) + return cmp_val; + } + return 0; +} + +/* NOINLINE so that complier doesn't unfold the call + * causing multiple copies of the arithmatic instrns + */ +static NOINLINE int read_cpu_jiffy(FILE *fp, jiffy_counts_t *p_jif) +{ +#if !ENABLE_FEATURE_TOP_SMP_CPU + static const char fmt[] = "cpu %lld %lld %lld %lld %lld %lld %lld %lld"; +#else + static const char fmt[] = "cp%*s %lld %lld %lld %lld %lld %lld %lld %lld"; +#endif + int ret; + + if (!fgets(line_buf, LINE_BUF_SIZE, fp) || line_buf[0] != 'c' /* not "cpu" */) + return 0; + ret = sscanf(line_buf, fmt, + &p_jif->usr, &p_jif->nic, &p_jif->sys, &p_jif->idle, + &p_jif->iowait, &p_jif->irq, &p_jif->softirq, + &p_jif->steal); + if (ret >= 4) { + p_jif->total = p_jif->usr + p_jif->nic + p_jif->sys + p_jif->idle + + p_jif->iowait + p_jif->irq + p_jif->softirq + p_jif->steal; + /* procps 2.x does not count iowait as busy time */ + p_jif->busy = p_jif->total - p_jif->idle - p_jif->iowait; + } + + return ret; +} + +static void get_jiffy_counts(void) +{ + FILE* fp = xfopen_for_read("stat"); + + /* We need to parse cumulative counts even if SMP CPU display is on, + * they are used to calculate per process CPU% */ + prev_jif = cur_jif; + if (read_cpu_jiffy(fp, &cur_jif) < 4) + bb_error_msg_and_die("can't read /proc/stat"); + +#if !ENABLE_FEATURE_TOP_SMP_CPU + fclose(fp); + return; +#else + if (!smp_cpu_info) { + fclose(fp); + return; + } + + if (!num_cpus) { + /* First time here. How many CPUs? + * There will be at least 1 /proc/stat line with cpu%d + */ + while (1) { + cpu_jif = xrealloc_vector(cpu_jif, 1, num_cpus); + if (read_cpu_jiffy(fp, &cpu_jif[num_cpus]) <= 4) + break; + num_cpus++; + } + if (num_cpus == 0) /* /proc/stat with only "cpu ..." line?! */ + smp_cpu_info = 0; + + cpu_prev_jif = xzalloc(sizeof(cpu_prev_jif[0]) * num_cpus); + + /* Otherwise the first per cpu display shows all 100% idles */ + usleep(50000); + } else { /* Non first time invocation */ + jiffy_counts_t *tmp; + int i; + + /* First switch the sample pointers: no need to copy */ + tmp = cpu_prev_jif; + cpu_prev_jif = cpu_jif; + cpu_jif = tmp; + + /* Get the new samples */ + for (i = 0; i < num_cpus; i++) + read_cpu_jiffy(fp, &cpu_jif[i]); + } +#endif + fclose(fp); +} + +static void do_stats(void) +{ + top_status_t *cur; + pid_t pid; + int i, last_i, n; + struct save_hist *new_hist; + + get_jiffy_counts(); + total_pcpu = 0; + /* total_vsz = 0; */ + new_hist = xmalloc(sizeof(new_hist[0]) * ntop); + /* + * Make a pass through the data to get stats. + */ + /* hist_iterations = 0; */ + i = 0; + for (n = 0; n < ntop; n++) { + cur = top + n; + + /* + * Calculate time in cur process. Time is sum of user time + * and system time + */ + pid = cur->pid; + new_hist[n].ticks = cur->ticks; + new_hist[n].pid = pid; + + /* find matching entry from previous pass */ + cur->pcpu = 0; + /* do not start at index 0, continue at last used one + * (brought hist_iterations from ~14000 down to 172) */ + last_i = i; + if (prev_hist_count) do { + if (prev_hist[i].pid == pid) { + cur->pcpu = cur->ticks - prev_hist[i].ticks; + total_pcpu += cur->pcpu; + break; + } + i = (i+1) % prev_hist_count; + /* hist_iterations++; */ + } while (i != last_i); + /* total_vsz += cur->vsz; */ + } + + /* + * Save cur frame's information. + */ + free(prev_hist); + prev_hist = new_hist; + prev_hist_count = ntop; +} + +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + +#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS && ENABLE_FEATURE_TOP_DECIMALS +/* formats 7 char string (8 with terminating NUL) */ +static char *fmt_100percent_8(char pbuf[8], unsigned value, unsigned total) +{ + unsigned t; + if (value >= total) { /* 100% ? */ + strcpy(pbuf, " 100% "); + return pbuf; + } + /* else generate " [N/space]N.N% " string */ + value = 1000 * value / total; + t = value / 100; + value = value % 100; + pbuf[0] = ' '; + pbuf[1] = t ? t + '0' : ' '; + pbuf[2] = '0' + (value / 10); + pbuf[3] = '.'; + pbuf[4] = '0' + (value % 10); + pbuf[5] = '%'; + pbuf[6] = ' '; + pbuf[7] = '\0'; + return pbuf; +} +#endif + +#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS +static void display_cpus(int scr_width, char *scrbuf, int *lines_rem_p) +{ + /* + * xxx% = (cur_jif.xxx - prev_jif.xxx) / (cur_jif.total - prev_jif.total) * 100% + */ + unsigned total_diff; + jiffy_counts_t *p_jif, *p_prev_jif; + int i; + +#if ENABLE_FEATURE_TOP_SMP_CPU + int n_cpu_lines; +#endif + + /* using (unsigned) casts to make operations cheaper */ +#define CALC_TOT_DIFF ((unsigned)(p_jif->total - p_prev_jif->total) ? : 1) + +#if ENABLE_FEATURE_TOP_DECIMALS +#define CALC_STAT(xxx) char xxx[8] +#define SHOW_STAT(xxx) fmt_100percent_8(xxx, (unsigned)(p_jif->xxx - p_prev_jif->xxx), total_diff) +#define FMT "%s" +#else +#define CALC_STAT(xxx) unsigned xxx = 100 * (unsigned)(p_jif->xxx - p_prev_jif->xxx) / total_diff +#define SHOW_STAT(xxx) xxx +#define FMT "%4u%% " +#endif + +#if !ENABLE_FEATURE_TOP_SMP_CPU + { + i = 1; + p_jif = &cur_jif; + p_prev_jif = &prev_jif; +#else + /* Loop thru CPU(s) */ + n_cpu_lines = smp_cpu_info ? num_cpus : 1; + if (n_cpu_lines > *lines_rem_p) + n_cpu_lines = *lines_rem_p; + + for (i = 0; i < n_cpu_lines; i++) { + p_jif = &cpu_jif[i]; + p_prev_jif = &cpu_prev_jif[i]; +#endif + total_diff = CALC_TOT_DIFF; + + { /* Need a block: CALC_STAT are declarations */ + CALC_STAT(usr); + CALC_STAT(sys); + CALC_STAT(nic); + CALC_STAT(idle); + CALC_STAT(iowait); + CALC_STAT(irq); + CALC_STAT(softirq); + /*CALC_STAT(steal);*/ + + snprintf(scrbuf, scr_width, + /* Barely fits in 79 chars when in "decimals" mode. */ +#if ENABLE_FEATURE_TOP_SMP_CPU + "CPU%s:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq", + (smp_cpu_info ? utoa(i) : ""), +#else + "CPU:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq", +#endif + SHOW_STAT(usr), SHOW_STAT(sys), SHOW_STAT(nic), SHOW_STAT(idle), + SHOW_STAT(iowait), SHOW_STAT(irq), SHOW_STAT(softirq) + /*, SHOW_STAT(steal) - what is this 'steal' thing? */ + /* I doubt anyone wants to know it */ + ); + puts(scrbuf); + } + } +#undef SHOW_STAT +#undef CALC_STAT +#undef FMT + *lines_rem_p -= i; +} +#else /* !ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS */ +#define display_cpus(scr_width, scrbuf, lines_rem) ((void)0) +#endif + +static unsigned long display_header(int scr_width, int *lines_rem_p) +{ + FILE *fp; + char buf[80]; + char scrbuf[80]; + unsigned long total, used, mfree, shared, buffers, cached; + + /* read memory info */ + fp = xfopen_for_read("meminfo"); + + /* + * Old kernels (such as 2.4.x) had a nice summary of memory info that + * we could parse, however this is gone entirely in 2.6. Try parsing + * the old way first, and if that fails, parse each field manually. + * + * First, we read in the first line. Old kernels will have bogus + * strings we don't care about, whereas new kernels will start right + * out with MemTotal: + * -- PFM. + */ + if (fscanf(fp, "MemTotal: %lu %s\n", &total, buf) != 2) { + fgets(buf, sizeof(buf), fp); /* skip first line */ + + fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu", + &total, &used, &mfree, &shared, &buffers, &cached); + /* convert to kilobytes */ + used /= 1024; + mfree /= 1024; + shared /= 1024; + buffers /= 1024; + cached /= 1024; + total /= 1024; + } else { + /* + * Revert to manual parsing, which incidentally already has the + * sizes in kilobytes. This should be safe for both 2.4 and + * 2.6. + */ + fscanf(fp, "MemFree: %lu %s\n", &mfree, buf); + + /* + * MemShared: is no longer present in 2.6. Report this as 0, + * to maintain consistent behavior with normal procps. + */ + if (fscanf(fp, "MemShared: %lu %s\n", &shared, buf) != 2) + shared = 0; + + fscanf(fp, "Buffers: %lu %s\n", &buffers, buf); + fscanf(fp, "Cached: %lu %s\n", &cached, buf); + + used = total - mfree; + } + fclose(fp); + + /* output memory info */ + if (scr_width > (int)sizeof(scrbuf)) + scr_width = sizeof(scrbuf); + snprintf(scrbuf, scr_width, + "Mem: %luK used, %luK free, %luK shrd, %luK buff, %luK cached", + used, mfree, shared, buffers, cached); + /* clear screen & go to top */ + printf(OPT_BATCH_MODE ? "%s\n" : "\e[H\e[J%s\n", scrbuf); + (*lines_rem_p)--; + + /* Display CPU time split as percentage of total time + * This displays either a cumulative line or one line per CPU + */ + display_cpus(scr_width, scrbuf, lines_rem_p); + + /* read load average as a string */ + buf[0] = '\0'; + open_read_close("loadavg", buf, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\n'; + *strchr(buf, '\n') = '\0'; + snprintf(scrbuf, scr_width, "Load average: %s", buf); + puts(scrbuf); + (*lines_rem_p)--; + + return total; +} + +static NOINLINE void display_process_list(int lines_rem, int scr_width) +{ + enum { + BITS_PER_INT = sizeof(int) * 8 + }; + + top_status_t *s; + char vsz_str_buf[8]; + unsigned long total_memory = display_header(scr_width, &lines_rem); /* or use total_vsz? */ + /* xxx_shift and xxx_scale variables allow us to replace + * expensive divides with multiply and shift */ + unsigned pmem_shift, pmem_scale, pmem_half; +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + unsigned pcpu_shift, pcpu_scale, pcpu_half; + unsigned busy_jifs; +#endif + + /* what info of the processes is shown */ + printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, + " PID PPID USER STAT VSZ %MEM" +#if ENABLE_FEATURE_TOP_SMP_PROCESS + " CPU" +#endif +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + " %CPU" +#endif + " COMMAND"); + lines_rem--; + +#if ENABLE_FEATURE_TOP_DECIMALS +#define UPSCALE 1000 +#define CALC_STAT(name, val) div_t name = div((val), 10) +#define SHOW_STAT(name) name.quot, '0'+name.rem +#define FMT "%3u.%c" +#else +#define UPSCALE 100 +#define CALC_STAT(name, val) unsigned name = (val) +#define SHOW_STAT(name) name +#define FMT "%4u%%" +#endif + /* + * MEM% = s->vsz/MemTotal + */ + pmem_shift = BITS_PER_INT-11; + pmem_scale = UPSCALE*(1U<<(BITS_PER_INT-11)) / total_memory; + /* s->vsz is in kb. we want (s->vsz * pmem_scale) to never overflow */ + while (pmem_scale >= 512) { + pmem_scale /= 4; + pmem_shift -= 2; + } + pmem_half = (1U << pmem_shift) / (ENABLE_FEATURE_TOP_DECIMALS? 20 : 2); +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + busy_jifs = cur_jif.busy - prev_jif.busy; + /* This happens if there were lots of short-lived processes + * between two top updates (e.g. compilation) */ + if (total_pcpu < busy_jifs) total_pcpu = busy_jifs; + + /* + * CPU% = s->pcpu/sum(s->pcpu) * busy_cpu_ticks/total_cpu_ticks + * (pcpu is delta of sys+user time between samples) + */ + /* (cur_jif.xxx - prev_jif.xxx) and s->pcpu are + * in 0..~64000 range (HZ*update_interval). + * we assume that unsigned is at least 32-bit. + */ + pcpu_shift = 6; + pcpu_scale = (UPSCALE*64 * (uint16_t)busy_jifs ? : 1); + while (pcpu_scale < (1U << (BITS_PER_INT-2))) { + pcpu_scale *= 4; + pcpu_shift += 2; + } + pcpu_scale /= ( (uint16_t)(cur_jif.total - prev_jif.total) * total_pcpu ? : 1); + /* we want (s->pcpu * pcpu_scale) to never overflow */ + while (pcpu_scale >= 1024) { + pcpu_scale /= 4; + pcpu_shift -= 2; + } + pcpu_half = (1U << pcpu_shift) / (ENABLE_FEATURE_TOP_DECIMALS? 20 : 2); + /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */ +#endif + + /* Ok, all preliminary data is ready, go through the list */ + scr_width += 2; /* account for leading '\n' and trailing NUL */ + if (lines_rem > ntop) + lines_rem = ntop; + s = top; + while (--lines_rem >= 0) { + unsigned col; + CALC_STAT(pmem, (s->vsz*pmem_scale + pmem_half) >> pmem_shift); +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + CALC_STAT(pcpu, (s->pcpu*pcpu_scale + pcpu_half) >> pcpu_shift); +#endif + + if (s->vsz >= 100000) + sprintf(vsz_str_buf, "%6ldm", s->vsz/1024); + else + sprintf(vsz_str_buf, "%7ld", s->vsz); + /* PID PPID USER STAT VSZ %MEM [%CPU] COMMAND */ + col = snprintf(line_buf, scr_width, + "\n" "%5u%6u %-8.8s %s%s" FMT +#if ENABLE_FEATURE_TOP_SMP_PROCESS + " %3d" +#endif +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + FMT +#endif + " ", + s->pid, s->ppid, get_cached_username(s->uid), + s->state, vsz_str_buf, + SHOW_STAT(pmem) +#if ENABLE_FEATURE_TOP_SMP_PROCESS + , s->last_seen_on_cpu +#endif +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + , SHOW_STAT(pcpu) +#endif + ); + if ((int)(col + 1) < scr_width) + read_cmdline(line_buf + col, scr_width - col - 1, s->pid, s->comm); + fputs(line_buf, stdout); + /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu, + cur_jif.busy - prev_jif.busy, cur_jif.total - prev_jif.total); */ + s++; + } + /* printf(" %d", hist_iterations); */ + bb_putchar(OPT_BATCH_MODE ? '\n' : '\r'); + fflush(stdout); +} +#undef UPSCALE +#undef SHOW_STAT +#undef CALC_STAT +#undef FMT + +static void clearmems(void) +{ + clear_username_cache(); + free(top); + top = NULL; + ntop = 0; +} + +#if ENABLE_FEATURE_USE_TERMIOS +#include +#include + +static void reset_term(void) +{ + tcsetattr_stdin_TCSANOW(&initial_settings); + if (ENABLE_FEATURE_CLEAN_UP) { + clearmems(); +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + free(prev_hist); +#endif + } +} + +static void sig_catcher(int sig UNUSED_PARAM) +{ + reset_term(); + exit(EXIT_FAILURE); +} +#endif /* FEATURE_USE_TERMIOS */ + +/* + * TOPMEM support + */ + +typedef unsigned long mem_t; + +typedef struct topmem_status_t { + unsigned pid; + char comm[COMM_LEN]; + /* vsz doesn't count /dev/xxx mappings except /dev/zero */ + mem_t vsz ; + mem_t vszrw ; + mem_t rss ; + mem_t rss_sh ; + mem_t dirty ; + mem_t dirty_sh; + mem_t stack ; +} topmem_status_t; + +enum { NUM_SORT_FIELD = 7 }; + +#define topmem ((topmem_status_t*)top) + +#if ENABLE_FEATURE_TOPMEM + +static int topmem_sort(char *a, char *b) +{ + int n; + mem_t l, r; + + n = offsetof(topmem_status_t, vsz) + (sort_field * sizeof(mem_t)); + l = *(mem_t*)(a + n); + r = *(mem_t*)(b + n); +// if (l == r) { +// l = a->mapped_rw; +// r = b->mapped_rw; +// } + /* We want to avoid unsigned->signed and truncation errors */ + /* l>r: -1, l=r: 0, l r) ? -1 : (l != r); + return inverted ? -n : n; +} + +/* Cut "NNNN " out of " NNNN kb" */ +static char *grab_number(char *str, const char *match, unsigned sz) +{ + if (strncmp(str, match, sz) == 0) { + str = skip_whitespace(str + sz); + (skip_non_whitespace(str))[1] = '\0'; + return xstrdup(str); + } + return NULL; +} + +/* display header info (meminfo / loadavg) */ +static void display_topmem_header(int scr_width, int *lines_rem_p) +{ + char linebuf[128]; + unsigned i; + FILE *fp; + union { + struct { + /* 1 */ char *total; + /* 2 */ char *mfree; + /* 3 */ char *buf; + /* 4 */ char *cache; + /* 5 */ char *swaptotal; + /* 6 */ char *swapfree; + /* 7 */ char *dirty; + /* 8 */ char *mwrite; + /* 9 */ char *anon; + /* 10 */ char *map; + /* 11 */ char *slab; + } u; + char *str[11]; + } Z; +#define total Z.u.total +#define mfree Z.u.mfree +#define buf Z.u.buf +#define cache Z.u.cache +#define swaptotal Z.u.swaptotal +#define swapfree Z.u.swapfree +#define dirty Z.u.dirty +#define mwrite Z.u.mwrite +#define anon Z.u.anon +#define map Z.u.map +#define slab Z.u.slab +#define str Z.str + + memset(&Z, 0, sizeof(Z)); + + /* read memory info */ + fp = xfopen_for_read("meminfo"); + while (fgets(linebuf, sizeof(linebuf), fp)) { + char *p; + +#define SCAN(match, name) \ + p = grab_number(linebuf, match, sizeof(match)-1); \ + if (p) { name = p; continue; } + + SCAN("MemTotal:", total); + SCAN("MemFree:", mfree); + SCAN("Buffers:", buf); + SCAN("Cached:", cache); + SCAN("SwapTotal:", swaptotal); + SCAN("SwapFree:", swapfree); + SCAN("Dirty:", dirty); + SCAN("Writeback:", mwrite); + SCAN("AnonPages:", anon); + SCAN("Mapped:", map); + SCAN("Slab:", slab); +#undef SCAN + } + fclose(fp); + +#define S(s) (s ? s : "0 ") + snprintf(linebuf, sizeof(linebuf), + "Mem %stotal %sanon %smap %sfree", + S(total), S(anon), S(map), S(mfree)); + printf(OPT_BATCH_MODE ? "%.*s\n" : "\e[H\e[J%.*s\n", scr_width, linebuf); + + snprintf(linebuf, sizeof(linebuf), + " %sslab %sbuf %scache %sdirty %swrite", + S(slab), S(buf), S(cache), S(dirty), S(mwrite)); + printf("%.*s\n", scr_width, linebuf); + + snprintf(linebuf, sizeof(linebuf), + "Swap %stotal %sfree", // TODO: % used? + S(swaptotal), S(swapfree)); + printf("%.*s\n", scr_width, linebuf); + + (*lines_rem_p) -= 3; +#undef S + + for (i = 0; i < ARRAY_SIZE(str); i++) + free(str[i]); +#undef total +#undef free +#undef buf +#undef cache +#undef swaptotal +#undef swapfree +#undef dirty +#undef write +#undef anon +#undef map +#undef slab +#undef str +} + +static void ulltoa6_and_space(unsigned long long ul, char buf[6]) +{ + /* see http://en.wikipedia.org/wiki/Tera */ + smart_ulltoa5(ul, buf, " mgtpezy"); + buf[5] = ' '; +} + +static NOINLINE void display_topmem_process_list(int lines_rem, int scr_width) +{ +#define HDR_STR " PID VSZ VSZRW RSS (SHR) DIRTY (SHR) STACK" +#define MIN_WIDTH sizeof(HDR_STR) + const topmem_status_t *s = topmem; + + display_topmem_header(scr_width, &lines_rem); + strcpy(line_buf, HDR_STR " COMMAND"); + line_buf[5 + sort_field * 6] = '*'; + printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, line_buf); + lines_rem--; + + if (lines_rem > ntop) + lines_rem = ntop; + while (--lines_rem >= 0) { + /* PID VSZ VSZRW RSS (SHR) DIRTY (SHR) COMMAND */ + ulltoa6_and_space(s->pid , &line_buf[0*6]); + ulltoa6_and_space(s->vsz , &line_buf[1*6]); + ulltoa6_and_space(s->vszrw , &line_buf[2*6]); + ulltoa6_and_space(s->rss , &line_buf[3*6]); + ulltoa6_and_space(s->rss_sh , &line_buf[4*6]); + ulltoa6_and_space(s->dirty , &line_buf[5*6]); + ulltoa6_and_space(s->dirty_sh, &line_buf[6*6]); + ulltoa6_and_space(s->stack , &line_buf[7*6]); + line_buf[8*6] = '\0'; + if (scr_width > (int)MIN_WIDTH) { + read_cmdline(&line_buf[8*6], scr_width - MIN_WIDTH, s->pid, s->comm); + } + printf("\n""%.*s", scr_width, line_buf); + s++; + } + bb_putchar(OPT_BATCH_MODE ? '\n' : '\r'); + fflush(stdout); +#undef HDR_STR +#undef MIN_WIDTH +} + +#else +void display_topmem_process_list(int lines_rem, int scr_width); +int topmem_sort(char *a, char *b); +#endif /* TOPMEM */ + +/* + * end TOPMEM support + */ + +enum { + TOP_MASK = 0 + | PSSCAN_PID + | PSSCAN_PPID + | PSSCAN_VSZ + | PSSCAN_STIME + | PSSCAN_UTIME + | PSSCAN_STATE + | PSSCAN_COMM +#if ENABLE_FEATURE_TOP_SMP_PROCESS + | PSSCAN_CPU +#endif + | PSSCAN_UIDGID, + TOPMEM_MASK = 0 + | PSSCAN_PID + | PSSCAN_SMAPS + | PSSCAN_COMM, +}; + +int top_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int top_main(int argc UNUSED_PARAM, char **argv) +{ + int iterations; + unsigned lines, col; + int lines_rem; + unsigned interval; + char *str_interval, *str_iterations; + SKIP_FEATURE_TOPMEM(const) unsigned scan_mask = TOP_MASK; +#if ENABLE_FEATURE_USE_TERMIOS + struct termios new_settings; + struct pollfd pfd[1]; + unsigned char c; + + pfd[0].fd = 0; + pfd[0].events = POLLIN; +#endif /* FEATURE_USE_TERMIOS */ + + INIT_G(); + + interval = 5; /* default update interval is 5 seconds */ + iterations = 0; /* infinite */ +#if ENABLE_FEATURE_TOP_SMP_CPU + /*num_cpus = 0;*/ + /*smp_cpu_info = 0;*/ /* to start with show aggregate */ + cpu_jif = &cur_jif; + cpu_prev_jif = &prev_jif; +#endif + + /* all args are options; -n NUM */ + opt_complementary = "-"; + col = getopt32(argv, "d:n:b", &str_interval, &str_iterations); + if (col & OPT_d) { + /* work around for "-d 1" -> "-d -1" done by getopt32 */ + if (str_interval[0] == '-') + str_interval++; + /* Need to limit it to not overflow poll timeout */ + interval = xatou16(str_interval); + } + if (col & OPT_n) { + if (str_iterations[0] == '-') + str_iterations++; + iterations = xatou(str_iterations); + } + + /* change to /proc */ + xchdir("/proc"); +#if ENABLE_FEATURE_USE_TERMIOS + tcgetattr(0, (void *) &initial_settings); + memcpy(&new_settings, &initial_settings, sizeof(new_settings)); + /* unbuffered input, turn off echo */ + new_settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL); + + bb_signals(BB_FATAL_SIGS, sig_catcher); + tcsetattr_stdin_TCSANOW(&new_settings); +#endif /* FEATURE_USE_TERMIOS */ + +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + sort_function[0] = pcpu_sort; + sort_function[1] = mem_sort; + sort_function[2] = time_sort; +#else + sort_function[0] = mem_sort; +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + + while (1) { + procps_status_t *p = NULL; + + lines = 24; /* default */ + col = 79; +#if ENABLE_FEATURE_USE_TERMIOS + /* We output to stdout, we need size of stdout (not stdin)! */ + get_terminal_width_height(STDOUT_FILENO, &col, &lines); + if (lines < 5 || col < 10) { + sleep(interval); + continue; + } +#endif /* FEATURE_USE_TERMIOS */ + if (col > LINE_BUF_SIZE-2) /* +2 bytes for '\n', NUL, */ + col = LINE_BUF_SIZE-2; + + /* read process IDs & status for all the processes */ + while ((p = procps_scan(p, scan_mask)) != NULL) { + int n; + if (scan_mask == TOP_MASK) { + n = ntop; + top = xrealloc_vector(top, 6, ntop++); + top[n].pid = p->pid; + top[n].ppid = p->ppid; + top[n].vsz = p->vsz; +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + top[n].ticks = p->stime + p->utime; +#endif + top[n].uid = p->uid; + strcpy(top[n].state, p->state); + strcpy(top[n].comm, p->comm); +#if ENABLE_FEATURE_TOP_SMP_PROCESS + top[n].last_seen_on_cpu = p->last_seen_on_cpu; +#endif + } else { /* TOPMEM */ +#if ENABLE_FEATURE_TOPMEM + if (!(p->mapped_ro | p->mapped_rw)) + continue; /* kernel threads are ignored */ + n = ntop; + /* No bug here - top and topmem are the same */ + top = xrealloc_vector(topmem, 6, ntop++); + strcpy(topmem[n].comm, p->comm); + topmem[n].pid = p->pid; + topmem[n].vsz = p->mapped_rw + p->mapped_ro; + topmem[n].vszrw = p->mapped_rw; + topmem[n].rss_sh = p->shared_clean + p->shared_dirty; + topmem[n].rss = p->private_clean + p->private_dirty + topmem[n].rss_sh; + topmem[n].dirty = p->private_dirty + p->shared_dirty; + topmem[n].dirty_sh = p->shared_dirty; + topmem[n].stack = p->stack; +#endif + } + } /* end of "while we read /proc" */ + if (ntop == 0) { + bb_error_msg("no process info in /proc"); + break; + } + + if (scan_mask == TOP_MASK) { +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + if (!prev_hist_count) { + do_stats(); + usleep(100000); + clearmems(); + continue; + } + do_stats(); + /* TODO: we don't need to sort all 10000 processes, we need to find top 24! */ + qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp); +#else + qsort(top, ntop, sizeof(top_status_t), (void*)(sort_function[0])); +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + } +#if ENABLE_FEATURE_TOPMEM + else { /* TOPMEM */ + qsort(topmem, ntop, sizeof(topmem_status_t), (void*)topmem_sort); + } +#endif + lines_rem = lines; + if (OPT_BATCH_MODE) { + lines_rem = INT_MAX; + } + if (scan_mask == TOP_MASK) + display_process_list(lines_rem, col); +#if ENABLE_FEATURE_TOPMEM + else + display_topmem_process_list(lines_rem, col); +#endif + clearmems(); + if (iterations >= 0 && !--iterations) + break; +#if !ENABLE_FEATURE_USE_TERMIOS + sleep(interval); +#else + if (option_mask32 & (OPT_b|OPT_EOF)) + /* batch mode, or EOF on stdin ("top 0) { + if (safe_read(STDIN_FILENO, &c, 1) != 1) { /* error/EOF? */ + option_mask32 |= OPT_EOF; + continue; + } + if (c == initial_settings.c_cc[VINTR]) + break; + c |= 0x20; /* lowercase */ + if (c == 'q') + break; + if (c == 'n') { + USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;) + sort_function[0] = pid_sort; + } + if (c == 'm') { + USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;) + sort_function[0] = mem_sort; +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + sort_function[1] = pcpu_sort; + sort_function[2] = time_sort; +#endif + } +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + if (c == 'p') { + USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;) + sort_function[0] = pcpu_sort; + sort_function[1] = mem_sort; + sort_function[2] = time_sort; + } + if (c == 't') { + USE_FEATURE_TOPMEM(scan_mask = TOP_MASK;) + sort_function[0] = time_sort; + sort_function[1] = mem_sort; + sort_function[2] = pcpu_sort; + } +#if ENABLE_FEATURE_TOPMEM + if (c == 's') { + scan_mask = TOPMEM_MASK; + free(prev_hist); + prev_hist = NULL; + prev_hist_count = 0; + sort_field = (sort_field + 1) % NUM_SORT_FIELD; + } + if (c == 'r') + inverted ^= 1; +#endif +#if ENABLE_FEATURE_TOP_SMP_CPU + /* procps-2.0.18 uses 'C', 3.2.7 uses '1' */ + if (c == 'c' || c == '1') { + /* User wants to toggle per cpu <> aggregate */ + if (smp_cpu_info) { + free(cpu_prev_jif); + free(cpu_jif); + cpu_jif = &cur_jif; + cpu_prev_jif = &prev_jif; + } else { + /* Prepare for xrealloc() */ + cpu_jif = cpu_prev_jif = NULL; + } + num_cpus = 0; + smp_cpu_info = !smp_cpu_info; + get_jiffy_counts(); + } +#endif +#endif + } +#endif /* FEATURE_USE_TERMIOS */ + } /* end of "while (1)" */ + + bb_putchar('\n'); +#if ENABLE_FEATURE_USE_TERMIOS + reset_term(); +#endif + return EXIT_SUCCESS; +} diff --git a/procps/uptime.c b/procps/uptime.c new file mode 100644 index 0000000..d9aa1d0 --- /dev/null +++ b/procps/uptime.c @@ -0,0 +1,60 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini uptime implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +/* This version of uptime doesn't display the number of users on the system, + * since busybox init doesn't mess with utmp. For folks using utmp that are + * just dying to have # of users reported, feel free to write it as some type + * of CONFIG_FEATURE_UTMP_SUPPORT #define + */ + +/* getopt not needed */ + +#include "libbb.h" + +#ifndef FSHIFT +# define FSHIFT 16 /* nr of bits of precision */ +#endif +#define FIXED_1 (1<> FSHIFT) +#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100) + + +int uptime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int uptime_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + int updays, uphours, upminutes; + struct sysinfo info; + struct tm *current_time; + time_t current_secs; + + time(¤t_secs); + current_time = localtime(¤t_secs); + + sysinfo(&info); + + printf(" %02d:%02d:%02d up ", + current_time->tm_hour, current_time->tm_min, current_time->tm_sec); + updays = (int) info.uptime / (60*60*24); + if (updays) + printf("%d day%s, ", updays, (updays != 1) ? "s" : ""); + upminutes = (int) info.uptime / 60; + uphours = (upminutes / 60) % 24; + upminutes %= 60; + if (uphours) + printf("%2d:%02d, ", uphours, upminutes); + else + printf("%d min, ", upminutes); + + printf("load average: %ld.%02ld, %ld.%02ld, %ld.%02ld\n", + LOAD_INT(info.loads[0]), LOAD_FRAC(info.loads[0]), + LOAD_INT(info.loads[1]), LOAD_FRAC(info.loads[1]), + LOAD_INT(info.loads[2]), LOAD_FRAC(info.loads[2])); + + return EXIT_SUCCESS; +} diff --git a/procps/watch.c b/procps/watch.c new file mode 100644 index 0000000..5fd0510 --- /dev/null +++ b/procps/watch.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini watch implementation for busybox + * + * Copyright (C) 2001 by Michael Habermann + * Copyrigjt (C) Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A */ +/* BB_AUDIT GNU defects -- only option -n is supported. */ + +#include "libbb.h" + +// procps 2.0.18: +// watch [-d] [-n seconds] +// [--differences[=cumulative]] [--interval=seconds] command +// +// procps-3.2.3: +// watch [-dt] [-n seconds] +// [--differences[=cumulative]] [--interval=seconds] [--no-title] command +// +// (procps 3.x and procps 2.x are forks, not newer/older versions of the same) + +int watch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int watch_main(int argc UNUSED_PARAM, char **argv) +{ + unsigned opt; + unsigned period = 2; + unsigned width, new_width; + char *header; + char *cmd; + + opt_complementary = "-1:n+"; // at least one param; -n NUM + // "+": stop at first non-option (procps 3.x only) + opt = getopt32(argv, "+dtn:", &period); + argv += optind; + + // watch from both procps 2.x and 3.x does concatenation. Example: + // watch ls -l "a /tmp" "2>&1" -- ls won't see "a /tmp" as one param + cmd = *argv; + while (*++argv) + cmd = xasprintf("%s %s", cmd, *argv); // leaks cmd + + width = (unsigned)-1; // make sure first time new_width != width + header = NULL; + while (1) { + printf("\033[H\033[J"); + if (!(opt & 0x2)) { // no -t + const unsigned time_len = sizeof("1234-67-90 23:56:89"); + time_t t; + + get_terminal_width_height(STDIN_FILENO, &new_width, NULL); + if (new_width != width) { + width = new_width; + free(header); + header = xasprintf("Every %us: %-*s", period, (int)width, cmd); + } + time(&t); + if (time_len < width) + strftime(header + width - time_len, time_len, + "%Y-%m-%d %H:%M:%S", localtime(&t)); + + puts(header); + } + fflush(stdout); + // TODO: 'real' watch pipes cmd's output to itself + // and does not allow it to overflow the screen + // (taking into account linewrap!) + system(cmd); + sleep(period); + } + return 0; // gcc thinks we can reach this :) +} -- cgit v1.2.3