diff options
Diffstat (limited to 'src')
35 files changed, 25801 insertions, 0 deletions
diff --git a/src/free.c b/src/free.c new file mode 100644 index 0000000..579591d --- /dev/null +++ b/src/free.c @@ -0,0 +1,479 @@ +/* + * free.c - display free memory information + * + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2012-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2004 Albert Cahalan + * Copyright © 2002-2003 Robert Love <rml@tech9.net> + * Copyright © 1992 Brian Edmonds and Rafal Maszkowski + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <locale.h> +#include <errno.h> +#include <limits.h> +#include <ctype.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <wchar.h> + +#include "config.h" +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "fileutils.h" + +#include "meminfo.h" + +#ifndef SIZE_MAX +#define SIZE_MAX 32 +#endif + +#define FREE_HUMANREADABLE (1 << 1) +#define FREE_LOHI (1 << 2) +#define FREE_WIDE (1 << 3) +#define FREE_TOTAL (1 << 4) +#define FREE_SI (1 << 5) +#define FREE_REPEAT (1 << 6) +#define FREE_REPEATCOUNT (1 << 7) +#define FREE_COMMITTED (1 << 8) +#define FREE_LINE (1 << 9) + +struct commandline_arguments { + int exponent; /* demanded in kilos, magas... */ + float repeat_interval; /* delay in seconds */ + int repeat_counter; /* number of repeats */ +}; + +/* function prototypes */ +static void usage(FILE * out); +double power(unsigned int base, unsigned int expo); +static const char *scale_size(unsigned long size, int flags, struct commandline_arguments args); + +static void __attribute__ ((__noreturn__)) + usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -b, --bytes show output in bytes\n"), out); + fputs(_(" --kilo show output in kilobytes\n"), out); + fputs(_(" --mega show output in megabytes\n"), out); + fputs(_(" --giga show output in gigabytes\n"), out); + fputs(_(" --tera show output in terabytes\n"), out); + fputs(_(" --peta show output in petabytes\n"), out); + fputs(_(" -k, --kibi show output in kibibytes\n"), out); + fputs(_(" -m, --mebi show output in mebibytes\n"), out); + fputs(_(" -g, --gibi show output in gibibytes\n"), out); + fputs(_(" --tebi show output in tebibytes\n"), out); + fputs(_(" --pebi show output in pebibytes\n"), out); + fputs(_(" -h, --human show human-readable output\n"), out); + fputs(_(" --si use powers of 1000 not 1024\n"), out); + fputs(_(" -l, --lohi show detailed low and high memory statistics\n"), out); + fputs(_(" -L, --line show output on a single line\n"), out); + fputs(_(" -t, --total show total for RAM + swap\n"), out); + fputs(_(" -v, --committed show committed memory and commit limit\n"), out); + fputs(_(" -s N, --seconds N repeat printing every N seconds\n"), out); + fputs(_(" -c N, --count N repeat printing N times, then exit\n"), out); + fputs(_(" -w, --wide wide output\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(_(" --help display this help and exit\n"), out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("free(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +double power(unsigned int base, unsigned int expo) +{ + return (expo == 0) ? 1 : base * power(base, expo - 1); +} + +/* idea of this function is copied from top size scaling */ +static const char *scale_size(unsigned long size, int flags, struct commandline_arguments args) +{ + static char up[] = { 'B', 'K', 'M', 'G', 'T', 'P', 0 }; + static char buf[BUFSIZ]; + int i; + float base; + long long bytes; + + base = (flags & FREE_SI) ? 1000.0 : 1024.0; + bytes = size * 1024LL; + + if (!(flags & FREE_HUMANREADABLE)) { + switch (args.exponent) { + case 0: + /* default output */ + snprintf(buf, sizeof(buf), "%ld", (long int)(bytes / (long long int)base)); + return buf; + case 1: + /* in bytes, which can not be in SI */ + snprintf(buf, sizeof(buf), "%lld", bytes); + return buf; + default: + /* In desired scale. */ + snprintf(buf, sizeof(buf), "%ld", + (long)(bytes / power(base, args.exponent-1))); + return buf; + } + } + + /* human readable output */ + if (4 >= snprintf(buf, sizeof(buf), "%lld%c", bytes, up[0])) + return buf; + + for (i = 1; up[i] != 0; i++) { + if (flags & FREE_SI) { + if (4 >= snprintf(buf, sizeof(buf), "%.1f%c", + (float)(bytes / power(base, i)), up[i])) + return buf; + if (4 >= snprintf(buf, sizeof(buf), "%ld%c", + (long)(bytes / power(base, i)), up[i])) + return buf; + } else { + if (5 >= snprintf(buf, sizeof(buf), "%.1f%ci", + (float)(bytes / power(base, i)), up[i])) + return buf; + if (5 >= snprintf(buf, sizeof(buf), "%ld%ci", + (long)(bytes / power(base, i)), up[i])) + return buf; + } + } + /* + * On system where there is more than exbibyte of memory or swap the + * output does not fit to column. For incoming few years this should + * not be a big problem (wrote at Apr, 2015). + */ + return buf; +} + +static void check_unit_set(int *unit_set) +{ + if (*unit_set) + xerrx(EXIT_FAILURE, + _("Multiple unit options don't make sense.")); + *unit_set = 1; +} + +/* + * Print the header columns. + * We cannot simply use the second printf because the length of the + * translated strings doesn't work with it. Instead we need to find + * the wide length of the string and use that. + * This method also removes the messy wprintf/printf buffering issues + */ +#define HC_WIDTH 9 +static void print_head_col(const char *str) +{ + int len; + int spaces = 9; + wchar_t wstr[BUFSIZ]; + + len = mbstowcs(wstr, str, BUFSIZ); + if (len < 0) + spaces = 9; + else if (len < HC_WIDTH) { + int width; + if ( (width = wcswidth(wstr, 99)) > 0) + spaces = HC_WIDTH - width; + else + spaces = HC_WIDTH - len; + } else + spaces = 0; + + printf("%s%.*s", str, spaces, " "); +} + +int main(int argc, char **argv) +{ + int c, flags = 0, unit_set = 0, rc = 0; + struct commandline_arguments args; + struct meminfo_info *mem_info = NULL; + + /* + * For long options that have no equivalent short option, use a + * non-character as a pseudo short option, starting with CHAR_MAX + 1. + */ + enum { + SI_OPTION = CHAR_MAX + 1, + KILO_OPTION, + MEGA_OPTION, + GIGA_OPTION, + TERA_OPTION, + PETA_OPTION, + TEBI_OPTION, + PEBI_OPTION, + HELP_OPTION + }; + + static const struct option longopts[] = { + { "bytes", no_argument, NULL, 'b' }, + { "kilo", no_argument, NULL, KILO_OPTION }, + { "mega", no_argument, NULL, MEGA_OPTION }, + { "giga", no_argument, NULL, GIGA_OPTION }, + { "tera", no_argument, NULL, TERA_OPTION }, + { "peta", no_argument, NULL, PETA_OPTION }, + { "kibi", no_argument, NULL, 'k' }, + { "mebi", no_argument, NULL, 'm' }, + { "gibi", no_argument, NULL, 'g' }, + { "tebi", no_argument, NULL, TEBI_OPTION }, + { "pebi", no_argument, NULL, PEBI_OPTION }, + { "human", no_argument, NULL, 'h' }, + { "si", no_argument, NULL, SI_OPTION }, + { "lohi", no_argument, NULL, 'l' }, + { "line", no_argument, NULL, 'L' }, + { "total", no_argument, NULL, 't' }, + { "committed", no_argument, NULL, 'v' }, + { "seconds", required_argument, NULL, 's' }, + { "count", required_argument, NULL, 'c' }, + { "wide", no_argument, NULL, 'w' }, + { "help", no_argument, NULL, HELP_OPTION }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + /* defaults */ + args.exponent = 0; + args.repeat_interval = 1000000; + args.repeat_counter = 0; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "bkmghlLtvc:ws:V", longopts, NULL)) != -1) + switch (c) { + case 'b': + check_unit_set(&unit_set); + args.exponent = 1; + break; + case 'k': + check_unit_set(&unit_set); + args.exponent = 2; + break; + case 'm': + check_unit_set(&unit_set); + args.exponent = 3; + break; + case 'g': + check_unit_set(&unit_set); + args.exponent = 4; + break; + case TEBI_OPTION: + check_unit_set(&unit_set); + args.exponent = 5; + break; + case PEBI_OPTION: + check_unit_set(&unit_set); + args.exponent = 6; + break; + case KILO_OPTION: + check_unit_set(&unit_set); + args.exponent = 2; + flags |= FREE_SI; + break; + case MEGA_OPTION: + check_unit_set(&unit_set); + args.exponent = 3; + flags |= FREE_SI; + break; + case GIGA_OPTION: + check_unit_set(&unit_set); + args.exponent = 4; + flags |= FREE_SI; + break; + case TERA_OPTION: + check_unit_set(&unit_set); + args.exponent = 5; + flags |= FREE_SI; + break; + case PETA_OPTION: + check_unit_set(&unit_set); + args.exponent = 6; + flags |= FREE_SI; + break; + case 'h': + flags |= FREE_HUMANREADABLE; + break; + case SI_OPTION: + flags |= FREE_SI; + break; + case 'l': + flags |= FREE_LOHI; + break; + case 'L': + flags |= FREE_LINE; + break; + case 't': + flags |= FREE_TOTAL; + break; + case 'v': + flags |= FREE_COMMITTED; + break; + case 's': + flags |= FREE_REPEAT; + errno = 0; + args.repeat_interval = (1000000 * strtod_nol_or_err(optarg, "seconds argument failed")); + if (args.repeat_interval < 1) + xerrx(EXIT_FAILURE, + _("seconds argument `%s' is not positive number"), optarg); + break; + case 'c': + flags |= FREE_REPEAT; + flags |= FREE_REPEATCOUNT; + args.repeat_counter = strtol_or_err(optarg, + _("failed to parse count argument")); + if (args.repeat_counter < 1) + error(EXIT_FAILURE, ERANGE, + _("failed to parse count argument: '%s'"), optarg); + break; + case 'w': + flags |= FREE_WIDE; + break; + case HELP_OPTION: + usage(stdout); + case 'V': + printf(PROCPS_NG_VERSION); + exit(EXIT_SUCCESS); + default: + usage(stderr); + } + if (optind != argc) + usage(stderr); + + if ( (rc = procps_meminfo_new(&mem_info)) < 0) + { + if (rc == -ENOENT) + xerrx(EXIT_FAILURE, + _("Memory information file /proc/meminfo does not exist")); + else + xerrx(EXIT_FAILURE, + _("Unable to create meminfo structure")); + } + do { + if ( flags & FREE_LINE ) { + /* Translation Hint: These are shortened column headers + * that are all 7 characters long. Use spaces and right + * align if the translation is shorter. + */ + printf("%s %11s ", _("SwapUse"), scale_size(MEMINFO_GET(mem_info, MEMINFO_SWAP_USED, ul_int), flags, args)); + printf("%s %11s ", _("CachUse"), scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_BUFFERS, ul_int) + + MEMINFO_GET(mem_info, MEMINFO_MEM_CACHED_ALL, ul_int), flags, args)); + printf("%s %11s ", _(" MemUse"), scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_USED, ul_int), flags, args)); + printf("%s %11s ", _("MemFree"), scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_FREE, ul_int), flags, args)); + if ( (flags & FREE_REPEAT) == 0 ) + printf("\n"); + else if ( args.repeat_counter == 1 ) + printf("\n"); + } else { + /* Translation Hint: You can use 9 character words in + * the header, and the words need to be right align to + * beginning of a number. */ + if (flags & FREE_WIDE) { + printf(_(" total used free shared buffers cache available")); + } else { + printf(_(" total used free shared buff/cache available")); + } + printf("\n"); + print_head_col(_("Mem:")); + printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_TOTAL, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_USED, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_FREE, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_SHARED, ul_int), flags, args)); + if (flags & FREE_WIDE) { + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_BUFFERS, ul_int), + flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_CACHED_ALL, ul_int) + , flags, args)); + } else { + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_BUFFERS, ul_int) + + MEMINFO_GET(mem_info, MEMINFO_MEM_CACHED_ALL, ul_int), flags, args)); + } + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_AVAILABLE, ul_int), flags, args)); + printf("\n"); + /* + * Print low vs. high information, if the user requested it. + * Note we check if low_total == 0: if so, then this kernel + * does not export the low and high stats. Note we still want + * to print the high info, even if it is zero. + */ + if (flags & FREE_LOHI) { + print_head_col(_("Low:")); + printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_LOW_TOTAL, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_LOW_USED, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_LOW_FREE, ul_int), flags, args)); + printf("\n"); + + print_head_col( _("High:")); + printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_HIGH_TOTAL, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_HIGH_USED, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_HIGH_FREE, ul_int), flags, args)); + printf("\n"); + } + + print_head_col(_("Swap:")); + printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_SWAP_TOTAL, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_SWAP_USED, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_SWAP_FREE, ul_int), flags, args)); + printf("\n"); + + if (flags & FREE_TOTAL) { + print_head_col(_("Total:")); + printf("%11s", scale_size( + MEMINFO_GET(mem_info, MEMINFO_MEM_TOTAL, ul_int) + + MEMINFO_GET(mem_info, MEMINFO_SWAP_TOTAL, ul_int), flags, args)); + printf(" %11s", scale_size( + MEMINFO_GET(mem_info, MEMINFO_MEM_USED, ul_int) + + MEMINFO_GET(mem_info, MEMINFO_SWAP_USED, ul_int), flags, args)); + printf(" %11s", scale_size( + MEMINFO_GET(mem_info, MEMINFO_MEM_FREE, ul_int) + + MEMINFO_GET(mem_info, MEMINFO_SWAP_FREE, ul_int), flags, args)); + printf("\n"); + } + if (flags & FREE_COMMITTED) { + print_head_col(_("Comm:")); + printf("%11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_COMMIT_LIMIT, ul_int), flags, args)); + printf(" %11s", scale_size(MEMINFO_GET(mem_info, MEMINFO_MEM_COMMITTED_AS, ul_int), flags, args)); + printf(" %11s", scale_size( + MEMINFO_GET(mem_info, MEMINFO_MEM_COMMIT_LIMIT, ul_int) - + MEMINFO_GET(mem_info, MEMINFO_MEM_COMMITTED_AS, ul_int), flags, args)); + printf("\n"); + } + + } /* end else of if FREE_LINE */ + fflush(stdout); + if (flags & FREE_REPEATCOUNT) { + args.repeat_counter--; + if (args.repeat_counter < 1) + exit(EXIT_SUCCESS); + } + if (flags & FREE_REPEAT) { + printf("\n"); + usleep(args.repeat_interval); + } + } while ((flags & FREE_REPEAT)); + + exit(EXIT_SUCCESS); +} diff --git a/src/kill.c b/src/kill.c new file mode 100644 index 0000000..612a9b5 --- /dev/null +++ b/src/kill.c @@ -0,0 +1,167 @@ +/* + * kill.c - send a signal to process + * + * Copyright © 1995-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 1998-2002 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <unistd.h> +#include <getopt.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <signal.h> +#include <ctype.h> + +#include "c.h" +#include "signals.h" +#include "strutils.h" +#include "nls.h" + +/* kill help */ +static void __attribute__ ((__noreturn__)) print_usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] <pid> [...]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" <pid> [...] send signal to every <pid> listed\n"), out); + fputs(_(" -<signal>, -s, --signal <signal>\n" + " specify the <signal> to be sent\n"), out); + fputs(_(" -q, --queue <value> integer value to be sent with the signal\n"), out); + fputs(_(" -l, --list=[<signal>] list all signal names, or convert one to a name\n"), out); + fputs(_(" -L, --table list all signal names in a nice table\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("kill(1)")); + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +inline static int execute_kill(pid_t pid, int sig_num, const bool use_sigqueue, union sigval sigval) +{ + if (use_sigqueue) + return sigqueue(pid, sig_num, sigval); + else + return kill(pid, sig_num); +} + +int main(int argc, char **argv) +{ + int signo, i; + long pid; + int exitvalue = EXIT_SUCCESS; + int optindex; + union sigval sigval; + bool use_sigqueue = false; + char *sig_option; + + static const struct option longopts[] = { + {"list", optional_argument, NULL, 'l'}, + {"table", no_argument, NULL, 'L'}, + {"signal", required_argument, NULL, 's'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"queue", required_argument, NULL, 'q'}, + {NULL, 0, NULL, 0} + }; + + + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + + if (argc < 2) + print_usage(stderr); + + signo = skill_sig_option(&argc, argv); + if (signo < 0) + signo = SIGTERM; + + opterr=0; /* suppress errors on -123 */ + while ((i = getopt_long(argc, argv, "l::Ls:hVq:", longopts, &optindex)) != -1) + switch (i) { + case 'l': + sig_option = NULL; + if (optarg) { + sig_option = optarg; + } else if (argv[optind] != NULL && argv[optind][0] != '-') { + sig_option = argv[optind]; + } + if (sig_option) { + char *s; + s = strtosig(sig_option); + if (s) + printf("%s\n", s); + else + xwarnx(_("unknown signal name %s"), + sig_option); + free(s); + } else { + unix_print_signals(); + } + exit(EXIT_SUCCESS); + case 'L': + pretty_print_signals(); + exit(EXIT_SUCCESS); + case 's': + signo = signal_name_to_number(optarg); + break; + case 'h': + print_usage(stdout); + case 'V': + fprintf(stdout, PROCPS_NG_VERSION); + exit(EXIT_SUCCESS); + case 'q': + sigval.sival_int = strtol_or_err(optarg, _("must be an integer value to be passed with the signal.")); + use_sigqueue = true; + break; + case '?': + if (!isdigit(optopt)) { + xwarnx(_("invalid argument %c"), optopt); + print_usage(stderr); + } else { + /* Special case for signal digit negative + * PIDs */ + pid = (long)('0' - optopt); + if (!execute_kill((pid_t) pid, signo, use_sigqueue, sigval)) + exitvalue = EXIT_FAILURE; + exit(exitvalue); + } + xerrx(EXIT_FAILURE, _("internal error")); + default: + print_usage(stderr); + } + + argc -= optind; + argv += optind; + + if (argc < 1) + print_usage(stderr); + + for (i = 0; i < argc; i++) { + pid = strtol_or_err(argv[i], _("failed to parse argument")); + if (!execute_kill((pid_t) pid, signo, use_sigqueue, sigval)) + continue; + error(0, errno, "(%ld)", pid); + exitvalue = EXIT_FAILURE; + continue; + } + + return exitvalue; +} diff --git a/src/pgrep.c b/src/pgrep.c new file mode 100644 index 0000000..d8e57df --- /dev/null +++ b/src/pgrep.c @@ -0,0 +1,1212 @@ +/* + * pgrep/pkill -- utilities to filter the process table + * + * Copyright © 2009-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2013-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2012 Roberto Polli <rpolli@babel.it> + * Copyright © 2002-2007 Albert Cahalan + * Copyright © 2000 Kjetil Torgrim Homme <kjetilho@ifi.uio.no> + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <ctype.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <signal.h> +#include <pwd.h> +#include <grp.h> +#include <regex.h> +#include <errno.h> +#include <getopt.h> +#include <stdbool.h> +#include <time.h> + +#ifdef ENABLE_PIDWAIT +#include <sys/epoll.h> +#ifndef HAVE_PIDFD_OPEN +#include <sys/syscall.h> +#endif /* !HAVE_PIDFD_OPEN */ +#endif + +/* EXIT_SUCCESS is 0 */ +/* EXIT_FAILURE is 1 */ +#define EXIT_USAGE 2 +#define EXIT_FATAL 3 +#define XALLOC_EXIT_CODE EXIT_FATAL + +#include "c.h" +#include "fileutils.h" +#include "nls.h" +#include "signals.h" +#include "xalloc.h" + +#include "misc.h" +#include "pids.h" + +enum pids_item Items[] = { + PIDS_ID_PID, + PIDS_ID_PPID, + PIDS_ID_PGRP, + PIDS_ID_EUID, + PIDS_ID_RUID, + PIDS_ID_RGID, + PIDS_ID_SESSION, + PIDS_ID_TGID, + PIDS_TICS_BEGAN, + PIDS_TTY_NAME, + PIDS_CMD, + PIDS_CMDLINE, + PIDS_STATE, + PIDS_TIME_ELAPSED, + PIDS_CGROUP_V, + PIDS_SIGCATCH +}; +enum rel_items { + EU_PID, EU_PPID, EU_PGRP, EU_EUID, EU_RUID, EU_RGID, EU_SESSION, + EU_TGID, EU_STARTTIME, EU_TTYNAME, EU_CMD, EU_CMDLINE, EU_STA, EU_ELAPSED, + EU_CGROUP, EU_SIGCATCH +}; +#define grow_size(x) do { \ + if ((x) < 0 || (size_t)(x) >= INT_MAX / 5 / sizeof(struct el)) \ + xerrx(EXIT_FAILURE, _("integer overflow")); \ + (x) = (x) * 5 / 4 + 4; \ +} while (0) + +static enum { + PGREP = 0, + PKILL, +#ifdef ENABLE_PIDWAIT + PIDWAIT, +#endif +} prog_mode; + +struct el { + long num; + char * str; +}; + +/* User supplied arguments */ + +static int opt_full = 0; +static int opt_long = 0; +static int opt_longlong = 0; +static int opt_oldest = 0; +static int opt_older = 0; +static int opt_newest = 0; +static int opt_negate = 0; +static int opt_exact = 0; +static int opt_count = 0; +static int opt_signal = SIGTERM; +static int opt_lock = 0; +static int opt_case = 0; +static int opt_echo = 0; +static int opt_threads = 0; +static pid_t opt_ns_pid = 0; +static bool use_sigqueue = false; +static bool require_handler = false; +static union sigval sigval = {0}; + +static const char *opt_delim = "\n"; +static struct el *opt_pgrp = NULL; +static struct el *opt_rgid = NULL; +static struct el *opt_pid = NULL; +static struct el *opt_ppid = NULL; +static struct el *opt_ignore_ancestors = NULL; +static struct el *opt_sid = NULL; +static struct el *opt_term = NULL; +static struct el *opt_euid = NULL; +static struct el *opt_ruid = NULL; +static struct el *opt_nslist = NULL; +static struct el *opt_cgroup = NULL; +static char *opt_pattern = NULL; +static char *opt_pidfile = NULL; +static char *opt_runstates = NULL; + +/* by default, all namespaces will be checked */ +static int ns_flags = 0x3f; + +static int __attribute__ ((__noreturn__)) usage(int opt) +{ + int err = (opt == '?'); + FILE *fp = err ? stderr : stdout; + + fputs(USAGE_HEADER, fp); + fprintf(fp, _(" %s [options] <pattern>\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, fp); + switch (prog_mode) { + case PGREP: + fputs(_(" -d, --delimiter <string> specify output delimiter\n"),fp); + fputs(_(" -l, --list-name list PID and process name\n"),fp); + fputs(_(" -a, --list-full list PID and full command line\n"),fp); + fputs(_(" -v, --inverse negates the matching\n"),fp); + fputs(_(" -w, --lightweight list all TID\n"), fp); + break; + case PKILL: + fputs(_(" -<sig> signal to send (either number or name)\n"), fp); + fputs(_(" -H, --require-handler match only if signal handler is present\n"), fp); + fputs(_(" -q, --queue <value> integer value to be sent with the signal\n"), fp); + fputs(_(" -e, --echo display what is killed\n"), fp); + break; +#ifdef ENABLE_PIDWAIT + case PIDWAIT: + fputs(_(" -e, --echo display PIDs before waiting\n"), fp); + break; +#endif + } + fputs(_(" -c, --count count of matching processes\n"), fp); + fputs(_(" -f, --full use full process name to match\n"), fp); + fputs(_(" -g, --pgroup <PGID,...> match listed process group IDs\n"), fp); + fputs(_(" -G, --group <GID,...> match real group IDs\n"), fp); + fputs(_(" -i, --ignore-case match case insensitively\n"), fp); + fputs(_(" -n, --newest select most recently started\n"), fp); + fputs(_(" -o, --oldest select least recently started\n"), fp); + fputs(_(" -O, --older <seconds> select where older than seconds\n"), fp); + fputs(_(" -P, --parent <PPID,...> match only child processes of the given parent\n"), fp); + fputs(_(" -s, --session <SID,...> match session IDs\n"), fp); + fputs(_(" --signal <sig> signal to send (either number or name)\n"), fp); + fputs(_(" -t, --terminal <tty,...> match by controlling terminal\n"), fp); + fputs(_(" -u, --euid <ID,...> match by effective IDs\n"), fp); + fputs(_(" -U, --uid <ID,...> match by real IDs\n"), fp); + fputs(_(" -x, --exact match exactly with the command name\n"), fp); + fputs(_(" -F, --pidfile <file> read PIDs from file\n"), fp); + fputs(_(" -L, --logpidfile fail if PID file is not locked\n"), fp); + fputs(_(" -r, --runstates <state> match runstates [D,S,Z,...]\n"), fp); + fputs(_(" -A, --ignore-ancestors exclude our ancestors from results\n"), fp); + fputs(_(" --cgroup <grp,...> match by cgroup v2 names\n"), fp); + fputs(_(" --ns <PID> match the processes that belong to the same\n" + " namespace as <pid>\n"), fp); + fputs(_(" --nslist <ns,...> list which namespaces will be considered for\n" + " the --ns option.\n" + " Available namespaces: ipc, mnt, net, pid, user, uts\n"), fp); + fputs(USAGE_SEPARATOR, fp); + fputs(USAGE_HELP, fp); + fputs(USAGE_VERSION, fp); + fprintf(fp, USAGE_MAN_TAIL("pgrep(1)")); + + exit(fp == stderr ? EXIT_USAGE : EXIT_SUCCESS); +} + +static struct el *get_our_ancestors(void) +{ +#define PIDS_GETINT(e) PIDS_VAL(EU_##e, s_int, stack, info) + struct el *list = NULL; + int i = 0; + int size = 0; + int done = 0; + pid_t search_pid = getpid(); + struct pids_stack *stack; + + while (!done) { + struct pids_info *info = NULL; + + if (procps_pids_new(&info, Items, 16) < 0) + xerrx(EXIT_FATAL, _("Unable to create pid info structure")); + + if (i == size) { + grow_size(size); + list = xrealloc(list, (1 + size) * sizeof(*list)); + } + + done = 1; + while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) { + if (PIDS_GETINT(PID) == search_pid) { + list[++i].num = PIDS_GETINT(PPID); + search_pid = list[i].num; + done = 0; + break; + } + } + + procps_pids_unref(&info); + } + + if (i == 0) { + free(list); + list = NULL; + } else { + list[0].num = i; + } + return list; +#undef PIDS_GETINT +} + +static struct el *split_list (const char *restrict str, int (*convert)(const char *, struct el *)) +{ + char *copy; + char *ptr; + char *sep_pos; + int i = 0; + int size = 0; + struct el *list = NULL; + + if (str[0] == '\0') + return NULL; + + copy = xstrdup (str); + ptr = copy; + + do { + if (i == size) { + grow_size(size); + /* add 1 because slot zero is a count */ + list = xrealloc (list, (1 + size) * sizeof *list); + } + sep_pos = strchr (ptr, ','); + if (sep_pos) + *sep_pos = 0; + /* Use ++i instead of i++ because slot zero is a count */ + if (list && !convert (ptr, &list[++i])) + exit (EXIT_USAGE); + if (sep_pos) + ptr = sep_pos + 1; + } while (sep_pos); + + free (copy); + if (!i) { + free (list); + list = NULL; + } else { + list[0].num = i; + } + return list; +} + +/* strict_atol returns a Boolean: TRUE if the input string + * contains a plain number, FALSE if there are any non-digits. */ +static int strict_atol (const char *restrict str, long *restrict value) +{ + long res = 0; + long sign = 1; + + if (*str == '+') + ++str; + else if (*str == '-') { + ++str; + sign = -1; + } + + for ( ; *str; ++str) { + if (! isdigit (*str)) + return 0; + if (res >= LONG_MAX / 10) + return 0; + res *= 10; + if (res >= LONG_MAX - (*str - '0')) + return 0; + res += *str - '0'; + } + *value = sign * res; + return 1; +} + +#include <sys/file.h> + +/* We try a read lock. The daemon should have a write lock. + * Seen using flock: FreeBSD code */ +static int has_flock(int fd) +{ + return flock(fd, LOCK_SH|LOCK_NB)==-1 && errno==EWOULDBLOCK; +} + +/* We try a read lock. The daemon should have a write lock. + * Seen using fcntl: libslack */ +static int has_fcntl(int fd) +{ + struct flock f; /* seriously, struct flock is for a fnctl lock! */ + f.l_type = F_RDLCK; + f.l_whence = SEEK_SET; + f.l_start = 0; + f.l_len = 0; + return fcntl(fd,F_SETLK,&f)==-1 && (errno==EACCES || errno==EAGAIN); +} + +static struct el *read_pidfile(void) +{ + char buf[12]; + int fd; + struct stat sbuf; + char *endp; + int n, pid; + struct el *list = NULL; + + fd = open(opt_pidfile, O_RDONLY|O_NOCTTY|O_NONBLOCK); + if(fd<0) + goto just_ret; + if(fstat(fd,&sbuf) || !S_ISREG(sbuf.st_mode) || sbuf.st_size<1) + goto out; + /* type of lock, if any, is not standardized on Linux */ + if(opt_lock && !has_flock(fd) && !has_fcntl(fd)) + goto out; + memset(buf,'\0',sizeof buf); + n = read(fd,buf,sizeof buf-1); + if (n<1) + goto out; + pid = strtoul(buf,&endp,10); + if(endp<=buf || pid<1 ) + goto out; + if(*endp && !isspace(*endp)) + goto out; + list = xmalloc(2 * sizeof *list); + list[0].num = 1; + list[1].num = pid; +out: + close(fd); +just_ret: + return list; +} + +static int conv_uid (const char *restrict name, struct el *restrict e) +{ + struct passwd *pwd; + + if (strict_atol (name, &e->num)) + return (1); + + pwd = getpwnam (name); + if (pwd == NULL) { + xwarnx(_("invalid user name: %s"), name); + return 0; + } + e->num = pwd->pw_uid; + return 1; +} + + +static int conv_gid (const char *restrict name, struct el *restrict e) +{ + struct group *grp; + + if (strict_atol (name, &e->num)) + return 1; + + grp = getgrnam (name); + if (grp == NULL) { + xwarnx(_("invalid group name: %s"), name); + return 0; + } + e->num = grp->gr_gid; + return 1; +} + + +static int conv_pgrp (const char *restrict name, struct el *restrict e) +{ + if (! strict_atol (name, &e->num)) { + xwarnx(_("invalid process group: %s"), name); + return 0; + } + if (e->num == 0) + e->num = getpgrp (); + return 1; +} + + +static int conv_sid (const char *restrict name, struct el *restrict e) +{ + if (! strict_atol (name, &e->num)) { + xwarnx(_("invalid session id: %s"), name); + return 0; + } + if (e->num == 0) + e->num = getsid (0); + return 1; +} + + +static int conv_num (const char *restrict name, struct el *restrict e) +{ + if (! strict_atol (name, &e->num)) { + xwarnx(_("not a number: %s"), name); + return 0; + } + return 1; +} + + +static int conv_str (const char *restrict name, struct el *restrict e) +{ + e->str = xstrdup (name); + return 1; +} + + +static int conv_ns (const char *restrict name, struct el *restrict e) +{ + int rc = conv_str(name, e); + int id; + + ns_flags = 0; + id = procps_ns_get_id(name); + if (id < 0) + return 0; + ns_flags |= (1 << id); + + return rc; +} + +static int match_numlist (long value, const struct el *restrict list) +{ + int found = 0; + if (list != NULL) { + int i; + for (i = list[0].num; i > 0; i--) { + if (list[i].num == value) { + found = 1; + break; + } + } + } + return found; +} + +static unsigned long long unhex (const char *restrict in) +{ + unsigned long long ret; + char *rem; + errno = 0; + ret = strtoull(in, &rem, 16); + if (errno || *rem != '\0') { + xwarnx(_("not a hex string: %s"), in); + return 0; + } + return ret; +} + +static int match_signal_handler (const char *restrict sigcgt, const int signal) +{ + return sigcgt && (((1UL << (signal - 1)) & unhex(sigcgt)) != 0); +} + +static int match_strlist (const char *restrict value, const struct el *restrict list) +{ + int found = 0; + if (list != NULL) { + int i; + for (i = list[0].num; i > 0; i--) { + if (! strcmp (list[i].str, value)) { + found = 1; + break; + } + } + } + return found; +} + +static int match_ns (const int pid, + const struct procps_ns *match_ns) +{ + struct procps_ns proc_ns; + int found = 1; + int i; + + if (procps_ns_read_pid(pid, &proc_ns) < 0) + xerrx(EXIT_FATAL, + _("Unable to read process namespace information")); + for (i = 0; i < PROCPS_NS_COUNT; i++) { + if (ns_flags & (1 << i)) { + if (proc_ns.ns[i] != match_ns->ns[i]) { + found = 0; + break; + } + } + } + return found; +} + +static int cgroup_cmp(const char *restrict cgroup, + const char *restrict path) +{ + if (cgroup == NULL || path == NULL) + return 1; + // Cgroup v2 have 0:: + if (strncmp("0::", cgroup, 3) == 0) { + return strcmp(cgroup+3, path); + } //might try for cgroup v1 later + return 1; +} + + +static int match_cgroup_list(char **values, + const struct el *restrict list) +{ + if (list != NULL && values != NULL) { + int i, j; + for (i = list[0].num; i > 0; i--) { + for (j=0; values[j] && values[j][0]; j++) { + if (! cgroup_cmp (values[j], list[i].str)) { + return 1; + } + } + } + } + return 0; +} + +static void output_numlist (const struct el *restrict list, int num) +{ + int i; + const char *delim = opt_delim; + for (i = 0; i < num; i++) { + if(i+1==num) + delim = "\n"; + printf ("%ld%s", list[i].num, delim); + } +} + +static void output_strlist (const struct el *restrict list, int num) +{ +/* FIXME: escape codes */ + int i; + const char *delim = opt_delim; + for (i = 0; i < num; i++) { + if(i+1==num) + delim = "\n"; + printf ("%lu %s%s", list[i].num, list[i].str, delim); + } +} + +static regex_t * do_regcomp (void) +{ + regex_t *preg = NULL; + + if (opt_pattern) { + char *re; + char errbuf[256]; + int re_err; + + preg = xmalloc (sizeof (regex_t)); + if (opt_exact) { + re = xmalloc (strlen (opt_pattern) + 5); + sprintf (re, "^(%s)$", opt_pattern); + } else { + re = opt_pattern; + } + + re_err = regcomp (preg, re, REG_EXTENDED | REG_NOSUB | opt_case); + + if (opt_exact) free(re); + + if (re_err) { + regerror (re_err, preg, errbuf, sizeof(errbuf)); + xerrx(EXIT_USAGE, _("regex error: %s"), errbuf); + } + } + return preg; +} + +/* + * SC_ARG_MAX used to return the maximum size a command line can be + * however changes to the kernel mean this can be bigger than we can + * alloc. Clamp it to 128kB like xargs and friends do + * Should also not be smaller than POSIX_ARG_MAX which is 4096 + */ +static size_t get_arg_max(void) +{ +#define MIN_ARG_SIZE 4096u +#define MAX_ARG_SIZE (128u * 1024u) + + size_t val = sysconf(_SC_ARG_MAX); + + if (val < MIN_ARG_SIZE) + val = MIN_ARG_SIZE; + if (val > MAX_ARG_SIZE) + val = MAX_ARG_SIZE; + + return val; +} + +/* + * Check if we have a long simple (non-regex) match + * Returns true if the string: + * 1) is longer than 15 characters + * 2) Doesn't have | or [ which are used by regex + * This is not an exhaustive list but catches most instances + * It's only used to suppress the warning + */ +static bool is_long_match(const char *str) +{ + int i, len; + + if (str == NULL) + return FALSE; + if (15 >= (len = strlen(str))) + return FALSE; + for (i=0; i<len; i++) + if (str[i] == '|' || str[i] == '[') + return FALSE; + return TRUE; +} +static struct el * select_procs (int *num) +{ +#define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, stack, info) +#define PIDS_GETUNT(e) PIDS_VAL(EU_ ## e, u_int, stack, info) +#define PIDS_GETULL(e) PIDS_VAL(EU_ ## e, ull_int, stack, info) +#define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, stack, info) +#define PIDS_GETSCH(e) PIDS_VAL(EU_ ## e, s_ch, stack, info) +#define PIDS_GETSTV(e) PIDS_VAL(EU_ ## e, strv, stack, info) +#define PIDS_GETFLT(e) PIDS_VAL(EU_ ## e, real, stack, info) + struct pids_info *info=NULL; + struct procps_ns nsp; + struct pids_stack *stack; + unsigned long long saved_start_time; /* for new/old support */ + int saved_pid = 0; /* for new/old support */ + int matches = 0; + int size = 0; + regex_t *preg; + pid_t myself = getpid(); + struct el *list = NULL; + long cmdlen = get_arg_max() * sizeof(char); + char *cmdline = xmalloc(cmdlen); + char *cmdsearch = xmalloc(cmdlen); + char *cmdoutput = xmalloc(cmdlen); + char *task_cmdline; + enum pids_fetch_type which; + + preg = do_regcomp(); + + if (opt_newest) saved_start_time = 0ULL; + else saved_start_time = ~0ULL; + + if (opt_newest) saved_pid = 0; + if (opt_oldest) saved_pid = INT_MAX; + if (opt_ns_pid && procps_ns_read_pid(opt_ns_pid, &nsp) < 0) { + xerrx(EXIT_FATAL, + _("Error reading reference namespace information\n")); + } + + if (procps_pids_new(&info, Items, 16) < 0) + xerrx(EXIT_FATAL, + _("Unable to create pid info structure")); + which = PIDS_FETCH_TASKS_ONLY; + // pkill and pidwait don't support -w, but this is checked in getopt + if (opt_threads) + which = PIDS_FETCH_THREADS_TOO; + + while ((stack = procps_pids_get(info, which))) { + int match = 1; + + if (PIDS_GETINT(PID) == myself) + continue; + else if (opt_ignore_ancestors && match_numlist(PIDS_GETINT(PID), opt_ignore_ancestors)) + continue; + else if (opt_newest && PIDS_GETULL(STARTTIME) < saved_start_time) + match = 0; + else if (opt_oldest && PIDS_GETULL(STARTTIME) > saved_start_time) + match = 0; + else if (opt_ppid && ! match_numlist(PIDS_GETINT(PPID), opt_ppid)) + match = 0; + else if (opt_pid && ! match_numlist (PIDS_GETINT(TGID), opt_pid)) + match = 0; + else if (opt_pgrp && ! match_numlist (PIDS_GETINT(PGRP), opt_pgrp)) + match = 0; + else if (opt_euid && ! match_numlist (PIDS_GETUNT(EUID), opt_euid)) + match = 0; + else if (opt_ruid && ! match_numlist (PIDS_GETUNT(RUID), opt_ruid)) + match = 0; + else if (opt_rgid && ! match_numlist (PIDS_GETUNT(RGID), opt_rgid)) + match = 0; + else if (opt_sid && ! match_numlist (PIDS_GETINT(SESSION), opt_sid)) + match = 0; + else if (opt_ns_pid && ! match_ns (PIDS_GETINT(PID), &nsp)) + match = 0; + else if (opt_older && (int)PIDS_GETFLT(ELAPSED) < opt_older) + match = 0; + else if (opt_term && ! match_strlist(PIDS_GETSTR(TTYNAME), opt_term)) + match = 0; + else if (opt_runstates && ! strchr(opt_runstates, PIDS_GETSCH(STA))) + match = 0; + else if (opt_cgroup && ! match_cgroup_list (PIDS_GETSTV(CGROUP), opt_cgroup)) + match = 0; + else if (require_handler && ! match_signal_handler (PIDS_GETSTR(SIGCATCH), opt_signal)) + match = 0; + + task_cmdline = PIDS_GETSTR(CMDLINE); + + if (opt_long || opt_longlong || (match && opt_pattern)) { + if (opt_longlong) + strncpy (cmdoutput, task_cmdline, cmdlen -1); + else + strncpy (cmdoutput, PIDS_GETSTR(CMD), cmdlen -1); + cmdoutput[cmdlen - 1] = '\0'; + } + + if (match && opt_pattern) { + if (opt_full) + strncpy (cmdsearch, task_cmdline, cmdlen -1); + else + strncpy (cmdsearch, PIDS_GETSTR(CMD), cmdlen -1); + cmdsearch[cmdlen - 1] = '\0'; + + if (regexec (preg, cmdsearch, 0, NULL, 0) != 0) + match = 0; + } + + if (match ^ opt_negate) { /* Exclusive OR is neat */ + if (opt_newest) { + if (saved_start_time == PIDS_GETULL(STARTTIME) && + saved_pid > PIDS_GETINT(PID)) + continue; + saved_start_time = PIDS_GETULL(STARTTIME); + saved_pid = PIDS_GETINT(PID); + matches = 0; + } + if (opt_oldest) { + if (saved_start_time == PIDS_GETULL(STARTTIME) && + saved_pid < PIDS_GETINT(PID)) + continue; + saved_start_time = PIDS_GETULL(STARTTIME); + saved_pid = PIDS_GETINT(PID); + matches = 0; + } + if (matches == size) { + grow_size(size); + list = xrealloc(list, size * sizeof *list); + } + if (list && (opt_long || opt_longlong || opt_echo)) { + list[matches].num = PIDS_GETINT(PID); + list[matches++].str = xstrdup (cmdoutput); + } else if (list) { + list[matches++].num = PIDS_GETINT(PID); + } else { + xerrx(EXIT_FATAL, _("internal error")); + } + } + } + procps_pids_unref(&info); + free(cmdline); + free(cmdsearch); + free(cmdoutput); + + if (preg) { + regfree(preg); + free(preg); + } + + *num = matches; + + if ((!matches) && (!opt_full) && is_long_match(opt_pattern)) + xwarnx(_("pattern that searches for process name longer than 15 characters will result in zero matches\n" + "Try `%s -f' option to match against the complete command line."), + program_invocation_short_name); + return list; +#undef PIDS_GETINT +#undef PIDS_GETUNT +#undef PIDS_GETULL +#undef PIDS_GETSTR +#undef PIDS_GETSTV +} + +static int signal_option(int *argc, char **argv) +{ + int sig; + int i; + for (i = 1; i < *argc; i++) { + if (argv[i][0] == '-') { + sig = signal_name_to_number(argv[i] + 1); + if (-1 < sig) { + memmove(argv + i, argv + i + 1, + sizeof(char *) * (*argc - i)); + (*argc)--; + return sig; + } + } + } + return -1; +} + +#if defined(ENABLE_PIDWAIT) && !defined(HAVE_PIDFD_OPEN) +static int pidfd_open (pid_t pid, unsigned int flags) +{ + return syscall(__NR_pidfd_open, pid, flags); +} +#endif + +static void parse_opts (int argc, char **argv) +{ + char opts[64] = ""; + int opt; + int criteria_count = 0; + + enum { + SIGNAL_OPTION = CHAR_MAX + 1, + NS_OPTION, + NSLIST_OPTION, + CGROUP_OPTION, + }; + static const struct option longopts[] = { + {"signal", required_argument, NULL, SIGNAL_OPTION}, + {"ignore-ancestors", no_argument, NULL, 'A'}, + {"require-handler", no_argument, NULL, 'H'}, + {"count", no_argument, NULL, 'c'}, + {"cgroup", required_argument, NULL, CGROUP_OPTION}, + {"delimiter", required_argument, NULL, 'd'}, + {"list-name", no_argument, NULL, 'l'}, + {"list-full", no_argument, NULL, 'a'}, + {"full", no_argument, NULL, 'f'}, + {"pgroup", required_argument, NULL, 'g'}, + {"group", required_argument, NULL, 'G'}, + {"ignore-case", no_argument, NULL, 'i'}, + {"newest", no_argument, NULL, 'n'}, + {"oldest", no_argument, NULL, 'o'}, + {"older", required_argument, NULL, 'O'}, + {"parent", required_argument, NULL, 'P'}, + {"session", required_argument, NULL, 's'}, + {"terminal", required_argument, NULL, 't'}, + {"euid", required_argument, NULL, 'u'}, + {"uid", required_argument, NULL, 'U'}, + {"inverse", no_argument, NULL, 'v'}, + {"lightweight", no_argument, NULL, 'w'}, + {"exact", no_argument, NULL, 'x'}, + {"pidfile", required_argument, NULL, 'F'}, + {"logpidfile", no_argument, NULL, 'L'}, + {"echo", no_argument, NULL, 'e'}, + {"ns", required_argument, NULL, NS_OPTION}, + {"nslist", required_argument, NULL, NSLIST_OPTION}, + {"queue", required_argument, NULL, 'q'}, + {"runstates", required_argument, NULL, 'r'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + + +#ifdef ENABLE_PIDWAIT + if (strcmp (program_invocation_short_name, "pidwait") == 0 || + strcmp (program_invocation_short_name, "lt-pidwait") == 0) { + prog_mode = PIDWAIT; + strcat (opts, "e"); + } else +#endif + if (strcmp (program_invocation_short_name, "pkill") == 0 || + strcmp (program_invocation_short_name, "lt-pkill") == 0) { + int sig; + prog_mode = PKILL; + sig = signal_option(&argc, argv); + if (-1 < sig) + opt_signal = sig; + strcat (opts, "eq:"); + } else { + strcat (opts, "lad:vw"); + prog_mode = PGREP; + } + + strcat (opts, "LF:cfinoxP:O:AHg:s:u:U:G:t:r:?Vh"); + + while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != -1) { + switch (opt) { + case SIGNAL_OPTION: + opt_signal = signal_name_to_number (optarg); + if (opt_signal == -1) { + if (isdigit (optarg[0])) + opt_signal = atoi (optarg); + else { + fprintf(stderr, _("Unknown signal \"%s\"."), optarg); + usage('?'); + } + } + break; + case 'e': + opt_echo = 1; + break; +/* case 'D': / * FreeBSD: print info about non-matches for debugging * / + * break; */ + case 'F': /* FreeBSD: the arg is a file containing a PID to match */ + free(opt_pidfile); + opt_pidfile = xstrdup (optarg); + ++criteria_count; + break; + case 'G': /* Solaris: match rgid/rgroup */ + opt_rgid = split_list (optarg, conv_gid); + if (opt_rgid == NULL) + usage ('?'); + ++criteria_count; + break; +/* case 'I': / * FreeBSD: require confirmation before killing * / + * break; */ +/* case 'J': / * Solaris: match by project ID (name or number) * / + * break; */ + case 'L': /* FreeBSD: fail if pidfile (see -F) not locked */ + opt_lock++; + break; +/* case 'M': / * FreeBSD: specify core (OS crash dump) file * / + * break; */ +/* case 'N': / * FreeBSD: specify alternate namelist file (for us, System.map -- but we don't need it) * / + * break; */ + case 'P': /* Solaris: match by PPID */ + opt_ppid = split_list (optarg, conv_num); + if (opt_ppid == NULL) + usage ('?'); + ++criteria_count; + break; +/* case 'S': / * FreeBSD: don't ignore the built-in kernel tasks * / + * break; */ +/* case 'T': / * Solaris: match by "task ID" (probably not a Linux task) * / + * break; */ + case 'U': /* Solaris: match by ruid/rgroup */ + opt_ruid = split_list (optarg, conv_uid); + if (opt_ruid == NULL) + usage ('?'); + ++criteria_count; + break; + case 'V': + printf(PROCPS_NG_VERSION); + exit(EXIT_SUCCESS); +/* case 'c': / * Solaris: match by contract ID * / + * break; */ + case 'c': + opt_count = 1; + break; + case 'd': /* Solaris: change the delimiter */ + opt_delim = xstrdup (optarg); + break; + case 'f': /* Solaris: match full process name (as in "ps -f") */ + opt_full = 1; + break; + case 'g': /* Solaris: match pgrp */ + opt_pgrp = split_list (optarg, conv_pgrp); + if (opt_pgrp == NULL) + usage ('?'); + ++criteria_count; + break; + case 'i': /* FreeBSD: ignore case. OpenBSD: withdrawn. See -I. This sucks. */ + if (opt_case) + usage (opt); + opt_case = REG_ICASE; + break; +/* case 'j': / * FreeBSD: restricted to the given jail ID * / + * break; */ + case 'l': /* Solaris: long output format (pgrep only) Should require -f for beyond argv[0] maybe? */ + opt_long = 1; + break; + case 'a': + opt_longlong = 1; + break; + case 'A': + opt_ignore_ancestors = get_our_ancestors(); + break; + case 'n': /* Solaris: match only the newest */ + if (opt_oldest|opt_negate|opt_newest) + usage ('?'); + opt_newest = 1; + ++criteria_count; + break; + case 'o': /* Solaris: match only the oldest */ + if (opt_oldest|opt_negate|opt_newest) + usage ('?'); + opt_oldest = 1; + ++criteria_count; + break; + case 'O': + opt_older = atoi (optarg); + ++criteria_count; + break; + case 's': /* Solaris: match by session ID -- zero means self */ + opt_sid = split_list (optarg, conv_sid); + if (opt_sid == NULL) + usage ('?'); + ++criteria_count; + break; + case 't': /* Solaris: match by tty */ + opt_term = split_list (optarg, conv_str); + if (opt_term == NULL) + usage ('?'); + ++criteria_count; + break; + case 'u': /* Solaris: match by euid/egroup */ + opt_euid = split_list (optarg, conv_uid); + if (opt_euid == NULL) + usage ('?'); + ++criteria_count; + break; + case 'v': /* Solaris: as in grep, invert the matching (uh... applied after selection I think) */ + if (opt_oldest|opt_negate|opt_newest) + usage ('?'); + opt_negate = 1; + break; + case 'w': // Linux: show threads (lightweight process) too + opt_threads = 1; + break; + /* OpenBSD -x, being broken, does a plain string */ + case 'x': /* Solaris: use ^(regexp)$ in place of regexp (FreeBSD too) */ + opt_exact = 1; + break; +/* case 'z': / * Solaris: match by zone ID * / + * break; */ + case NS_OPTION: + opt_ns_pid = atoi(optarg); + if (opt_ns_pid == 0) + case 'r': /* match by runstate */ + opt_runstates = xstrdup (optarg); + ++criteria_count; + break; + case NSLIST_OPTION: + opt_nslist = split_list (optarg, conv_ns); + if (opt_nslist == NULL) + usage ('?'); + break; + case 'q': + sigval.sival_int = atoi(optarg); + use_sigqueue = true; + break; + case CGROUP_OPTION: + opt_cgroup = split_list (optarg, conv_str); + if (opt_cgroup == NULL) + usage ('?'); + ++criteria_count; + break; + case 'H': + require_handler = true; + ++criteria_count; + break; + case 'h': + case '?': + usage (opt); + break; + } + } + + if(opt_lock && !opt_pidfile) + xerrx(EXIT_USAGE, _("-L without -F makes no sense\n" + "Try `%s --help' for more information."), + program_invocation_short_name); + + if(opt_pidfile){ + opt_pid = read_pidfile(); + if(!opt_pid) + xerrx(EXIT_FAILURE, _("pidfile not valid\n" + "Try `%s --help' for more information."), + program_invocation_short_name); + } + + if (argc - optind == 1) + opt_pattern = argv[optind]; + + else if (argc - optind > 1) + xerrx(EXIT_USAGE, _("only one pattern can be provided\n" + "Try `%s --help' for more information."), + program_invocation_short_name); + else if (criteria_count == 0) + xerrx(EXIT_USAGE, _("no matching criteria specified\n" + "Try `%s --help' for more information."), + program_invocation_short_name); +} + +inline static int execute_kill(pid_t pid, int sig_num) +{ + if (use_sigqueue) + return sigqueue(pid, sig_num, sigval); + else + return kill(pid, sig_num); +} + +int main (int argc, char **argv) +{ + struct el *procs; + int num; + int i; + int kill_count = 0; +#ifdef ENABLE_PIDWAIT + int poll_count = 0; + int wait_count = 0; + int epollfd = epoll_create(1); + struct epoll_event ev, events[32]; +#endif + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + parse_opts (argc, argv); + + procs = select_procs (&num); + switch (prog_mode) { + case PGREP: + if (opt_count) { + fprintf(stdout, "%d\n", num); + } else { + if (opt_long || opt_longlong) + output_strlist (procs,num); + else + output_numlist (procs,num); + } + return !num; + case PKILL: + for (i = 0; i < num; i++) { + if (execute_kill (procs[i].num, opt_signal) != -1) { + if (opt_echo) + printf(_("%s killed (pid %lu)\n"), procs[i].str, procs[i].num); + kill_count++; + continue; + } + if (errno==ESRCH) + /* gone now, which is OK */ + continue; + xwarn(_("killing pid %ld failed"), procs[i].num); + } + if (opt_count) + fprintf(stdout, "%d\n", num); + return !kill_count; +#ifdef ENABLE_PIDWAIT + case PIDWAIT: + if (opt_count) + fprintf(stdout, "%d\n", num); + + for (i = 0; i < num; i++) { + if (opt_echo) + printf(_("waiting for %s (pid %lu)\n"), procs[i].str, procs[i].num); + int pidfd = pidfd_open(procs[i].num, 0); + if (pidfd == -1) { + if (errno == ENOSYS) + xerrx(EXIT_FAILURE, _("pidfd_open() not implemented in Linux < 5.3")); + /* ignore ESRCH, same as pkill */ + if (errno != ESRCH) + xwarn(_("opening pid %ld failed"), procs[i].num); + continue; + } + ev.events = EPOLLIN | EPOLLET; + ev.data.fd = pidfd; + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, pidfd, &ev) != -1) + poll_count++; + } + + while (wait_count < poll_count) { + int ew = epoll_wait(epollfd, events, sizeof(events)/sizeof(events[0]), -1); + if (ew == -1) { + if (errno == EINTR) + continue; + xwarn(_("epoll_wait failed")); + } + wait_count += ew; + } + + return !wait_count; +#endif + } + /* Not sure if it is possible to get here */ + return -1; +} diff --git a/src/pidof.c b/src/pidof.c new file mode 100644 index 0000000..abf9fae --- /dev/null +++ b/src/pidof.c @@ -0,0 +1,420 @@ +/* + * pidof.c - Utility for listing pids of running processes + * + * Copyright © 2013-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2016-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2015-2020 Jan Rybar <jrybar@redhat.com> + * Copyright © 2013 Jaromir Capik <jcapik@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <limits.h> +#include <sys/types.h> + +#include "c.h" +#include "fileutils.h" +#include "nls.h" +#include "xalloc.h" + +#include "pids.h" + + +#define grow_size(x) do { \ + if ((x) < 0 || (size_t)(x) >= INT_MAX / 5 / sizeof(struct el)) \ + xerrx(EXIT_FAILURE, _("integer overflow")); \ + (x) = (x) * 5 / 4 + 1024; \ +} while (0) + +#define safe_free(x) if (x) { free(x); x=NULL; } + + +struct el { + pid_t pid; +}; + +struct el *procs = NULL; +static int proc_count = 0; + +struct el *omitted_procs = NULL; +static int omit_count = 0; + +static char *program = NULL; + +/* switch flags */ +static int opt_single_shot = 0; /* -s */ +static int opt_scripts_too = 0; /* -x */ +static int opt_rootdir_check = 0; /* -c */ +static int opt_with_workers = 0; /* -w */ +static int opt_threads = 0; /* -t */ +static int opt_quiet = 0; /* -q */ + +static char *pidof_root = NULL; + +static int __attribute__ ((__noreturn__)) usage(int opt) +{ + int err = (opt == '?'); + FILE *fp = err ? stderr : stdout; + + fputs(USAGE_HEADER, fp); + fprintf(fp, _(" %s [options] [program [...]]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, fp); + fputs(_(" -s, --single-shot return one PID only\n"), fp); + fputs(_(" -c, --check-root omit processes with different root\n"), fp); + fputs(_(" -q, quiet mode, only set the exit code\n"), fp); + fputs(_(" -w, --with-workers show kernel workers too\n"), fp); + fputs(_(" -x also find shells running the named scripts\n"), fp); + fputs(_(" -o, --omit-pid <PID,...> omit processes with PID\n"), fp); + fputs(_(" -t, --lightweight list threads too\n"), fp); + fputs(_(" -S, --separator SEP use SEP as separator put between PIDs"), fp); + fputs(USAGE_SEPARATOR, fp); + fputs(USAGE_HELP, fp); + fputs(USAGE_VERSION, fp); + fprintf(fp, USAGE_MAN_TAIL("pidof(1)")); + + exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + + +static int is_omitted (pid_t pid) +{ + int i; + + for (i = 0; i < omit_count; i++) { + if (pid == omitted_procs[i].pid) return 1; + } + + return 0; +} + + +static char *get_basename (char *filename) +{ + char *pos; + char *result; + + pos = result = filename; + while (*pos != '\0') { + if (*(pos++) == '/') result = pos; + } + + return result; +} + + +static char *pid_link (pid_t pid, const char *base_name) +{ + char link [1000]; + char *result; + ssize_t path_alloc_size; + ssize_t len; + + snprintf(link, sizeof(link), "/proc/%d/%s", pid, base_name); + + len = path_alloc_size = 0; + result = NULL; + do { + grow_size(path_alloc_size); + result = xrealloc(result, path_alloc_size); + + if ((len = readlink(link, result, path_alloc_size)) < 0) { + len = 0; + break; + } + + } while (len == path_alloc_size); + + result[len] = '\0'; + + return result; +} + + +static void select_procs (void) +{ + enum pids_item items[] = { PIDS_ID_PID, PIDS_CMD, PIDS_CMDLINE_V }; + enum rel_items { rel_pid, rel_cmd, rel_cmdline }; + struct pids_info *info = NULL; + struct pids_stack *stack; + int match; + static int size = 0; + char *cmd_arg0, *cmd_arg0base; + char *cmd_arg1, *cmd_arg1base; + char *program_base; + char *root_link; + char *exe_link; + char *exe_link_base; + + /* get the input base name */ + program_base = get_basename(program); + + procps_pids_new(&info, items, 3); + + exe_link = root_link = NULL; + while ((stack = procps_pids_get(info, (opt_threads + ? PIDS_FETCH_THREADS_TOO + : PIDS_FETCH_TASKS_ONLY)))) { + char *p_cmd = PIDS_VAL(rel_cmd, str, stack, info), + **p_cmdline = PIDS_VAL(rel_cmdline, strv, stack, info); + int tid = PIDS_VAL(rel_pid, s_int, stack, info); + + if (opt_rootdir_check) { + /* get the /proc/<pid>/root symlink value */ + root_link = pid_link(tid, "root"); + match = !strcmp(pidof_root, root_link); + safe_free(root_link); + + if (!match) { /* root check failed */ + continue; + } + } + + if (!is_omitted(tid) && ((p_cmdline && *p_cmdline) || opt_with_workers)) { + + cmd_arg0 = (p_cmdline && *p_cmdline) ? *p_cmdline : "\0"; + + /* processes starting with '-' are login shells */ + if (*cmd_arg0 == '-') { + cmd_arg0++; + } + + /* get the argv0 base name */ + cmd_arg0base = get_basename(cmd_arg0); + + /* get the /proc/<pid>/exe symlink value */ + exe_link = pid_link(tid, "exe"); + + /* get the exe_link base name */ + exe_link_base = get_basename(exe_link); + + match = 0; + + if (!strcmp(program, cmd_arg0base) || + !strcmp(program_base, cmd_arg0) || + !strcmp(program, cmd_arg0) || + (opt_with_workers && !strcmp(program, p_cmd)) || + !strcmp(program, exe_link_base) || + !strcmp(program, exe_link)) + { + match = 1; + + } else if (opt_scripts_too && p_cmdline && *(p_cmdline+1)) { + + cmd_arg1 = *(p_cmdline+1); + + /* get the arg1 base name */ + cmd_arg1base = get_basename(cmd_arg1); + + /* if script, then cmd = argv1, otherwise cmd = argv0 */ + if (p_cmd && + !strncmp(p_cmd, cmd_arg1base, strlen(p_cmd)) && + (!strcmp(program, cmd_arg1base) || + !strcmp(program_base, cmd_arg1) || + !strcmp(program, cmd_arg1))) + { + match = 1; + } + } + /* If there is a space in arg0 then process probably has + * setproctitle so use the cmdline + */ + if (!match && strchr(cmd_arg0, ' ')) { + match = (strcmp(program, p_cmd)==0); + } + + safe_free(exe_link); + + if (match) { + if (proc_count == size) { + grow_size(size); + procs = xrealloc(procs, size * (sizeof *procs)); + } + if (procs) { + procs[proc_count++].pid = tid; + } else { + xerrx(EXIT_FAILURE, _("internal error")); + } + } + + } + + } + + procps_pids_unref(&info); +} + + +static void add_to_omit_list (char *input_arg) +{ + static int omit_size = 0; + + char *omit_str; + char *endptr; + + pid_t omit_pid; + + omit_str = NULL; + omit_str = strtok(input_arg, ",;:"); + while (omit_str) { + + if (!strcmp(omit_str,"%PPID")) { /* keeping this %PPID garbage for backward compatibility only */ + omit_pid = getppid(); /* ... as it can be replaced with $$ in common shells */ + endptr = omit_str + sizeof("%PPID") - 1; + } else { + omit_pid = strtoul(omit_str, &endptr, 10); + } + + if (*endptr == '\0') { + if (omit_count == omit_size) { + grow_size(omit_size); + omitted_procs = xrealloc(omitted_procs, omit_size * sizeof(*omitted_procs)); + } + if (omitted_procs) { + omitted_procs[omit_count++].pid = omit_pid; + } else { + xerrx(EXIT_FAILURE, _("internal error")); + } + } else { + xwarnx(_("illegal omit pid value (%s)!\n"), omit_str); + } + + omit_str = strtok(NULL, ",;:"); + } +} + + + +int main (int argc, char **argv) +{ + int opt; + signed int i; + int found = 0; + int first_pid = 1; + + const char *separator = " "; + const char *opts = "scnqxwtmo:S:?Vh"; + + static const struct option longopts[] = { + {"check-root", no_argument, NULL, 'c'}, + {"single-shot", no_argument, NULL, 's'}, + {"omit-pid", required_argument, NULL, 'o'}, + {"separator", required_argument, NULL, 'S'}, + {"quiet", no_argument, NULL, 'q'}, + {"with-workers", no_argument, NULL, 'w'}, + {"lightweight", no_argument, NULL, 't'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + atexit (close_stdout); + + /* process command-line options */ + while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != -1) { + switch (opt) { + case 'q': + opt_quiet = 1; + /* fallthrough */ + case 's': + opt_single_shot = 1; + break; + case 'o': + add_to_omit_list (optarg); + break; + case 'x': + opt_scripts_too = 1; + break; + case 'w': + opt_with_workers = 1; + break; + case 'c': + if (geteuid() == 0) { + opt_rootdir_check = 1; + safe_free(pidof_root); + pidof_root = pid_link(getpid(), "root"); + } + break; + case 't': // Linux: show threads (lightweight process) too + opt_threads = 1; + break; + case 'd': /* sysv pidof uses this for S */ + case 'S': + separator = optarg; + break; + case 'V': + printf (PROCPS_NG_VERSION); + exit (EXIT_SUCCESS); + case 'h': + case '?': + usage (opt); + break; + /* compatibility-only switches */ + case 'n': /* avoiding stat(2) on NFS volumes doesn't make any sense anymore ... */ + /* ... as this reworked solution does not use stat(2) at all */ + case 'm': /* omitting relatives with argv[0] & argv[1] matching the argv[0] & argv[1] ...*/ + /* ... of explicitly omitted PIDs is too 'expensive' and as we don't know */ + /* ... wheter it is still needed, we won't re-implement it unless ... */ + /* ... somebody gives us a good reason to do so :) */ + break; + } + } + + /* main loop */ + while (argc - optind) { /* for each program */ + + program = argv[optind++]; + + if (*program == '\0') continue; + + select_procs(); /* get the list of matching processes */ + + if (proc_count) { + + found = 1; + for (i = proc_count - 1; i >= 0; i--) { /* and display their PIDs */ + if (!opt_quiet) { + if (first_pid) { + first_pid = 0; + printf ("%ld", (long) procs[i].pid); + } else { + printf ("%s%ld", separator, (long) procs[i].pid); + } + } + if (opt_single_shot) break; + } + + proc_count = 0; + } + } + + /* final line feed */ + if (!opt_quiet && found) printf("\n"); + + /* some cleaning */ + safe_free(procs); + safe_free(omitted_procs); + safe_free(pidof_root); + + return !found; +} diff --git a/src/pmap.c b/src/pmap.c new file mode 100644 index 0000000..ae57fd2 --- /dev/null +++ b/src/pmap.c @@ -0,0 +1,1217 @@ +/* + * pmap.c - print process memory mapping + * + * Copyright © 2010-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2002-2009 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <assert.h> +#include <limits.h> + +#include "c.h" +#include "fileutils.h" +#include "nls.h" +#include "xalloc.h" + +#include "pids.h" + +static struct pids_info *Pids_info; + +enum pids_item Pid_items[] = { + PIDS_ID_PID, PIDS_ID_TGID, + PIDS_CMDLINE, PIDS_ADDR_STACK_START }; +enum rel_items { pid, tgid, cmdline, start_stack }; + +const char *nls_Address, + *nls_Offset, + *nls_Device, + *nls_Mapping, + *nls_Perm, + *nls_Inode, + *nls_Kbytes, + *nls_Mode, + *nls_RSS, + *nls_Dirty; + +static void nls_initialize(void) +{ + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + /* these are the headings shared across all options, + though their widths when output might differ */ + nls_Address = _("Address"); + nls_Offset = _("Offset"); + nls_Device = _("Device"); + nls_Mapping = _("Mapping"); + + /* these headings are used only by the -X/-XX options, + and are not derived literally from /proc/#/smaps */ + nls_Perm = _("Perm"); + nls_Inode = _("Inode"); + + /* these are potentially used for options other than -X/-XX, */ + nls_Kbytes = _("Kbytes"); + nls_Mode = _("Mode"); + nls_RSS = _("RSS"); + nls_Dirty = _("Dirty"); +} + +static int justify_print(const char *str, int width, int right) +{ + if (width < 1) + puts(str); + else { + int len = strlen(str); + if (width < len) width = len; + printf(right ? "%*.*s " : "%-*.*s ", width, width, str); + } + return width; +} + +static int integer_width(unsigned long number) +{ + int result; + + result = !(number > 0); + while (number) { + result++; + number /= 10; + } + + return result; +} + + +static void __attribute__ ((__noreturn__)) +usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] PID [PID ...]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -x, --extended show details\n"), out); + fputs(_(" -X show even more details\n"), out); + fputs(_(" WARNING: format changes according to /proc/PID/smaps\n"), out); + fputs(_(" -XX show everything the kernel provides\n"), out); + fputs(_(" -c, --read-rc read the default rc\n"), out); + fputs(_(" -C, --read-rc-from=<file> read the rc from file\n"), out); + fputs(_(" -n, --create-rc create new default rc\n"), out); + fputs(_(" -N, --create-rc-to=<file> create new rc to file\n"), out); + fputs(_(" NOTE: pid arguments are not allowed with -n, -N\n"), out); + fputs(_(" -d, --device show the device format\n"), out); + fputs(_(" -q, --quiet do not display header and footer\n"), out); + fputs(_(" -p, --show-path show path in the mapping\n"), out); + fputs(_(" -A, --range=<low>[,<high>] limit results to the given range\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("pmap(1)")); + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static char mapbuf[1024]; + +static unsigned long range_low; +static unsigned long range_high = ~0ul; + +static int c_option = 0; +static int C_option = 0; +static int d_option = 0; +static int n_option = 0; +static int N_option = 0; +static int q_option = 0; +static int x_option = 0; +static int X_option = 0; + +static int map_desc_showpath; + +static unsigned shm_minor = ~0u; + +static void discover_shm_minor(void) +{ + void *addr; + int shmid; + char mapbuf_b[256]; + FILE *fp; + + if ( (fp = fopen("/proc/self/maps", "r")) == NULL) + return; + + /* create */ + shmid = shmget(IPC_PRIVATE, 42, IPC_CREAT | 0666); + if (shmid == -1) + { + /* failed; oh well */ + fclose(fp); + return; + } + /* attach */ + addr = shmat(shmid, NULL, SHM_RDONLY); + if (addr == (void *)-1) + goto out_destroy; + + while (fgets(mapbuf_b, sizeof mapbuf_b, fp)) { + char perms[32]; + /* to clean up unprintables */ + char *tmp; + unsigned long start, end; + unsigned long long file_offset, inode; + unsigned dev_major, dev_minor; + if (sscanf(mapbuf_b, "%lx-%lx %31s %llx %x:%x %llu", &start, + &end, perms, &file_offset, &dev_major, &dev_minor, &inode) < 6) + continue; + tmp = strchr(mapbuf_b, '\n'); + if (tmp) + *tmp = '\0'; + tmp = mapbuf_b; + while (*tmp) { + if (!isprint(*tmp)) + *tmp = '?'; + tmp++; + } + if (start > (unsigned long)addr) + continue; + if (dev_major) + continue; + if (perms[3] != 's') + continue; + if (strstr(mapbuf_b, "/SYSV")) { + shm_minor = dev_minor; + break; + } + } + + if (shmdt(addr)) + perror(_("shared memory detach")); + +out_destroy: + fclose(fp); + if (shmctl(shmid, IPC_RMID, NULL) && errno != EINVAL) + perror(_("shared memory remove")); + + return; +} + +static const char *mapping_name(struct pids_stack *p, unsigned long addr, + unsigned long len, const char *mapbuf_b, + unsigned showpath, unsigned dev_major, + unsigned dev_minor, unsigned long long inode) +{ + const char *cp; + + if (!dev_major && dev_minor == shm_minor && strstr(mapbuf_b, "/SYSV")) { + static char shmbuf[64]; + snprintf(shmbuf, sizeof shmbuf, " [ shmid=0x%llx ]", inode); + return shmbuf; + } + + cp = strrchr(mapbuf_b, '/'); + if (cp) { + if (showpath) + return strchr(mapbuf_b, '/'); + return cp[1] ? cp + 1 : cp; + } + + cp = _(" [ anon ]"); + if (PIDS_VAL(start_stack, ul_int, p, Pids_info) >= addr + && (PIDS_VAL(start_stack, ul_int, p, Pids_info) <= addr + len)) + cp = _(" [ stack ]"); + return cp; +} + + +#define DETAIL_LENGTH 32 +#define DETL "31" /* for format strings */ +#define NUM_LENGTH 21 /* python says: len(str(2**64)) == 20 */ +#define NUML "20" /* for format strings */ +#define VMFLAGS_LENGTH 128 /* 30 2-char space-separated flags == 90+1, but be safe */ +#define VMFL "127" /* for format strings */ + +struct listnode { + char description[DETAIL_LENGTH]; + char value_str[NUM_LENGTH]; + unsigned long value; + unsigned long total; + int max_width; + struct listnode *next; +}; + +static struct listnode *listhead=NULL, *listtail=NULL, *listnode; + + +struct cnf_listnode { + char description[DETAIL_LENGTH]; + struct cnf_listnode *next; +}; + +static struct cnf_listnode *cnf_listhead=NULL, *cnf_listnode; + +static int is_unimportant (const char *s) +{ + if (strcmp(s, "AnonHugePages") == 0) return 1; + if (strcmp(s, "KernelPageSize") == 0) return 1; + if (strcmp(s, "MMUPageSize") == 0) return 1; + if (strcmp(s, "Shared_Dirty") == 0) return 1; + if (strcmp(s, "Private_Dirty") == 0) return 1; + if (strcmp(s, "Shared_Clean") == 0) return 1; + if (strcmp(s, "Private_Clean") == 0) return 1; + if (strcmp(s, "VmFlags") == 0) return 1; + return 0; +} + +/* check, whether we want to display the field or not */ +static int is_enabled (const char *s) +{ + if (X_option == 1) return !is_unimportant(s); + + if (c_option) { /* taking the list of disabled fields from the rc file */ + + for (cnf_listnode = cnf_listhead; cnf_listnode; cnf_listnode = cnf_listnode -> next) { + if (!strcmp(s, cnf_listnode -> description)) return 1; + } + return 0; + + } + + return 1; +} + + +static void print_extended_maps (FILE *f) +{ + char perms[DETAIL_LENGTH], map_desc[128], + detail_desc[DETAIL_LENGTH], value_str[NUM_LENGTH], + start[NUM_LENGTH], end[NUM_LENGTH], + offset[NUM_LENGTH], inode[NUM_LENGTH], + dev[64], vmflags[VMFLAGS_LENGTH]; + int maxw1=0, maxw2=0, maxw3=0, maxw4=0, maxw5=0, maxwv=0; + int nfields, firstmapping, footer_gap, i, maxw_; + char *ret, *map_basename, c, has_vmflags = 0; + + ret = fgets(mapbuf, sizeof mapbuf, f); + firstmapping = 2; + while (ret != NULL) { + /* === READ MAPPING === */ + map_desc[0] = '\0'; + nfields = sscanf(mapbuf, + "%"NUML"[0-9a-f]-%"NUML"[0-9a-f] " + "%"DETL"s %"NUML"[0-9a-f] " + "%63[0-9a-f:] %"NUML"s %127[^\n]", + start, end, perms, offset, + dev, inode, map_desc); + /* Must read at least up to inode, else something has changed! */ + if (nfields < 6) + xerrx(EXIT_FAILURE, _("Unknown format in smaps file!")); + /* If line too long we dump everything else. */ + c = mapbuf[strlen(mapbuf) - 1]; + while (c != '\n') { + fgets(mapbuf, sizeof mapbuf, f); + if (!ret || !mapbuf[0]) + xerrx(EXIT_FAILURE, _("Unknown format in smaps file!")); + c = mapbuf[strlen(mapbuf) - 1]; + } + + /* Store maximum widths for printing nice later */ + if (strlen(start ) > maxw1) maxw1 = strlen(start); + if (strlen(perms ) > maxw2) maxw2 = strlen(perms); + if (strlen(offset) > maxw3) maxw3 = strlen(offset); + if (strlen(dev ) > maxw4) maxw4 = strlen(dev); + if (strlen(inode ) > maxw5) maxw5 = strlen(inode); + + ret = fgets(mapbuf, sizeof mapbuf, f); + nfields = ret ? sscanf(mapbuf, "%"DETL"[^:]: %"NUML"[0-9] kB %c", + detail_desc, value_str, &c) : 0; + listnode = listhead; + /* === READ MAPPING DETAILS === */ + while (ret != NULL && nfields == 2) { + + if (!is_enabled(detail_desc)) goto loop_end; + + /* === CREATE LIST AND FILL description FIELD === */ + if (listnode == NULL) { + assert(firstmapping == 2); + listnode = calloc(1, sizeof *listnode); + if (listnode == NULL) + xerrx(EXIT_FAILURE, _("ERROR: memory allocation failed")); + + if (listhead == NULL) { + assert(listtail == NULL); + listhead = listnode; + } else { + listtail->next = listnode; + } + listtail = listnode; + /* listnode was calloc()ed so all fields are already NULL! */ + strcpy(listnode->description, detail_desc); + if (!q_option) listnode->max_width = strlen(detail_desc); + else listnode->max_width = 0; + } else { + /* === LIST EXISTS === */ + if (strcmp(listnode->description, detail_desc) != 0) + xerrx(EXIT_FAILURE, "ERROR: %s %s", + _("inconsistent detail field in smaps file, line:\n"), + mapbuf); + } + strcpy(listnode->value_str, value_str); + sscanf(value_str, "%lu", &listnode->value); + if (firstmapping == 2) { + listnode->total += listnode->value; + if (q_option) { + maxw_ = strlen(listnode->value_str); + if (maxw_ > listnode->max_width) + listnode->max_width = maxw_; + } + } + listnode = listnode->next; +loop_end: + ret = fgets(mapbuf, sizeof mapbuf, f); + nfields = ret ? sscanf(mapbuf, "%"DETL"[^:]: %"NUML"[0-9] kB %c", + detail_desc, value_str, &c) : 0; + } + + /* === GET VMFLAGS === */ + nfields = ret ? sscanf(mapbuf, "VmFlags: %"VMFL"[a-z ]", vmflags) : 0; + if (nfields == 1) { + int len = strlen(vmflags); + if (len > 0 && vmflags[len-1] == ' ') vmflags[--len] = '\0'; + if (len > maxwv) maxwv = len; + if (! has_vmflags) has_vmflags = 1; + ret = fgets(mapbuf, sizeof mapbuf, f); + } + + if (firstmapping == 2) { /* width measurement stage, do not print anything yet */ + if (ret == NULL) { /* once the end of file is reached ...*/ + firstmapping = 1; /* ... we reset the file position to the beginning of the file */ + fseek(f, 0, SEEK_SET); /* ... and repeat the process with printing enabled */ + ret = fgets(mapbuf, sizeof mapbuf, f); /* this is not ideal and needs to be redesigned one day */ + + if (!q_option) { + /* calculate width of totals */ + for (listnode=listhead; listnode!=NULL; listnode=listnode->next) { + maxw_ = integer_width(listnode->total); + if (maxw_ > listnode->max_width) + listnode->max_width = maxw_; + } + } + + } + } else { /* the maximum widths have been measured, we've already reached the printing stage */ + /* === PRINT THIS MAPPING === */ + + /* Print header */ + if (firstmapping && !q_option) { + + maxw1 = justify_print(nls_Address, maxw1, 1); + + if (is_enabled(nls_Perm)) + maxw2 = justify_print(nls_Perm, maxw2, 1); + + if (is_enabled(nls_Offset)) + maxw3 = justify_print(nls_Offset, maxw3, 1); + + if (is_enabled(nls_Device)) + maxw4 = justify_print(nls_Device, maxw4, 1); + + if (is_enabled(nls_Inode)) + maxw5 = justify_print(nls_Inode, maxw5, 1); + + for (listnode=listhead; listnode!=NULL; listnode=listnode->next) + justify_print(listnode->description, listnode->max_width, 1); + + if (has_vmflags && is_enabled("VmFlags")) + maxwv = justify_print("VmFlags", maxwv, 1); + + if (is_enabled(nls_Mapping)) + justify_print(nls_Mapping, 0, 0); + else + printf("\n"); + } + + /* Print data */ + printf("%*s", maxw1, start); /* Address field is always enabled */ + + if (is_enabled(nls_Perm)) + printf(" %*s", maxw2, perms); + + if (is_enabled(nls_Offset)) + printf(" %*s", maxw3, offset); + + if (is_enabled(nls_Device)) + printf(" %*s", maxw4, dev); + + if (is_enabled(nls_Inode)) + printf(" %*s", maxw5, inode); + + for (listnode=listhead; listnode!=NULL; listnode=listnode->next) + printf(" %*s", listnode->max_width, listnode->value_str); + + if (has_vmflags && is_enabled("VmFlags")) + printf(" %*s", maxwv, vmflags); + + if (is_enabled(nls_Mapping)) { + if (map_desc_showpath) { + printf(" %s", map_desc); + } else { + map_basename = strrchr(map_desc, '/'); + if (!map_basename) { + printf(" %s", map_desc); + } else { + printf(" %s", map_basename + 1); + } + + } + } + + printf("\n"); + + firstmapping = 0; + + } + } + /* === PRINT TOTALS === */ + if (!q_option && listhead!=NULL) { /* footer enabled and non-empty */ + + footer_gap = maxw1 + 1; /* Address field is always enabled */ + if (is_enabled(nls_Perm )) footer_gap += maxw2 + 1; + if (is_enabled(nls_Offset)) footer_gap += maxw3 + 1; + if (is_enabled(nls_Device)) footer_gap += maxw4 + 1; + if (is_enabled(nls_Inode )) footer_gap += maxw5 + 1; + + for (i=0; i<footer_gap; i++) putc(' ', stdout); + + for (listnode=listhead; listnode!=NULL; listnode=listnode->next) { + for (i=0; i<listnode->max_width; i++) + putc('=', stdout); + putc(' ', stdout); + } + + putc('\n', stdout); + + for (i=0; i<footer_gap; i++) putc(' ', stdout); + + for (listnode=listhead; listnode!=NULL; listnode=listnode->next) { + printf("%*lu ", listnode->max_width, listnode->total); + listnode->total = 0; + } + + fputs("KB \n", stdout); + } + /* We don't free() the list, it's used for all PIDs passed as arguments */ +} + + // variable placed here to silence compiler 'uninitialized' warning +static unsigned long start_To_Avoid_Warning; + +static int one_proc (struct pids_stack *p) +{ + char buf[32]; + FILE *fp; + unsigned long total_shared = 0ul; + unsigned long total_private_readonly = 0ul; + unsigned long total_private_writeable = 0ul; + unsigned long diff = 0; + unsigned long end; + char perms[32] = ""; + const char *cp2 = NULL; + unsigned long long rss = 0ull; + unsigned long long private_dirty = 0ull; + unsigned long long shared_dirty = 0ull; + unsigned long long total_rss = 0ull; + unsigned long long total_private_dirty = 0ull; + unsigned long long total_shared_dirty = 0ull; + int maxw1=0, maxw2=0, maxw3=0, maxw4=0, maxw5=0; + + printf("%u: %s\n", PIDS_VAL(tgid, s_int, p, Pids_info), PIDS_VAL(cmdline, str, p, Pids_info)); + + if (x_option || X_option || c_option) { + snprintf(buf, sizeof buf, "/proc/%u/smaps", PIDS_VAL(tgid, s_int, p, Pids_info)); + if ((fp = fopen(buf, "r")) == NULL) + return 1; + } else { + snprintf(buf, sizeof buf, "/proc/%u/maps", PIDS_VAL(tgid, s_int, p, Pids_info)); + if ((fp = fopen(buf, "r")) == NULL) + return 1; + } + + if (X_option || c_option) { + print_extended_maps(fp); + fclose(fp); + return 0; + } + + if (x_option) { + maxw1 = 16; + if (sizeof(long) == 4) maxw1 = 8; + maxw2 = maxw3 = maxw4 = 7; + maxw5 = 5; + if (!q_option) { + maxw1 = justify_print(nls_Address, maxw1, 0); + maxw2 = justify_print(nls_Kbytes, maxw2, 1); + maxw3 = justify_print(nls_RSS, maxw3, 1); + maxw4 = justify_print(nls_Dirty, maxw4, 1); + maxw5 = justify_print(nls_Mode, maxw5, 0); + justify_print(nls_Mapping, 0, 0); + } + } + + if (d_option) { + maxw1 = 16; + if (sizeof(long) == 4) maxw1 = 8; + maxw2 = 7; + maxw3 = 5; + maxw4 = 16; + maxw5 = 9; + if (!q_option) { + maxw1 = justify_print(nls_Address, maxw1, 0); + maxw2 = justify_print(nls_Kbytes, maxw2, 1); + maxw3 = justify_print(nls_Mode, maxw3, 0); + maxw4 = justify_print(nls_Offset, maxw4, 0); + maxw5 = justify_print(nls_Device, maxw5, 0); + justify_print(nls_Mapping, 0, 0); + } + } + + while (fgets(mapbuf, sizeof mapbuf, fp)) { + /* to clean up unprintables */ + char *tmp; + unsigned long long file_offset, inode; + unsigned dev_major, dev_minor; + unsigned long long smap_value; + char smap_key[21]; + + /* hex values are lower case or numeric, keys are upper */ + if (mapbuf[0] >= 'A' && mapbuf[0] <= 'Z') { + /* Its a key */ + if (sscanf(mapbuf, "%20[^:]: %llu", smap_key, &smap_value) == 2) { + if (strcmp("Rss", smap_key) == 0) { + rss = smap_value; + total_rss += smap_value; + continue; + } + if (strcmp("Shared_Dirty", smap_key) == 0) { + shared_dirty = smap_value; + total_shared_dirty += smap_value; + continue; + } + if (strcmp("Private_Dirty", smap_key) == 0) { + private_dirty = smap_value; + total_private_dirty += smap_value; + continue; + } + if (strcmp("Swap", smap_key) == 0) { + /* doesn't matter as long as last */ + if (cp2) + printf("%0*lx %*lu %*llu %*llu %*s %s\n", + maxw1, start_To_Avoid_Warning, + maxw2, (unsigned long)(diff >> 10), + maxw3, rss, + maxw4, (private_dirty + shared_dirty), + maxw5, perms, + cp2); + /* reset some counters */ + rss = shared_dirty = private_dirty = 0ull; + diff = end = 0; + perms[0] = '\0'; + cp2 = NULL; + continue; + } + } + /* Other keys or not a key-value pair */ + continue; + } + sscanf(mapbuf, "%lx-%lx %31s %llx %x:%x %llu", + &start_To_Avoid_Warning, + &end, perms, &file_offset, &dev_major, &dev_minor, + &inode); + + if (end - 1 < range_low) + continue; + if (range_high < start_To_Avoid_Warning) + break; + + tmp = strchr(mapbuf, '\n'); + if (tmp) + *tmp = '\0'; + tmp = mapbuf; + while (*tmp) { + if (!isprint(*tmp)) + *tmp = '?'; + tmp++; + } + + diff = end - start_To_Avoid_Warning; + if (perms[3] == 's') + total_shared += diff; + if (perms[3] == 'p') { + perms[3] = '-'; + if (perms[1] == 'w') + total_private_writeable += diff; + else + total_private_readonly += diff; + } + /* format used by Solaris 9 and procps-3.2.0+ an 'R' + * if swap not reserved (MAP_NORESERVE, SysV ISM + * shared mem, etc.) + */ + perms[4] = '-'; + perms[5] = '\0'; + + if (x_option) { + cp2 = + mapping_name(p, start_To_Avoid_Warning, diff, mapbuf, map_desc_showpath, dev_major, + dev_minor, inode); + /* printed with the keys */ + continue; + } + if (d_option) { + const char *cp = + mapping_name(p, start_To_Avoid_Warning, diff, mapbuf, map_desc_showpath, dev_major, + dev_minor, inode); + printf("%0*lx %*lu %*s %0*llx %*.*s%03x:%05x %s\n", + maxw1, start_To_Avoid_Warning, + maxw2, (unsigned long)(diff >> 10), + maxw3, perms, + maxw4, file_offset, + (maxw5-9), (maxw5-9), " ", dev_major, dev_minor, + cp); + } + if (!x_option && !d_option) { + const char *cp = + mapping_name(p, start_To_Avoid_Warning, diff, mapbuf, map_desc_showpath, dev_major, + dev_minor, inode); + printf((sizeof(long) == 8) + ? "%016lx %6luK %s %s\n" + : "%08lx %6luK %s %s\n", + start_To_Avoid_Warning, (unsigned long)(diff >> 10), perms, cp); + } + + } + fclose(fp); + if (!q_option) { + if (x_option) { + if (sizeof(long) == 4) + justify_print("--------", maxw1, 0); + else + justify_print("----------------", maxw1, 0); + justify_print("-------", maxw2, 1); + justify_print("-------", maxw3, 1); + justify_print("-------", maxw4, 1); + printf("\n"); + + printf("%-*s ", maxw1, _("total kB")); + printf("%*ld %*llu %*llu\n", + maxw2, (total_shared + + total_private_writeable + + total_private_readonly) >> 10, + maxw3, total_rss, + maxw4, (total_shared_dirty + + total_private_dirty)); + } + if (d_option) { + printf + (_("mapped: %ldK writeable/private: %ldK shared: %ldK\n"), + (total_shared + total_private_writeable + + total_private_readonly) >> 10, + total_private_writeable >> 10, total_shared >> 10); + } + if (!x_option && !d_option) { + if (sizeof(long) == 8) + /* Translation Hint: keep total string length + * as 24 characters. Adjust %16 if needed*/ + printf(_(" total %16ldK\n"), + (total_shared + total_private_writeable + + total_private_readonly) >> 10); + else + /* Translation Hint: keep total string length + * as 16 characters. Adjust %8 if needed*/ + printf(_(" total %8ldK\n"), + (total_shared + total_private_writeable + + total_private_readonly) >> 10); + } + } + + return 0; +} + +static void range_arguments(const char *optarg) +{ + char *buf, *arg1, *arg2; + + if ((buf = xstrdup(optarg)) == NULL) { + xerrx(EXIT_FAILURE, "%s: '%s'", _("failed to parse argument"), + (optarg?optarg:"(null)")); + } + arg1 = buf; + arg2 = strchr(arg1, ','); + if (arg2) + *arg2++ = '\0'; + else + arg2 = arg1; + if (arg1[0] != '\0') + range_low = strtoul(arg1, &arg1, 16); + if (arg2[0] != '\0') + range_high = strtoul(arg2, &arg2, 16); + if (*arg1 || *arg2) { + free(buf); + xerrx(EXIT_FAILURE, "%s: '%s'", _("failed to parse argument"), + optarg); + } + free(buf); +} + + +#define MAX_CNF_LINE_LEN 1024 + +#define SECTION_ID_NONE 0 +#define SECTION_ID_UNSUPPORTED 1 + +#define SECTION_STR_FIELDS_DISPLAY "[Fields Display]" +#define SECTION_STR_FIELDS_DISPLAY_LEN (sizeof(SECTION_STR_FIELDS_DISPLAY) - 1) +#define SECTION_ID_FIELDS_DISPLAY 2 + +#define SECTION_STR_MAPPING "[Mapping]" +#define SECTION_STR_MAPPING_LEN (sizeof(SECTION_STR_MAPPING) - 1) +#define SECTION_ID_MAPPING 3 + +static int config_read (char *rc_filename) +{ + FILE *f; + char line_buf[MAX_CNF_LINE_LEN + 1]; + char tmp_buf [MAX_CNF_LINE_LEN + 1]; + char *trimmed; + int length; + char *tail, *token; + int line_cnt, section_id; + + f = fopen(rc_filename, "r"); + + if (!f) return 0; /* can't open the file for reading */ + + line_cnt = 0; + section_id = SECTION_ID_NONE; + + while (fgets (line_buf, MAX_CNF_LINE_LEN + 1, f)) { + + line_cnt++; + + /* get rid of the LF char */ + length = strlen(line_buf); + if (length > 0 && line_buf[length - 1] == '\n') { + line_buf[length - 1] = '\0'; + } else if (length == MAX_CNF_LINE_LEN) { /* no LF char -> line too long */ + xwarnx(_("config line too long - line %d"), line_cnt); + /* ignoring the tail */ + while (fgets (tmp_buf, MAX_CNF_LINE_LEN + 1, f) && + (length = strlen(tmp_buf))>0 && + tmp_buf[length - 1] != '\n') ; + } + + /* trim leading whitespaces */ + trimmed = line_buf; + while (*trimmed == ' ' || *trimmed == '\t') trimmed++; + + /* skip comments and empty lines */ + if (*trimmed == '#' || *trimmed == '\0') continue; + + if (*trimmed == '[') { /* section */ + if (!strncmp(trimmed, SECTION_STR_FIELDS_DISPLAY, SECTION_STR_FIELDS_DISPLAY_LEN)) { + trimmed += SECTION_STR_FIELDS_DISPLAY_LEN; + section_id = SECTION_ID_FIELDS_DISPLAY; + } else if (!strncmp(trimmed, SECTION_STR_MAPPING, SECTION_STR_MAPPING_LEN)) { + trimmed += SECTION_STR_MAPPING_LEN; + section_id = SECTION_ID_MAPPING; + } else { + while (*trimmed != ']' && *trimmed != '\0') trimmed++; + if (*trimmed == ']') { + section_id = SECTION_ID_UNSUPPORTED; + xwarnx(_("unsupported section found in the config - line %d"), line_cnt); + trimmed++; + } else { + xwarnx(_("syntax error found in the config - line %d"), line_cnt); + } + } + + /* trim trailing whitespaces */ + while (*trimmed == ' ' || *trimmed == '\t') trimmed++; + + /* skip comments and empty tails */ + if (*trimmed == '#' || *trimmed == '\0') continue; + + /* anything else found on the section line ??? */ + xwarnx(_("syntax error found in the config - line %d"), line_cnt); + } + + switch (section_id) { + case SECTION_ID_FIELDS_DISPLAY: + token = strtok (trimmed, " \t"); + + if (token) { + tail = strtok (NULL, " \t"); + + if (tail && *tail != '#') { + xwarnx(_("syntax error found in the config - line %d"), line_cnt); + } + + /* add the field in the list */ + if (!(cnf_listnode = calloc(1, sizeof *cnf_listnode))) { + xwarnx(_("memory allocation failed")); + fclose(f); + return 0; + } + snprintf(cnf_listnode -> description, sizeof(cnf_listnode -> description), "%s", token); + cnf_listnode -> next = cnf_listhead; + cnf_listhead = cnf_listnode; + } + + break; + + case SECTION_ID_MAPPING: + token = strtok (trimmed, " \t"); + + if (token) { + tail = strtok (NULL, " \t"); + + if (tail && *tail != '#') { + xwarnx(_("syntax error found in the config - line %d"), line_cnt); + } + + if (!strcmp(token,"ShowPath")) map_desc_showpath = !map_desc_showpath; + } + + break; + + case SECTION_ID_UNSUPPORTED: + break; /* ignore the content */ + + default: + xwarnx(_("syntax error found in the config - line %d"), line_cnt); + } + } + + fclose(f); + + return 1; +} + + +static int config_create (const char *rc_filename) +{ + FILE *f; + + if (rc_filename == NULL) + return 0; + + /* check if rc exists */ + f = fopen(rc_filename, "r"); + + if (f) { /* file exists ... let user to delete/remove it first */ + fclose(f); + xwarnx(_("the file already exists - delete or rename it first")); + return 0; + } + + /* file doesn't exist */ + + f = fopen(rc_filename, "w"); + + if (!f) return 0; /* can't open the file for writing */ + + /* current rc template, might change in the future */ + fprintf(f,"# pmap's Config File\n"); + fprintf(f,"\n"); + fprintf(f,"# All the entries are case sensitive.\n"); + fprintf(f,"# Unsupported entries are ignored!\n"); + fprintf(f,"\n"); + fprintf(f,"[Fields Display]\n"); + fprintf(f,"\n"); + fprintf(f,"# To enable a field uncomment its entry\n"); + fprintf(f,"\n"); + fprintf(f,"#%s\n", nls_Perm); + fprintf(f,"#%s\n", nls_Offset); + fprintf(f,"#%s\n", nls_Device); + fprintf(f,"#%s\n", nls_Inode); + fprintf(f,"#Size\n"); + fprintf(f,"#Rss\n"); + fprintf(f,"#Pss\n"); + fprintf(f,"#Shared_Clean\n"); + fprintf(f,"#Shared_Dirty\n"); + fprintf(f,"#Private_Clean\n"); + fprintf(f,"#Private_Dirty\n"); + fprintf(f,"#Referenced\n"); + fprintf(f,"#Anonymous\n"); + fprintf(f,"#AnonHugePages\n"); + fprintf(f,"#Swap\n"); + fprintf(f,"#KernelPageSize\n"); + fprintf(f,"#MMUPageSize\n"); + fprintf(f,"#Locked\n"); + fprintf(f,"#VmFlags\n"); + fprintf(f,"#%s\n", nls_Mapping); + fprintf(f,"\n"); + fprintf(f,"\n"); + fprintf(f,"[Mapping]\n"); + fprintf(f,"\n"); + fprintf(f,"# to show paths in the mapping column uncomment the following line\n"); + fprintf(f,"#ShowPath\n"); + fprintf(f,"\n"); + + fclose(f); + + return 1; +} + + +/* returns rc filename based on the program_invocation_short_name */ +static char *get_default_rc_filename(void) +{ + char *rc_filename; + int rc_filename_len; + const char *homedir; + + homedir = getenv("HOME"); + if (!homedir) { + xwarnx(_("HOME variable undefined")); + return NULL; + } + + rc_filename_len = snprintf(NULL, 0, "%s/.%src", homedir, program_invocation_short_name); + + rc_filename = (char *) calloc (1, rc_filename_len + 1); + if (!rc_filename) { + xwarnx(_("memory allocation failed")); + return NULL; + } + + snprintf(rc_filename, rc_filename_len + 1, "%s/.%src", homedir, program_invocation_short_name); + + return rc_filename; +} + + +int main(int argc, char **argv) +{ + struct pids_fetch *pids_fetch; + unsigned *pidlist; + int reap_count, user_count; + int ret = 0, c, conf_ret; + char *rc_filename = NULL; + + static const struct option longopts[] = { + {"extended", no_argument, NULL, 'x'}, + {"device", no_argument, NULL, 'd'}, + {"quiet", no_argument, NULL, 'q'}, + {"range", required_argument, NULL, 'A'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"read-rc", no_argument, NULL, 'c'}, + {"read-rc-from", required_argument, NULL, 'C'}, + {"create-rc", no_argument, NULL, 'n'}, + {"create-rc-to", required_argument, NULL, 'N'}, + {"show-path", no_argument, NULL, 'p'}, + {NULL, 0, NULL, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + nls_initialize(); + atexit(close_stdout); + + if (argc < 2) + usage(stderr); + + while ((c = getopt_long(argc, argv, "xXrdqA:hVcC:nN:p", longopts, NULL)) != -1) + switch (c) { + case 'x': + x_option = 1; + break; + case 'X': + X_option++; + break; + case 'r': + xwarnx(_("option -r is ignored as SunOS compatibility")); + break; + case 'd': + d_option = 1; + break; + case 'q': + q_option = 1; + break; + case 'A': + range_arguments(optarg); + break; + case 'h': + usage(stdout); + case 'V': + printf(PROCPS_NG_VERSION); + return EXIT_SUCCESS; + case 'c': + c_option = 1; + break; + case 'C': + C_option = 1; + rc_filename = optarg; + break; + case 'n': + n_option = 1; + break; + case 'N': + N_option = 1; + rc_filename = optarg; + break; + case 'p': + map_desc_showpath = 1; + break; + case 'a': /* Sun prints anon/swap reservations */ + case 'F': /* Sun forces hostile ptrace-like grab */ + case 'l': /* Sun shows unresolved dynamic names */ + case 'L': /* Sun shows lgroup info */ + case 's': /* Sun shows page sizes */ + case 'S': /* Sun shows swap reservations */ + default: + usage(stderr); + } + + argc -= optind; + argv += optind; + + if (c_option + C_option + d_option + n_option + N_option + x_option + !!X_option > 1) + xerrx(EXIT_FAILURE, _("options -c, -C, -d, -n, -N, -x, -X are mutually exclusive")); + + if ((n_option || N_option) && (q_option || map_desc_showpath)) + xerrx(EXIT_FAILURE, _("options -p, -q are mutually exclusive with -n, -N")); + + if ((n_option || N_option) && argc > 0) + xerrx(EXIT_FAILURE, _("too many arguments")); + + if (N_option) { + if (config_create(rc_filename)) { + xwarnx(_("rc file successfully created, feel free to edit the content")); + return (EXIT_SUCCESS); + } else { + xerrx(EXIT_FAILURE, _("couldn't create the rc file")); + } + } + + if (n_option) { + rc_filename = get_default_rc_filename(); + + if (!rc_filename) return(EXIT_FAILURE); + + conf_ret = config_create(rc_filename); free(rc_filename); + + if (conf_ret) { + xwarnx(_("~/.%src file successfully created, feel free to edit the content"), program_invocation_short_name); + return (EXIT_SUCCESS); + } else { + xerrx(EXIT_FAILURE, _("couldn't create ~/.%src"), program_invocation_short_name); + } + } + + if (argc < 1) + xerrx(EXIT_FAILURE, _("argument missing")); + + if (C_option) c_option = 1; + + if (c_option) { + if (!C_option) rc_filename = get_default_rc_filename(); + + if (!rc_filename) return(EXIT_FAILURE); + + conf_ret = config_read(rc_filename); + + if (!conf_ret) { + if (C_option) { + xerrx(EXIT_FAILURE, _("couldn't read the rc file")); + } else { + xwarnx(_("couldn't read ~/.%src"), program_invocation_short_name); + free(rc_filename); + return(EXIT_FAILURE); + } + } + } + if ((size_t)argc >= INT_MAX / sizeof(pid_t)) + xerrx(EXIT_FAILURE, _("too many arguments")); + if (procps_pids_new(&Pids_info, Pid_items, 4)) + xerrx(EXIT_FAILURE, _("library failed pids statistics")); + pidlist = xmalloc(sizeof(pid_t) * argc); + + user_count = 0; + while (*argv) { + char *walk = *argv++; + char *endp; + unsigned long pid; + if (!strncmp("/proc/", walk, 6)) { + walk += 6; + /* user allowed to do: pmap /proc/PID */ + if (*walk < '0' || *walk > '9') + continue; + } + if (*walk < '0' || *walk > '9') + usage(stderr); + pid = strtoul(walk, &endp, 0); + if (pid < 1ul || pid > 0x7ffffffful || *endp) + usage(stderr); + pidlist[user_count++] = pid; + } + + discover_shm_minor(); + + if (!(pids_fetch = procps_pids_select(Pids_info, pidlist, user_count, PIDS_SELECT_PID))) + xerrx(EXIT_FAILURE, _("library failed pids statistics")); + + for (reap_count = 0; reap_count < pids_fetch->counts->total; reap_count++) { + ret |= one_proc(pids_fetch->stacks[reap_count]); + } + + free(pidlist); + procps_pids_unref(&Pids_info); + + /* cleaning the list used for the -c/-X/-XX modes */ + for (listnode = listhead; listnode != NULL ; ) { + listnode = listnode -> next; + free(listhead); + listhead = listnode; + } + + /* cleaning the list used for the -c mode */ + for (cnf_listnode = cnf_listhead; cnf_listnode != NULL ; ) { + cnf_listnode = cnf_listnode -> next; + free(cnf_listhead); + cnf_listhead = cnf_listnode; + } + + if (reap_count < user_count) + /* didn't find all processes asked for */ + ret |= 42; + return ret; +} diff --git a/src/ps/HACKING b/src/ps/HACKING new file mode 100644 index 0000000..e7a6b4c --- /dev/null +++ b/src/ps/HACKING @@ -0,0 +1,46 @@ +Warning: + +This code must corrctly handle lots of picky little details to meet +the Unix98 standard while simultaneously being as compatible as +possible with the original Linux ps. Don't "fix" something without +considering the impact on all the special-case code. For example, +the "tty" format _must_ use "TT" as the header, even though the SysV +output formats _must_ use "TTY". + +File overview: + +display.c main(), debug code, iterates over processes +escape.c Does stuff like \202 and < to command and environment. +global.c Data + code to init it. +help.c Help message. +output.c Giant tables and lots of output functions. +parser.c Initial command parsing. +select.c want_this_proc() checks a process against flags & lists +sortformat.c Parses sort & format specifier lists. Picks output format. +stacktrace.c Debug code, not normally used. +../proc/* Library used to gather data. +regression Regression tests that ought to be run. +common.h Lots of interesting stuff. +Makefile Makefile +p Script used to test ps when the library is not installed. +utf Empty file used to test "ps ut?" unmangling behavior. +ps.1 Man page. + +Operation: + +Unless the personality forces BSD parsing, parser.c tries to parse the +command line as a mixed BSD+SysV+Gnu mess. On failure, BSD parsing is +attempted. If BSD parsing fails _after_ SysV parsing has been attempted, +the error message comes from the original SysV parse. + +Control goes to sortformat.c, which must pick apart ambiguous options +like "O". Failure can reset the whole program and set PER_FORCE_BSD, +which means a second trip through parser.c and sortformat.c. + +The choice of output format happens in sortformat.c. There is a switch() +with all the valid format_flags combinations. The SysV and default +options are NULL (unless overridden by personality), which causes a +trip through SysV output format generation hackery. Note that the +default format always goes through there, even if it is for BSD. +Formats that came from the switch() (generally BSD, plus overrides) +get mangled a bit to support various SysV output modifiers. diff --git a/src/ps/common.h b/src/ps/common.h new file mode 100644 index 0000000..ea49a03 --- /dev/null +++ b/src/ps/common.h @@ -0,0 +1,512 @@ +/* + * common.h - shared header file + * + * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net + * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 1998-2002 Albert Cahalan + * + * This file may be used subject to the terms and conditions of the + * GNU Library General Public License Version 2, or any later version + * at your option, as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + */ + +#ifndef PROCPS_PS_H +#define PROCPS_PS_H + +#include <stdbool.h> + +#include "nls.h" +#include "meminfo.h" +#include "misc.h" +#include "pids.h" +#include "stat.h" + +// --- <pids> interface begin |||||||||||||||||||||||||||||||||||||||||||| +// ----------------------------------------------------------------------- + +// hack to minimize code impact +#undef proc_t +#define proc_t struct pids_stack + +/* this is for allocation of the Pids_items and represents a compromise. + we can't predict how many fields will actually be requested yet there + are numerous duplicate format_array entries. here are the statistics: + 252 entries in the format_array + 82 of those entries are unique + 60 equals a former proc_t size + in reality, only a small portion of the stack depth will be occupied, + and the excess represents storage cost only, not a run-time cpu cost! */ +#define PIDSITEMS 70 + +/* a 'results stack value' extractor macro + where: E=rel enum, T=data type, S=stack */ +#define rSv(E,T,S) PIDS_VAL(rel_ ## E, T, S, Pids_info) + +#define namREL(e) rel_ ## e +#define makEXT(e) extern int namREL(e); +#define makREL(e) int namREL(e) = -1; +#define chkREL(e) if (namREL(e) < 0) { \ + Pids_items[Pids_index] = PIDS_ ## e; \ + namREL(e) = (Pids_index < PIDSITEMS) ? Pids_index++ : rel_noop; } + +#define setREL1(e) { \ + if (!outbuf) { \ + chkREL(e) \ + return 0; \ + } } +#define setREL2(e1,e2) { \ + if (!outbuf) { \ + chkREL(e1) chkREL(e2) \ + return 0; \ + } } +#define setREL3(e1,e2,e3) { \ + if (!outbuf) { \ + chkREL(e1) chkREL(e2) chkREL(e3) \ + return 0; \ + } } +#define setREL4(e1,e2,e3,e4) { \ + if (!outbuf) { \ + chkREL(e1) chkREL(e2) chkREL(e3) chkREL(e4) \ + return 0; \ + } } + +extern struct pids_info *Pids_info; +extern enum pids_item *Pids_items; +extern int Pids_index; + +// most of these need not be extern, they're unique to output.c +// (but for future flexibility the easiest path has been taken) +makEXT(ADDR_CODE_END) +makEXT(ADDR_CODE_START) +makEXT(ADDR_CURR_EIP) +makEXT(ADDR_CURR_ESP) +makEXT(ADDR_STACK_START) +makEXT(AUTOGRP_ID) +makEXT(AUTOGRP_NICE) +makEXT(CGNAME) +makEXT(CGROUP) +makEXT(CMD) +makEXT(CMDLINE) +makEXT(ENVIRON) +makEXT(EXE) +makEXT(FLAGS) +makEXT(FLT_MAJ) +makEXT(FLT_MAJ_C) +makEXT(FLT_MIN) +makEXT(FLT_MIN_C) +makEXT(ID_EGID) +makEXT(ID_EGROUP) +makEXT(ID_EUID) +makEXT(ID_EUSER) +makEXT(ID_FGID) +makEXT(ID_FGROUP) +makEXT(ID_FUID) +makEXT(ID_FUSER) +makEXT(ID_LOGIN) +makEXT(ID_PGRP) +makEXT(ID_PID) +makEXT(ID_PPID) +makEXT(ID_RGID) +makEXT(ID_RGROUP) +makEXT(ID_RUID) +makEXT(ID_RUSER) +makEXT(ID_SESSION) +makEXT(ID_SGID) +makEXT(ID_SGROUP) +makEXT(ID_SUID) +makEXT(ID_SUSER) +makEXT(ID_TGID) +makEXT(ID_TPGID) +makEXT(IO_READ_BYTES) +makEXT(IO_READ_CHARS) +makEXT(IO_READ_OPS) +makEXT(IO_WRITE_BYTES) +makEXT(IO_WRITE_CBYTES) +makEXT(IO_WRITE_CHARS) +makEXT(IO_WRITE_OPS) +makEXT(LXCNAME) +makEXT(NICE) +makEXT(NLWP) +makEXT(NS_CGROUP) +makEXT(NS_IPC) +makEXT(NS_MNT) +makEXT(NS_NET) +makEXT(NS_PID) +makEXT(NS_TIME) +makEXT(NS_USER) +makEXT(NS_UTS) +makEXT(OOM_ADJ) +makEXT(OOM_SCORE) +makEXT(PRIORITY) +makEXT(PRIORITY_RT) +makEXT(PROCESSOR) +makEXT(PROCESSOR_NODE) +makEXT(RSS) +makEXT(RSS_RLIM) +makEXT(SCHED_CLASS) +makEXT(SD_MACH) +makEXT(SD_OUID) +makEXT(SD_SEAT) +makEXT(SD_SESS) +makEXT(SD_SLICE) +makEXT(SD_UNIT) +makEXT(SD_UUNIT) +makEXT(SIGBLOCKED) +makEXT(SIGCATCH) +makEXT(SIGIGNORE) +makEXT(SIGNALS) +makEXT(SIGPENDING) +makEXT(SMAP_PRV_TOTAL) +makEXT(SMAP_PSS) +makEXT(STATE) +makEXT(SUPGIDS) +makEXT(SUPGROUPS) +makEXT(TICS_ALL) +makEXT(TICS_ALL_C) +makEXT(TIME_ALL) +makEXT(TIME_ELAPSED) +makEXT(TICS_BEGAN) +makEXT(TTY) +makEXT(TTY_NAME) +makEXT(TTY_NUMBER) +makEXT(UTILIZATION) +makEXT(UTILIZATION_C) +makEXT(VM_DATA) +makEXT(VM_RSS_LOCKED) +makEXT(VM_RSS) +makEXT(VM_SIZE) +makEXT(VM_STACK) +makEXT(VSIZE_BYTES) +makEXT(WCHAN_NAME) +makEXT(extra) +makEXT(noop) +// ----------------------------------------------------------------------- +// --- <pids> interface end |||||||||||||||||||||||||||||||||||||||||||||| + + +#if TRACE +#define trace(...) fprintf(stderr, __VA_ARGS__) +#else +#define trace(...) +#endif + + +/***************** GENERAL DEFINE ********************/ + +/* selection list */ +#define SEL_RUID 1 +#define SEL_EUID 2 +#define SEL_SUID 3 +#define SEL_FUID 4 +#define SEL_RGID 5 +#define SEL_EGID 6 +#define SEL_SGID 7 +#define SEL_FGID 8 +#define SEL_PGRP 9 +#define SEL_PID 10 +#define SEL_TTY 11 +#define SEL_SESS 12 +#define SEL_COMM 13 +#define SEL_PPID 14 +#define SEL_PID_QUICK 15 + +/* Since an enum could be smashed by a #define, it would be bad. */ +#define U98 0 /* Unix98 standard */ /* This must be 0 */ +#define XXX 1 /* Common extension */ +#define DEC 2 /* Digital Unix */ +#define AIX 3 /* AIX */ +#define SCO 4 /* SCO */ +#define LNX 5 /* Linux original :-) */ +#define BSD 6 /* FreeBSD and OpenBSD */ +#define SUN 7 /* SunOS 5 (Solaris) */ +#define HPU 8 /* HP-UX */ +#define SGI 9 /* Irix */ +#define SOE 10 /* IBM's S/390 OpenEdition */ +#define TST 11 /* test code */ + +/* + * Try not to overflow the output buffer: + * 32 pages for env+cmd + * 64 kB pages on IA-64 + * plus some slack for other stuff + * That is about 8.5 MB on IA-64, or 0.6 MB on i386 + * + * Sadly, current kernels only supply one page of env/command data. + * The buffer is now protected with a guard page, and via other means + * to avoid hitting the guard page. + */ + +/* output buffer size */ +#define OUTBUF_SIZE (2 * 64*1024) + +/******************* PS DEFINE *******************/ + +// Column flags +// Justification control for flags field comes first. +#define CF_JUST_MASK 0x0f +// CF_AIXHACK 0 +#define CF_USER 1 // left if text, right if numeric +#define CF_LEFT 2 +#define CF_RIGHT 3 +#define CF_UNLIMITED 4 +#define CF_WCHAN 5 // left if text, right if numeric +#define CF_SIGNAL 6 // right in 9, or 16 if screen_cols>107 +// Then the other flags +#define CF_PIDMAX 0x00000010 // react to pid_max +// Only one allowed; use separate bits to catch errors. +#define CF_PRINT_THREAD_ONLY 0x10000000 +#define CF_PRINT_PROCESS_ONLY 0x20000000 +#define CF_PRINT_EVERY_TIME 0x40000000 +#define CF_PRINT_AS_NEEDED 0x80000000 // means we have no clue, so assume EVERY TIME +#define CF_PRINT_MASK 0xf0000000 + +/* thread_flags */ +#define TF_B_H 0x0001 +#define TF_B_m 0x0002 +#define TF_U_m 0x0004 +#define TF_U_T 0x0008 +#define TF_U_L 0x0010 +#define TF_show_proc 0x0100 // show the summary line +#define TF_show_task 0x0200 // show the per-thread lines +#define TF_show_both 0x0400 // distinct proc/task format lists +#define TF_loose_tasks 0x0800 // let sorting break up task groups (BSDish) +#define TF_no_sort 0x1000 // don't know if thread-grouping should survive a sort +#define TF_no_forest 0x2000 // don't see how to do threads w/ forest option +#define TF_must_use 0x4000 // options only make sense if LWP/SPID column added + +/* personality control flags */ +#define PER_BROKEN_o 0x0001 +#define PER_BSD_h 0x0002 +#define PER_BSD_m 0x0004 +#define PER_IRIX_l 0x0008 +#define PER_FORCE_BSD 0x0010 +#define PER_GOOD_o 0x0020 +#define PER_OLD_m 0x0040 +#define PER_NO_DEFAULT_g 0x0080 +#define PER_ZAP_ADDR 0x0100 +#define PER_SANE_USER 0x0200 +#define PER_HPUX_x 0x0400 +#define PER_SVR4_x 0x0800 +#define PER_BSD_COLS 0x1000 +#define PER_UNIX_COLS 0x2000 + +/* Simple selections by bit mask */ +#define SS_B_x 0x01 +#define SS_B_g 0x02 +#define SS_U_d 0x04 +#define SS_U_a 0x08 +#define SS_B_a 0x10 + +/* predefined format flags such as: -l -f l u s -j */ +#define FF_Uf 0x0001 /* -f */ +#define FF_Uj 0x0002 /* -j */ +#define FF_Ul 0x0004 /* -l */ +#define FF_Bj 0x0008 /* j */ +#define FF_Bl 0x0010 /* l */ +#define FF_Bs 0x0020 /* s */ +#define FF_Bu 0x0040 /* u */ +#define FF_Bv 0x0080 /* v */ +#define FF_LX 0x0100 /* X */ +#define FF_Lm 0x0200 /* m */ /* overloaded: threads, sort, format */ +#define FF_Fc 0x0400 /* --context */ /* Flask security context format */ + +/* predefined format modifier flags such as: -l -f l u s -j */ +#define FM_c 0x0001 /* -c */ +#define FM_j 0x0002 /* -j */ /* only set when !sysv_j_format */ +#define FM_y 0x0004 /* -y */ +//#define FM_L 0x0008 /* -L */ +#define FM_P 0x0010 /* -P */ +#define FM_M 0x0020 /* -M */ +//#define FM_T 0x0040 /* -T */ +#define FM_F 0x0080 /* -F */ /* -F also sets the regular -f flags */ + +/* sorting & formatting */ +/* U,B,G is Unix,BSD,Gnu and then there is the option itself */ +#define SF_U_O 1 +#define SF_U_o 2 +#define SF_B_O 3 +#define SF_B_o 4 +#define SF_B_m 5 /* overloaded: threads, sort, format */ +#define SF_G_sort 6 +#define SF_G_format 7 + +/* headers */ +#define HEAD_SINGLE 0 /* default, must be 0 */ +#define HEAD_NONE 1 +#define HEAD_MULTI 2 + + +/********************** GENERAL TYPEDEF *******************/ + +/* Other fields that might be useful: + * + * char *name; user-defined column name (format specification) + * int reverse; sorting in reverse (sort specification) + * + * name in place of u + * reverse in place of n + */ + +typedef union sel_union { + pid_t pid; + pid_t ppid; + uid_t uid; + gid_t gid; + dev_t tty; + char cmd[64]; /* this is _not_ \0 terminated */ +} sel_union; + +typedef struct selection_node { + struct selection_node *next; + sel_union *u; /* used if selection type has a list of values */ + int n; /* used if selection type has a list of values */ + int typecode; +} selection_node; + +typedef struct sort_node { + struct sort_node *next; + enum pids_item sr; + int (*xe)(char *, proc_t *); // special format_node 'pr' guy + enum pids_sort_order reverse; + int typecode; +} sort_node; + +typedef struct format_node { + struct format_node *next; + char *name; /* user can override default name */ + int (*pr)(char *restrict const outbuf, const proc_t *restrict const pp); // print function + int width; + int vendor; /* Vendor that invented this */ + int flags; + int typecode; +} format_node; + +typedef struct format_struct { + const char *spec; /* format specifier */ + const char *head; /* default header in the POSIX locale */ + int (* const pr)(char *restrict const outbuf, const proc_t *restrict const pp); // print function + enum pids_item sr; + const int width; + const int vendor; /* Where does this come from? */ + const int flags; +} format_struct; + +/* though ps-specific, needed by general file */ +typedef struct macro_struct { + const char *spec; /* format specifier */ + const char *head; /* default header in the POSIX locale */ +} macro_struct; + +/**************** PS TYPEDEF ***********************/ + +typedef struct aix_struct { + const int desc; /* 1-character format code */ + const char *spec; /* format specifier */ + const char *head; /* default header in the POSIX locale */ +} aix_struct; + +typedef struct shortsort_struct { + const int desc; /* 1-character format code */ + const char *spec; /* format specifier */ +} shortsort_struct; + +/* Save these options for later: -o o -O O --format --sort */ +typedef struct sf_node { + struct sf_node *next; /* next arg */ + format_node *f_cooked; /* convert each arg alone, then merge */ + sort_node *s_cooked; /* convert each arg alone, then merge */ + char *sf; + int sf_code; +} sf_node; + +/********************* UNDECIDED GLOBALS **************/ + +/* output.c */ +extern void show_one_proc(const proc_t *restrict const p, const format_node *restrict fmt); +extern void print_format_specifiers(void); +extern const aix_struct *search_aix_array(const int findme); +extern const shortsort_struct *search_shortsort_array(const int findme); +extern const format_struct *search_format_array(const char *findme); +extern const macro_struct *search_macro_array(const char *findme); +extern void init_output(void); +extern int pr_nop(char *restrict const outbuf, const proc_t *restrict const pp); + +/* global.c */ +extern void reset_global(void); + +/* global.c */ +extern int all_processes; +extern const char *bsd_j_format; +extern const char *bsd_l_format; +extern const char *bsd_s_format; +extern const char *bsd_u_format; +extern const char *bsd_v_format; +extern int bsd_c_option; +extern int bsd_e_option; +extern uid_t cached_euid; +extern int cached_tty; +extern char forest_prefix[4 * 32*1024 + 100]; +extern int forest_type; +extern unsigned format_flags; /* -l -f l u s -j... */ +extern format_node *format_list; /* digested formatting options */ +extern unsigned format_modifiers; /* -c -j -y -P -L... */ +extern int header_gap; +extern int header_type; /* none, single, multi... */ +extern int include_dead_children; +extern int lines_to_next_header; +extern char *lstart_format; +extern int max_line_width; +extern int negate_selection; +extern int page_size; // "int" for math reasons? +extern unsigned personality; +extern int prefer_bsd_defaults; +extern int running_only; +extern int screen_cols; +extern int screen_rows; +extern selection_node *selection_list; +extern unsigned simple_select; +extern sort_node *sort_list; +extern const char *sysv_f_format; +extern const char *sysv_fl_format; +extern const char *sysv_j_format; +extern const char *sysv_l_format; +extern unsigned thread_flags; +extern int unix_f_option; +extern int user_is_number; +extern int wchan_is_number; +extern const char *the_word_help; +extern bool signal_names; + +/************************* PS GLOBALS *********************/ + +/* display.c */ +extern char *myname; + +/* sortformat.c */ +extern int defer_sf_option(const char *arg, int source); +extern const char *process_sf_options(); +extern void reset_sortformat(void); + +/* select.c */ +extern int want_this_proc(proc_t *buf); +extern const char *select_bits_setup(void); + +/* signames.c */ +int print_signame(char *restrict const outbuf, const char *restrict const sig, const size_t len); + +/* help.c */ +extern void __attribute__ ((__noreturn__)) do_help(const char *opt, int rc); + +/* global.c */ +extern void self_info(void); +extern void catastrophic_failure(const char *filename, unsigned int linenum, + const char *message); + +/* parser.c */ +extern int arg_parse(int argc, char *argv[]); + +#endif diff --git a/src/ps/display.c b/src/ps/display.c new file mode 100644 index 0000000..e1a3b0d --- /dev/null +++ b/src/ps/display.c @@ -0,0 +1,681 @@ +/* + * display.c - display ps output + * + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net + * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2012-2014 Jaromir Capik <jcapik@redhat.com + * Copyright © 1998-2003 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <grp.h> +#include <locale.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <sys/sysmacros.h> +#include <sys/types.h> + +#include "c.h" +#include "fileutils.h" +#include "signals.h" +#include "xalloc.h" + +#include "common.h" + +#ifndef SIGCHLD +#define SIGCHLD SIGCLD +#endif + +#define SIG_IS_TERM_OR_HUP(signo) (((signo) == SIGTERM) || (signo) == SIGHUP) + +char *myname; +long Hertz; + +/* just reports a crash */ +static void signal_handler(int signo){ + sigset_t ss; + + sigfillset(&ss); + sigprocmask(SIG_BLOCK, &ss, NULL); + if(signo==SIGPIPE) _exit(0); /* "ps | head" will cause this */ + /* fprintf() is not reentrant, but we _exit() anyway */ + if (!SIG_IS_TERM_OR_HUP(signo)) { + fprintf(stderr, + _("Signal %d (%s) caught by %s (%s).\n"), + signo, + signal_number_to_name(signo), + myname, + PACKAGE_VERSION + ); + } + switch (signo) { + case SIGHUP: + case SIGUSR1: + case SIGUSR2: + exit(EXIT_FAILURE); + default: + if (!SIG_IS_TERM_OR_HUP(signo)) + error_at_line(0, 0, __FILE__, __LINE__, "%s", _("please report this bug")); + signal(signo, SIG_DFL); /* allow core file creation */ + sigemptyset(&ss); + sigaddset(&ss, signo); + sigprocmask(SIG_UNBLOCK, &ss, NULL); + kill(getpid(), signo); + _exit(EXIT_FAILURE); + } +} + +///////////////////////////////////////////////////////////////////////////////////// +#undef DEBUG +#ifdef DEBUG +void init_stack_trace(char *prog_name); + +#include <ctype.h> + +void hex_dump(void *vp){ + char *charlist; + int i = 0; + int line = 45; + char *cp = (char *)vp; + + while(line--){ + printf("%8lx ", (unsigned long)cp); + charlist = cp; + cp += 16; + for(i=0; i<16; i++){ + if((charlist[i]>31) && (charlist[i]<127)){ + printf("%c", charlist[i]); + }else{ + printf("."); + } + } + printf(" "); + for(i=0; i<16; i++) printf(" %2x",(unsigned int)((unsigned char)(charlist[i]))); + printf("\n"); + i=0; + } +} + +static void show_tgid(char *s, int n, sel_union *data){ + printf("%s ", s); + while(--n){ + printf("%d,", data[n].tgid); + } + printf("%d\n", data[0].tgid); +} + +static void show_uid(char *s, int n, sel_union *data){ + struct passwd *pw_data; + printf("%s ", s); + while(--n){ + pw_data = getpwuid(data[n].uid); + if(pw_data) printf("%s,", pw_data->pw_name); + else printf("%d,", data[n].uid); + } + pw_data = getpwuid(data[n].uid); + if(pw_data) printf("%s\n", pw_data->pw_name); + else printf("%d\n", data[n].uid); +} + +static void show_gid(char *s, int n, sel_union *data){ + struct group *gr_data; + printf("%s ", s); + while(--n){ + gr_data = getgrgid(data[n].gid); + if(gr_data) printf("%s,", gr_data->gr_name); + else printf("%d,", data[n].gid); + } + gr_data = getgrgid(data[n].gid); + if(gr_data) printf("%s\n", gr_data->gr_name); + else printf("%d\n", data[n].gid); +} + +static void show_tty(char *s, int n, sel_union *data){ + printf("%s ", s); + while(--n){ + printf("%d:%d,", (int)major(data[n].tty), (int)minor(data[n].tty)); + } + printf("%d:%d\n", (int)major(data[n].tty), (int)minor(data[n].tty)); +} + +static void show_cmd(char *s, int n, sel_union *data){ + printf("%s ", s); + while(--n){ + printf("%.8s,", data[n].cmd); + } + printf("%.8s\n", data[0].cmd); +} + +static void arg_show(void){ + selection_node *walk = selection_list; + while(walk){ + switch(walk->typecode){ + case SEL_RUID: show_uid("RUID", walk->n, walk->u); break; + case SEL_EUID: show_uid("EUID", walk->n, walk->u); break; + case SEL_SUID: show_uid("SUID", walk->n, walk->u); break; + case SEL_FUID: show_uid("FUID", walk->n, walk->u); break; + case SEL_RGID: show_gid("RGID", walk->n, walk->u); break; + case SEL_EGID: show_gid("EGID", walk->n, walk->u); break; + case SEL_SGID: show_gid("SGID", walk->n, walk->u); break; + case SEL_FGID: show_gid("FGID", walk->n, walk->u); break; + case SEL_PGRP: show_pid("PGRP", walk->n, walk->u); break; + case SEL_PID : show_pid("PID ", walk->n, walk->u); break; + case SEL_PID_QUICK : show_pid("PID_QUICK ", walk->n, walk->u); break; + case SEL_PPID: show_pid("PPID", walk->n, walk->u); break; + case SEL_TTY : show_tty("TTY ", walk->n, walk->u); break; + case SEL_SESS: show_pid("SESS", walk->n, walk->u); break; + case SEL_COMM: show_cmd("COMM", walk->n, walk->u); break; + default: printf("Garbage typecode value!\n"); + } + walk = walk->next; + } +} + +#endif +////////////////////////////////////////////////////////////////////////// + + +/***** check the header */ +/* Unix98: must not print empty header */ +static void check_headers(void){ + format_node *walk = format_list; + int head_normal = 0; + if(header_type==HEAD_MULTI){ + header_gap = screen_rows-1; /* true BSD */ + return; + } + if(header_type==HEAD_NONE){ + lines_to_next_header = -1; /* old Linux */ + return; + } + while(walk){ + if(!*(walk->name)){ + walk = walk->next; + continue; + } + if(walk->pr){ + head_normal++; + walk = walk->next; + continue; + } + walk = walk->next; + } + if(!head_normal) lines_to_next_header = -1; /* how UNIX does --noheader */ +} + +static format_node *proc_format_list; +static format_node *task_format_list; + + +/***** munge lists and determine final needs */ +static void lists_and_needs(void){ + check_headers(); + + // only care about the difference when showing both + if(thread_flags & TF_show_both){ + format_node pfn, tfn; // junk, to handle special case at begin of list + format_node *walk = format_list; + format_node *p_end = &pfn; + format_node *t_end = &tfn; + while(walk){ + format_node *new = xmalloc(sizeof(format_node)); + memcpy(new,walk,sizeof(format_node)); + p_end->next = walk; + t_end->next = new; + p_end = walk; + t_end = new; + switch(walk->flags & CF_PRINT_MASK){ + case CF_PRINT_THREAD_ONLY: + p_end->pr = pr_nop; + break; + case CF_PRINT_PROCESS_ONLY: + t_end->pr = pr_nop; + break; + default: + catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + // FALL THROUGH + case CF_PRINT_AS_NEEDED: + case CF_PRINT_EVERY_TIME: + break; + } + walk = walk->next; + } + t_end->next = NULL; + p_end->next = NULL; + proc_format_list = pfn.next; + task_format_list = tfn.next; + }else{ + proc_format_list = format_list; + task_format_list = format_list; + } +} + +////////////////////////////////////////////////////////////////////////// + +/***** fill in %CPU; not in libproc because of include_dead_children */ +/* Note: for sorting, not display, so 0..0x7fffffff would be OK */ +static void value_this_proc_pcpu(proc_t *buf){ + unsigned long long used_jiffies; + unsigned long pcpu = 0; + unsigned long long seconds; + + if(want_this_proc(buf)) { + + if(include_dead_children) used_jiffies = rSv(TICS_ALL_C, ull_int, buf); + else used_jiffies = rSv(TICS_ALL, ull_int, buf); + + seconds = rSv(TIME_ELAPSED, real, buf); + if(seconds) pcpu = (used_jiffies * 1000ULL / Hertz) / seconds; + + // if xtra-procps-debug.h active, can't use PIDS_VAL as base due to assignment + buf->head[rel_extra].result.ul_int = pcpu; + } +} + +/***** just display */ +static void simple_spew(void){ + struct pids_fetch *pidread; + proc_t *buf; + int i; + + // -q option (only single SEL_PID_QUICK typecode entry expected in the list, if present) + if (selection_list && selection_list->typecode == SEL_PID_QUICK) { + unsigned *pidlist = xcalloc(selection_list->n, sizeof(unsigned)); + enum pids_select_type which; + for (i = 0; i < selection_list->n; i++) + pidlist[i] = selection_list->u[selection_list->n-i-1].pid; + which = (thread_flags & (TF_loose_tasks|TF_show_task)) + ? PIDS_SELECT_PID_THREADS : PIDS_SELECT_PID; + pidread = procps_pids_select(Pids_info, pidlist, selection_list->n, which); + free(pidlist); + } else { + enum pids_fetch_type which; + which = (thread_flags & (TF_loose_tasks|TF_show_task)) + ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY; + pidread = procps_pids_reap(Pids_info, which); + } + if (!pidread) { + fprintf(stderr, _("fatal library error, reap\n")); + exit(EXIT_FAILURE); + } + + switch(thread_flags & (TF_show_proc|TF_loose_tasks|TF_show_task)){ + case TF_show_proc: // normal non-thread output + for (i = 0; i < pidread->counts->total; i++) { + buf = pidread->stacks[i]; + if (want_this_proc(buf)) + show_one_proc(buf, proc_format_list); + } + break; + case TF_show_task: // -L and -T options + case TF_show_proc|TF_loose_tasks: // H option + for (i = 0; i < pidread->counts->total; i++) { + buf = pidread->stacks[i]; + if (want_this_proc(buf)) + show_one_proc(buf, task_format_list); + } + break; + case TF_show_proc|TF_show_task: // m and -m options + procps_pids_sort(Pids_info, pidread->stacks + , pidread->counts->total, PIDS_TICS_BEGAN, PIDS_SORT_ASCEND); + procps_pids_sort(Pids_info, pidread->stacks + , pidread->counts->total, PIDS_ID_TGID, PIDS_SORT_ASCEND); + for (i = 0; i < pidread->counts->total; i++) { + buf = pidread->stacks[i]; +next_proc: + if (want_this_proc(buf)) { + int self = rSv(ID_PID, s_int, buf); + show_one_proc(buf, proc_format_list); + for (; i < pidread->counts->total; i++) { + buf = pidread->stacks[i]; + if (rSv(ID_TGID, s_int, buf) != self) goto next_proc; + show_one_proc(buf, task_format_list); + } + } + } + break; + } +} + + +/***** forest output requires sorting by ppid; add start_time by default */ +static void prep_forest_sort(void){ + sort_node *tmp_list = sort_list; + const format_struct *incoming; + + if(!sort_list) { /* assume start time order */ + incoming = search_format_array("ppid"); + if(!incoming) { fprintf(stderr, _("could not find ppid\n")); exit(1); } + tmp_list = xmalloc(sizeof(sort_node)); + tmp_list->reverse = PIDS_SORT_ASCEND; + tmp_list->typecode = '?'; /* what was this for? */ + tmp_list->sr = incoming->sr; + tmp_list->next = sort_list; + sort_list = tmp_list; + } + /* this is required for the forest option */ + incoming = search_format_array("start_time"); + if(!incoming) { fprintf(stderr, _("could not find start_time\n")); exit(1); } + tmp_list = xmalloc(sizeof(sort_node)); + tmp_list->reverse = PIDS_SORT_ASCEND; + tmp_list->typecode = '?'; /* what was this for? */ + tmp_list->sr = incoming->sr; + tmp_list->next = sort_list; + sort_list = tmp_list; +} + +/* we rely on the POSIX requirement for zeroed memory */ +static proc_t **processes; + +/***** show pre-sorted array of process pointers */ +static void show_proc_array(int n){ + proc_t **p = processes; + while(n--){ + show_one_proc(*p, proc_format_list); + p++; + } +} + +/***** show tree */ +/* this needs some optimization work */ +#define ADOPTED(x) 1 + +#define IS_LEVEL_SAFE(level) \ + ((level) >= 0 && (size_t)(level) < sizeof(forest_prefix)) + +static void show_tree(const int self, const int n, const int level, const int have_sibling){ + int i = 0; + + if(!IS_LEVEL_SAFE(level)) + catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + + if(level){ + /* add prefix of "+" or "L" */ + if(have_sibling) forest_prefix[level-1] = '+'; + else forest_prefix[level-1] = 'L'; + } + forest_prefix[level] = '\0'; + show_one_proc(processes[self],format_list); /* first show self */ + for(;;){ /* look for children */ + if(i >= n) return; /* no children */ + if(rSv(ID_PPID, s_int, processes[i]) == rSv(ID_PID, s_int, processes[self])) break; + i++; + } + if(level){ + /* change our prefix to "|" or " " for the children */ + if(have_sibling) forest_prefix[level-1] = '|'; + else forest_prefix[level-1] = ' '; + } + forest_prefix[level] = '\0'; + for(;;){ + int self_pid; + int more_children = 1; + if(i >= n) break; /* over the edge */ + self_pid=rSv(ID_PID, s_int, processes[self]); + if(i+1 >= n) + more_children = 0; + else + if(rSv(ID_PPID, s_int, processes[i+1]) != self_pid) more_children = 0; + if(self_pid==1 && ADOPTED(processes[i]) && forest_type!='u') + show_tree(i++, n, level, more_children); + else + show_tree(i++, n, IS_LEVEL_SAFE(level+1) ? level+1 : level, more_children); + if(!more_children) break; + } + /* chop prefix that children added -- do we need this? */ + /* chop prefix that children added */ + forest_prefix[level] = '\0'; +} + +#undef IS_LEVEL_SAFE + +/***** show forest */ +static void show_forest(const int n){ + int i = n; + int j; + while(i--){ /* cover whole array looking for trees */ + j = n; + while(j--){ /* search for parent: if none, i is a tree! */ + if(rSv(ID_PID, s_int, processes[j]) == rSv(ID_PPID, s_int, processes[i])) goto not_root; + } + show_tree(i,n,0,0); +not_root: + ; + } + /* don't free the array because it takes time and ps will exit anyway */ +} + +#if 0 +static int want_this_proc_nop(proc_t *dummy){ + (void)dummy; + return 1; +} +#endif + +/***** sorted or forest */ +static void fancy_spew(void){ + struct pids_fetch *pidread; + enum pids_fetch_type which; + proc_t *buf; + int i, n = 0; + + which = (thread_flags & TF_loose_tasks) + ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY; + + pidread = procps_pids_reap(Pids_info, which); + if (!pidread || !pidread->counts->total) { + fprintf(stderr, _("fatal library error, reap\n")); + exit(EXIT_FAILURE); + } + processes = xcalloc(pidread->counts->total, sizeof(void*)); + for (i = 0; i < pidread->counts->total; i++) { + buf = pidread->stacks[i]; + value_this_proc_pcpu(buf); + if (want_this_proc(buf)) + processes[n++] = buf; + } + if (n) { + if(forest_type) prep_forest_sort(); + while(sort_list) { + procps_pids_sort(Pids_info, processes, n, sort_list->sr, sort_list->reverse); + sort_list = sort_list->next; + } + if(forest_type) show_forest(n); + else show_proc_array(n); + } + free(processes); +} + +static void arg_check_conflicts(void) +{ + int selection_list_len; + int has_quick_pid; + selection_node *walk = selection_list; + has_quick_pid = 0; + selection_list_len = 0; + + while (walk) { + if (walk->typecode == SEL_PID_QUICK) has_quick_pid++; + walk = walk->next; + selection_list_len++; + } + + /* -q doesn't allow multiple occurrences */ + if (has_quick_pid > 1) { + fprintf(stderr, "q/-q/--quick-pid can only be used once.\n"); + exit(1); + } + + /* -q doesn't allow combinations with other selection switches */ + if (has_quick_pid && selection_list_len > has_quick_pid) { + fprintf(stderr, "q/-q/--quick-pid cannot be combined with other selection options.\n"); + exit(1); + } + + /* -q cannot be used with forest type listings */ + if (has_quick_pid && forest_type) { + fprintf(stderr, "q/-q/--quick-pid cannot be used together with forest type listings.\n"); + exit(1); + } + + /* -q cannot be used with sort */ + if (has_quick_pid && sort_list) { + fprintf(stderr, "q/-q,--quick-pid cannot be used together with sort options.\n"); + exit(1); + } + + /* -q cannot be used with -N */ + if (has_quick_pid && negate_selection) { + fprintf(stderr, "q/-q/--quick-pid cannot be used together with negation switches.\n"); + exit(1); + } + +} + +static void finalize_stacks (void) +{ + format_node *f_node; + sort_node *s_node; + +#if (PIDSITEMS < 60) + # error PIDSITEMS (common.h) should be at least 60! +#endif + + /* first, ensure minimum result structures for items + which may or may not actually be displayable ... */ + Pids_index = 0; + + // needed by for selections + chkREL(CMD) + chkREL(ID_EGID) + chkREL(ID_EUID) + chkREL(ID_FGID) + chkREL(ID_FUID) + chkREL(ID_PID) + chkREL(ID_PPID) + chkREL(ID_RGID) + chkREL(ID_RUID) + chkREL(ID_SESSION) + chkREL(ID_SGID) + chkREL(ID_SUID) + chkREL(ID_TGID) + chkREL(STATE) + chkREL(TTY) + // needed to creata an enhanced 'stat/state' + chkREL(ID_PGRP) + chkREL(ID_TPGID) + chkREL(NICE) + chkREL(NLWP) + chkREL(RSS) + chkREL(VM_RSS_LOCKED) + // needed with 's' switch, previously assured + chkREL(SIGBLOCKED) + chkREL(SIGCATCH) + chkREL(SIGIGNORE) + chkREL(SIGNALS) + chkREL(SIGPENDING) + // needed with loss of defunct 'cook_time' macros + chkREL(TICS_ALL) + chkREL(TICS_ALL_C) + chkREL(TIME_ALL) + chkREL(TIME_ELAPSED) + chkREL(TICS_BEGAN) + // special items with 'extra' used as former pcpu + chkREL(extra) + chkREL(noop) + + // now accommodate any results not yet satisfied + f_node = format_list; + while (f_node) { + if (*f_node->pr) (*f_node->pr)(NULL, NULL); + f_node = f_node->next; + } + s_node = sort_list; + while (s_node) { + if (s_node->xe) (*s_node->xe)(NULL, NULL); + s_node = s_node->next; + } + + procps_pids_reset(Pids_info, Pids_items, Pids_index); +} + +/***** no comment */ +int main(int argc, char *argv[]){ + atexit(close_stdout); + myname = strrchr(*argv, '/'); + if (myname) ++myname; else myname = *argv; + Hertz = procps_hertz_get(); + + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + setenv("TZ", ":/etc/localtime", 0); + +#ifdef DEBUG + init_stack_trace(argv[0]); +#else + do { + struct sigaction sa; + int i = 32; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + sigfillset(&sa.sa_mask); + while(i--) switch(i){ + default: + sigaction(i,&sa,NULL); + case 0: + case SIGCONT: + case SIGINT: /* ^C */ + case SIGTSTP: /* ^Z */ + case SIGTTOU: /* see stty(1) man page */ + case SIGQUIT: /* ^\ */ + case SIGPROF: /* profiling */ + case SIGKILL: /* can not catch */ + case SIGSTOP: /* can not catch */ + case SIGWINCH: /* don't care if window size changes */ + case SIGURG: /* Urgent condition on socket (4.2BSD) */ + ; + } + } while (0); +#endif + + reset_global(); /* must be before parser */ + arg_parse(argc,argv); + + /* check for invalid combination of arguments */ + arg_check_conflicts(); + +/* arg_show(); */ + trace("screen is %ux%u\n",screen_cols,screen_rows); +/* printf("sizeof(proc_t) is %d.\n", sizeof(proc_t)); */ + trace("======= ps output follows =======\n"); + + init_output(); /* must be between parser and output */ + + finalize_stacks(); + lists_and_needs(); + + if(forest_type || sort_list) fancy_spew(); /* sort or forest */ + else simple_spew(); /* no sort, no forest */ + show_one_proc((proc_t *)-1,format_list); /* no output yet? */ + + procps_pids_unref(&Pids_info); + return 0; +} diff --git a/src/ps/global.c b/src/ps/global.c new file mode 100644 index 0000000..cc4fa24 --- /dev/null +++ b/src/ps/global.c @@ -0,0 +1,651 @@ +/* + * global.c - generic ps symbols and functions + * + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net + * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 1998-2002 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <fcntl.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <stdbool.h> + +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <sys/types.h> + +#include "c.h" +#include "xalloc.h" + +#include "common.h" + +#ifndef __GNU_LIBRARY__ +#define __GNU_LIBRARY__ -1 +#endif +#ifndef __GLIBC__ +#define __GLIBC__ -1 +#endif +#ifndef __GLIBC_MINOR__ +#define __GLIBC_MINOR__ -1 +#endif + +// --- <pids> interface begin |||||||||||||||||||||||||||||||||||||||||||| +// ----------------------------------------------------------------------- +struct pids_info *Pids_info = NULL; // our required <pids> context +enum pids_item *Pids_items; // allocated as PIDSITEMS +int Pids_index; // actual number of active enums + +// most of these could be defined as static in the output.c module +// (but for future flexibility, the easiest route has been chosen) +makREL(ADDR_CODE_END) +makREL(ADDR_CODE_START) +makREL(ADDR_CURR_EIP) +makREL(ADDR_CURR_ESP) +makREL(ADDR_STACK_START) +makREL(AUTOGRP_ID) +makREL(AUTOGRP_NICE) +makREL(CGNAME) +makREL(CGROUP) +makREL(CMD) +makREL(CMDLINE) +makREL(ENVIRON) +makREL(EXE) +makREL(FLAGS) +makREL(FLT_MAJ) +makREL(FLT_MAJ_C) +makREL(FLT_MIN) +makREL(FLT_MIN_C) +makREL(ID_EGID) +makREL(ID_EGROUP) +makREL(ID_EUID) +makREL(ID_EUSER) +makREL(ID_FGID) +makREL(ID_FGROUP) +makREL(ID_FUID) +makREL(ID_FUSER) +makREL(ID_LOGIN) +makREL(ID_PGRP) +makREL(ID_PID) +makREL(ID_PPID) +makREL(ID_RGID) +makREL(ID_RGROUP) +makREL(ID_RUID) +makREL(ID_RUSER) +makREL(ID_SESSION) +makREL(ID_SGID) +makREL(ID_SGROUP) +makREL(ID_SUID) +makREL(ID_SUSER) +makREL(ID_TGID) +makREL(ID_TPGID) +makREL(IO_READ_BYTES) +makREL(IO_READ_CHARS) +makREL(IO_READ_OPS) +makREL(IO_WRITE_BYTES) +makREL(IO_WRITE_CBYTES) +makREL(IO_WRITE_CHARS) +makREL(IO_WRITE_OPS) +makREL(LXCNAME) +makREL(NICE) +makREL(NLWP) +makREL(NS_CGROUP) +makREL(NS_IPC) +makREL(NS_MNT) +makREL(NS_NET) +makREL(NS_PID) +makREL(NS_TIME) +makREL(NS_USER) +makREL(NS_UTS) +makREL(OOM_ADJ) +makREL(OOM_SCORE) +makREL(PRIORITY) +makREL(PRIORITY_RT) +makREL(PROCESSOR) +makREL(PROCESSOR_NODE) +makREL(RSS) +makREL(RSS_RLIM) +makREL(SCHED_CLASS) +makREL(SD_MACH) +makREL(SD_OUID) +makREL(SD_SEAT) +makREL(SD_SESS) +makREL(SD_SLICE) +makREL(SD_UNIT) +makREL(SD_UUNIT) +makREL(SIGBLOCKED) +makREL(SIGCATCH) +makREL(SIGIGNORE) +makREL(SIGNALS) +makREL(SIGPENDING) +makREL(SMAP_PRV_TOTAL) +makREL(SMAP_PSS) +makREL(STATE) +makREL(SUPGIDS) +makREL(SUPGROUPS) +makREL(TICS_ALL) +makREL(TICS_ALL_C) +makREL(TIME_ALL) +makREL(TIME_ELAPSED) +makREL(TICS_BEGAN) +makREL(TTY) +makREL(TTY_NAME) +makREL(TTY_NUMBER) +makREL(UTILIZATION) +makREL(UTILIZATION_C) +makREL(VM_DATA) +makREL(VM_RSS_LOCKED) +makREL(VM_RSS) +makREL(VM_SIZE) +makREL(VM_STACK) +makREL(VSIZE_BYTES) +makREL(WCHAN_NAME) +makREL(extra) +makREL(noop) +// ----------------------------------------------------------------------- +// --- <pids> interface end |||||||||||||||||||||||||||||||||||||||||||||| + + +static const char * saved_personality_text = "You found a bug!"; + +int all_processes = -1; +const char *bsd_j_format = (const char *)0xdeadbeef; +const char *bsd_l_format = (const char *)0xdeadbeef; +const char *bsd_s_format = (const char *)0xdeadbeef; +const char *bsd_u_format = (const char *)0xdeadbeef; +const char *bsd_v_format = (const char *)0xdeadbeef; +int bsd_c_option = -1; +int bsd_e_option = -1; +unsigned cached_euid = 0xffffffff; +int cached_tty = -1; +char forest_prefix[4 * 32*1024 + 100]; // FIXME +int forest_type = -1; +unsigned format_flags = 0xffffffff; /* -l -f l u s -j... */ +format_node *format_list = (format_node *)0xdeadbeef; /* digested formatting options */ +unsigned format_modifiers = 0xffffffff; /* -c -j -y -P -L... */ +int header_gap = -1; +int header_type = -1; +int include_dead_children = -1; +int lines_to_next_header = -1; +char *lstart_format = NULL; +int negate_selection = -1; +int running_only = -1; +int page_size = -1; // "int" for math reasons? +unsigned personality = 0xffffffff; +int prefer_bsd_defaults = -1; +int screen_cols = -1; +int screen_rows = -1; +selection_node *selection_list = (selection_node *)0xdeadbeef; +unsigned simple_select = 0xffffffff; +sort_node *sort_list = (sort_node *)0xdeadbeef; /* ready-to-use sort list */ +const char *sysv_f_format = (const char *)0xdeadbeef; +const char *sysv_fl_format = (const char *)0xdeadbeef; +const char *sysv_j_format = (const char *)0xdeadbeef; +const char *sysv_l_format = (const char *)0xdeadbeef; +unsigned thread_flags = 0xffffffff; +int unix_f_option = -1; +int user_is_number = -1; +int wchan_is_number = -1; +const char *the_word_help; +bool signal_names = FALSE; + +static void reset_selection_list(void){ + selection_node *old; + selection_node *walk = selection_list; + if(selection_list == (selection_node *)0xdeadbeef){ + selection_list = NULL; + return; + } + while(walk){ + old = walk; + walk = old->next; + free(old->u); + free(old); + } + selection_list = NULL; +} + +// The rules: +// 1. Defaults are implementation-specific. (ioctl,termcap,guess) +// 2. COLUMNS and LINES override the defaults. (standards compliance) +// 3. Command line options override everything else. +// 4. Actual output may be more if the above is too narrow. +// +// SysV tends to spew semi-wide output in all cases. The args +// will be limited to 64 or 80 characters, without regard to +// screen size. So lines of 120 to 160 chars are normal. +// Tough luck if you want more or less than that! HP-UX has a +// new "-x" option for 1024-char args in place of comm that +// we'll implement at some point. +// +// BSD tends to make a good effort, then fall back to 80 cols. +// Use "ww" to get infinity. This is nicer for "ps | less" +// and "watch ps". It can run faster too. +static void set_screen_size(void){ + struct winsize ws; + char *columns; /* Unix98 environment variable */ + char *lines; /* Unix98 environment variable */ + + do{ + int fd; + if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break; + if(ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break; + if(ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col>0 && ws.ws_row>0) break; + fd = open("/dev/tty", O_NOCTTY|O_NONBLOCK|O_RDONLY); + if(fd != -1){ + int ret = ioctl(fd, TIOCGWINSZ, &ws); + close(fd); + if(ret != -1 && ws.ws_col>0 && ws.ws_row>0) break; + } + // TODO: ought to do tgetnum("co") and tgetnum("li") here + ws.ws_col = 80; + ws.ws_row = 24; + }while(0); + screen_cols = ws.ws_col; // hmmm, NetBSD subtracts 1 + screen_rows = ws.ws_row; + + // TODO: delete this line + if(!isatty(STDOUT_FILENO)) screen_cols = OUTBUF_SIZE; + + columns = getenv("COLUMNS"); + if(columns && *columns){ + long t; + char *endptr; + t = strtol(columns, &endptr, 0); + if(!*endptr && (t>0) && (t<(long)OUTBUF_SIZE)) screen_cols = (int)t; + } + + lines = getenv("LINES"); + if(lines && *lines){ + long t; + char *endptr; + t = strtol(lines, &endptr, 0); + if(!*endptr && (t>0) && (t<(long)OUTBUF_SIZE)) screen_rows = (int)t; + } + + if((screen_cols<9) || (screen_rows<2)) + fprintf(stderr,_("your %dx%d screen size is bogus. expect trouble\n"), + screen_cols, screen_rows + ); +} + +/**************** personality control **************/ + +typedef struct personality_table_struct { + const char *name; /* personality name */ + const void *jump; /* See gcc extension info. :-) */ +} personality_table_struct; + +static int compare_personality_table_structs(const void *a, const void *b){ + return strcasecmp(((const personality_table_struct*)a)->name,((const personality_table_struct*)b)->name); +} + +static const char *set_personality(void){ + const char *s; + size_t sl; + char buf[16]; + personality_table_struct findme = { buf, NULL}; + personality_table_struct *found; + static const personality_table_struct personality_table[] = { + {"390", &&case_390}, + {"aix", &&case_aix}, + {"bsd", &&case_bsd}, + {"compaq", &&case_compaq}, + {"debian", &&case_debian}, + {"default", &&case_default}, + {"digital", &&case_digital}, + {"gnu", &&case_gnu}, + {"hp", &&case_hp}, + {"hpux", &&case_hpux}, + {"irix", &&case_irix}, + {"linux", &&case_linux}, + {"old", &&case_old}, + {"os390", &&case_os390}, + {"posix", &&case_posix}, + {"s390", &&case_s390}, + {"sco", &&case_sco}, + {"sgi", &&case_sgi}, + {"solaris2", &&case_solaris2}, + {"sunos4", &&case_sunos4}, + {"svr4", &&case_svr4}, + {"sysv", &&case_sysv}, + {"tru64", &&case_tru64}, + {"unix", &&case_unix}, + {"unix95", &&case_unix95}, + {"unix98", &&case_unix98}, + {"unknown", &&case_unknown} + }; + const int personality_table_count = sizeof(personality_table)/sizeof(personality_table_struct); + + personality = 0; + prefer_bsd_defaults = 0; + + bsd_j_format = "OL_j"; + bsd_l_format = "OL_l"; + bsd_s_format = "OL_s"; + bsd_u_format = "OL_u"; + bsd_v_format = "OL_v"; + + /* When these are NULL, the code does SysV output modifier logic */ + sysv_f_format = NULL; + sysv_fl_format = NULL; + sysv_j_format = NULL; + sysv_l_format = NULL; + + s = getenv("PS_PERSONALITY"); + if(!s || !*s) s = getenv("CMD_ENV"); + if(!s || !*s) s="unknown"; /* "Do The Right Thing[tm]" */ + if(getenv("I_WANT_A_BROKEN_PS")) s="old"; + sl = strlen(s); + if(sl > 15) return _("environment specified an unknown personality"); + strncpy(buf, s, sl); + buf[sl] = '\0'; + if ((saved_personality_text = strdup(buf))==NULL) { + fprintf(stderr, _("cannot strdup() personality text\n")); + exit(EXIT_FAILURE); + } + + found = bsearch(&findme, personality_table, personality_table_count, + sizeof(personality_table_struct), compare_personality_table_structs + ); + + if(!found) return _("environment specified an unknown personality"); + + goto *(found->jump); /* See gcc extension info. :-) */ + + case_bsd: + personality = PER_FORCE_BSD | PER_BSD_h | PER_BSD_m; + prefer_bsd_defaults = 1; + bsd_j_format = "FB_j"; + bsd_l_format = "FB_l"; + /* bsd_s_format not used */ + bsd_u_format = "FB_u"; + bsd_v_format = "FB_v"; + return NULL; + + case_old: + personality = PER_FORCE_BSD | PER_OLD_m; + prefer_bsd_defaults = 1; + return NULL; + + case_debian: /* Toss this? They don't seem to care much. */ + case_gnu: + personality = PER_GOOD_o | PER_OLD_m; + prefer_bsd_defaults = 1; + sysv_f_format = "RD_f"; + /* sysv_fl_format = "RD_fl"; */ /* old Debian ps can't do this! */ + sysv_j_format = "RD_j"; + sysv_l_format = "RD_l"; + return NULL; + + case_linux: + personality = PER_GOOD_o | PER_ZAP_ADDR | PER_SANE_USER; + return NULL; + + case_default: /* use defaults for ps, ignoring other environment variables */ + case_unknown: /* defaults, but also check inferior environment variables */ + return NULL; + + case_aix: + bsd_j_format = "FB_j"; + bsd_l_format = "FB_l"; + /* bsd_s_format not used */ + bsd_u_format = "FB_u"; + bsd_v_format = "FB_v"; + return NULL; + + case_tru64: + case_compaq: + case_digital: + // no PER_NO_DEFAULT_g even though man page claims it + // Reality: the g is a NOP + personality = PER_GOOD_o | PER_BSD_h; + prefer_bsd_defaults = 1; + sysv_f_format = "F5FMT"; + sysv_fl_format = "FL5FMT"; + sysv_j_format = "JFMT"; + sysv_l_format = "L5FMT"; + bsd_j_format = "JFMT"; + bsd_l_format = "LFMT"; + bsd_s_format = "SFMT"; + bsd_u_format = "UFMT"; + bsd_v_format = "VFMT"; + return NULL; + + case_sunos4: + personality = PER_NO_DEFAULT_g; + prefer_bsd_defaults = 1; + bsd_j_format = "FB_j"; + bsd_l_format = "FB_l"; + /* bsd_s_format not used */ + bsd_u_format = "FB_u"; + bsd_v_format = "FB_v"; + return NULL; + + case_irix: + case_sgi: + s = getenv("_XPG"); + if(s && s[0]>'0' && s[0]<='9') + return NULL; + personality = PER_IRIX_l; + return NULL; + + case_os390: /* IBM's OS/390 OpenEdition on the S/390 mainframe */ + case_s390: + case_390: + sysv_j_format = "J390"; /* don't know what -jl and -jf do */ + return NULL; + + case_hp: + case_hpux: + personality = PER_HPUX_x; + return NULL; + + case_svr4: + case_sysv: + case_sco: + personality = PER_SVR4_x; + return NULL; + + case_posix: + case_solaris2: + case_unix95: + case_unix98: + case_unix: + return NULL; +} + + +/************ Call this to reinitialize everything ***************/ +void reset_global(void){ + proc_t *p; + int i; + + reset_selection_list(); + +// --- <pids> interface -------------------------------------------------- + if (!Pids_items) + Pids_items = xcalloc(PIDSITEMS, sizeof(enum pids_item)); + + for (i = 0; i < PIDSITEMS; i++) + Pids_items[i] = PIDS_noop; + + if (!Pids_info) { + if (procps_pids_new(&Pids_info, Pids_items, i)) { + fprintf(stderr, _("fatal library error, context\n")); + exit(EXIT_FAILURE); + } + } + + Pids_items[0] = PIDS_TTY; + procps_pids_reset(Pids_info, Pids_items, 1); + if (!(p = fatal_proc_unmounted(Pids_info, 1))) { + fprintf(stderr, _("fatal library error, lookup self\n")); + exit(EXIT_FAILURE); + } +// --- <pids> interface -------------------------------------------------- + + set_screen_size(); + set_personality(); + + all_processes = 0; + bsd_c_option = 0; + bsd_e_option = 0; + cached_euid = geteuid(); + cached_tty = PIDS_VAL(0, s_int, p, Pids_info); +/* forest_prefix must be all zero because of POSIX */ + forest_type = 0; + format_flags = 0; /* -l -f l u s -j... */ + format_list = NULL; /* digested formatting options */ + format_modifiers = 0; /* -c -j -y -P -L... */ + header_gap = -1; /* send lines_to_next_header to -infinity */ + header_type = HEAD_SINGLE; + include_dead_children = 0; + lines_to_next_header = 1; + negate_selection = 0; + page_size = getpagesize(); + running_only = 0; + selection_list = NULL; + simple_select = 0; + sort_list = NULL; + thread_flags = 0; + unix_f_option = 0; + user_is_number = 0; + wchan_is_number = 0; +/* Translation Note: + . The following translatable word will be used to recognize the + . user's request for help text. In other words, the translation + . you provide will alter program behavior. + . + . It must be limited to 15 characters or less. + */ + the_word_help = _("help"); +} + +static const char archdefs[] = +#ifdef __alpha__ +" alpha" +#endif +#ifdef __arm__ +" arm" +#endif +#ifdef __hppa__ +" hppa" +#endif +#ifdef __i386__ +" i386" +#endif +#ifdef __ia64__ +" ia64" +#endif +#ifdef __mc68000__ +" mc68000" +#endif +#ifdef __mips64__ +" mips64" +#endif +#ifdef __mips__ +" mips" +#endif +#ifdef __powerpc__ +" powerpc" +#endif +#ifdef __sh3__ +" sh3" +#endif +#ifdef __sh__ +" sh" +#endif +#ifdef __sparc__ +" sparc" +#endif +#ifdef __sparc_v9__ +" sparc_v9" +#endif +#ifdef __x86_64__ +" x86_64" +#endif +""; + +/*********** spew variables ***********/ +void self_info(void){ + fprintf(stderr, + "BSD j %s\n" + "BSD l %s\n" + "BSD s %s\n" + "BSD u %s\n" + "BSD v %s\n" + "SysV -f %s\n" + "SysV -fl %s\n" + "SysV -j %s\n" + "SysV -l %s\n" + "\n", + bsd_j_format ? bsd_j_format : "(none)", + bsd_l_format ? bsd_l_format : "(none)", + bsd_s_format ? bsd_s_format : "(none)", + bsd_u_format ? bsd_u_format : "(none)", + bsd_v_format ? bsd_v_format : "(none)", + sysv_f_format ? sysv_f_format : "(none)", + sysv_fl_format ? sysv_fl_format : "(none)", + sysv_j_format ? sysv_j_format : "(none)", + sysv_l_format ? sysv_l_format : "(none)" + ); + + fprintf(stderr, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION); + /* __libc_print_version(); */ /* how can we get the run-time version? */ + fprintf(stderr, "Compiled with: glibc %d.%d, gcc %d.%d\n\n", + __GLIBC__, __GLIBC_MINOR__, __GNUC__, __GNUC_MINOR__ + ); + + fprintf(stderr, + "header_gap=%d lines_to_next_header=%d\n" + "screen_cols=%d screen_rows=%d\n" + "\n", + header_gap, lines_to_next_header, + screen_cols, screen_rows + ); + + fprintf(stderr, + "personality=0x%08x (from \"%s\")\n" + "EUID=%d TTY=%d,%d page_size=%d\n", + personality, saved_personality_text, + cached_euid, (int)major(cached_tty), (int)minor(cached_tty), + (int)(page_size) + ); + + fprintf(stderr, + "sizeof(proc_t)=%d sizeof(long)=%d sizeof(long)=%d\n", + (int)sizeof(proc_t), (int)sizeof(long), (int)sizeof(long) + ); + + fprintf(stderr, "archdefs:%s\n", archdefs); +} + +void __attribute__ ((__noreturn__)) +catastrophic_failure(const char *filename, + unsigned int linenum, + const char *message) +{ + error_at_line(0, 0, filename, linenum, "%s", message); + exit(EXIT_FAILURE); +} diff --git a/src/ps/help.c b/src/ps/help.c new file mode 100644 index 0000000..61e6b7c --- /dev/null +++ b/src/ps/help.c @@ -0,0 +1,223 @@ +/* + * help.c - ps help output + * + * Copyright © 2012-2023 Jim Warner <james.warner@comcast.net + * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2012-2014 Jaromir Capik <jcapik@redhat.com + * Copyright © 1998-2004 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "common.h" + + +enum { + HELP_SMP, HELP_LST, HELP_OUT, + HELP_THD, HELP_MSC, HELP_ALL, + HELP_default +}; + +static struct { + const char *word; + const char *abrv; +} help_tab[HELP_default]; + + +static int parse_help_opt (const char *opt) { +/* Translation Notes for ps Help #1 --------------------------------- + . This next group of lines represents 6 pairs of words + abbreviations + . which are the basis of the 'ps' program help text. + . + . The words and abbreviations you provide will alter program behavior. + . They will also appear in the help usage summary associated with the + . "Notes for ps Help #2" below. + . + . In their English form, help text would look like this: + . Try 'ps --help <simple|list|output|threads|misc|all>' + . or 'ps --help <s|l|o|t|m|a>' + . for additional help text. + . + . When translating these 6 pairs you may choose any appropriate + . language equivalents and the only requirement is the abbreviated + . representations must be unique. + . + . By default, those abbreviations are single characters. However, + . they are not limited to only one character after translation. + . */ + +/* Translation Hint, Pair #1 */ + help_tab[HELP_SMP].word = _("simple"); help_tab[HELP_SMP].abrv = _("s"); +/* Translation Hint, Pair #2 */ + help_tab[HELP_LST].word = _("list"); help_tab[HELP_LST].abrv = _("l"); +/* Translation Hint, Pair #3 */ + help_tab[HELP_OUT].word = _("output"); help_tab[HELP_OUT].abrv = _("o"); +/* Translation Hint, Pair #4 */ + help_tab[HELP_THD].word = _("threads"); help_tab[HELP_THD].abrv = _("t"); +/* Translation Hint, Pair #5 */ + help_tab[HELP_MSC].word = _("misc"); help_tab[HELP_MSC].abrv = _("m"); +/* Translation Hint, Pair #6 */ + help_tab[HELP_ALL].word = _("all"); help_tab[HELP_ALL].abrv = _("a"); +/* + * the above are doubled on each line so they carry the same .pot + * line # reference and thus appear more like true "pairs" even + * though xgettext will produce separate msgid/msgstr groups */ + + if(opt) { + int i; + for (i = HELP_SMP; i < HELP_default; i++) + if (!strcmp(opt, help_tab[i].word) || !strcmp(opt, help_tab[i].abrv)) + return i; + } + return HELP_default; +} + + +void do_help (const char *opt, int rc); +void do_help (const char *opt, int rc) { + FILE *out = (rc == EXIT_SUCCESS) ? stdout : stderr; + int section = parse_help_opt(opt); + + fprintf(out, _("\n" + "Usage:\n" + " %s [options]\n"), myname); + + if (section == HELP_SMP || section == HELP_ALL) { + fputs(_("\nBasic options:\n"), out); + fputs(_(" -A, -e all processes\n"), out); + fputs(_(" -a all with tty, except session leaders\n"), out); + fputs(_(" a all with tty, including other users\n"), out); + fputs(_(" -d all except session leaders\n"), out); + fputs(_(" -N, --deselect negate selection\n"), out); + fputs(_(" r only running processes\n"), out); + fputs(_(" T all processes on this terminal\n"), out); + fputs(_(" x processes without controlling ttys\n"), out); + } + if (section == HELP_LST || section == HELP_ALL) { + fputs(_("\nSelection by list:\n"), out); + fputs(_(" -C <command> command name\n"), out); + fputs(_(" -G, --Group <GID> real group id or name\n"), out); + fputs(_(" -g, --group <group> session or effective group name\n"), out); + fputs(_(" -p, p, --pid <PID> process id\n"), out); + fputs(_(" --ppid <PID> parent process id\n"), out); + fputs(_(" -q, q, --quick-pid <PID>\n" + " process id (quick mode)\n"), out); + fputs(_(" -s, --sid <session> session id\n"), out); + fputs(_(" -t, t, --tty <tty> terminal\n"), out); + fputs(_(" -u, U, --user <UID> effective user id or name\n"), out); + fputs(_(" -U, --User <UID> real user id or name\n"), out); + fputs(_("\n" + " The selection options take as their argument either:\n" + " a comma-separated list e.g. '-u root,nobody' or\n" + " a blank-separated list e.g. '-p 123 4567'\n"), out); + } + if (section == HELP_OUT || section == HELP_ALL) { + fputs(_("\nOutput formats:\n"), out); + fputs(_(" -D <format> date format for lstart\n"), out); + fputs(_(" -F extra full\n"), out); + fputs(_(" -f full-format, including command lines\n"), out); + fputs(_(" f, --forest ascii art process tree\n"), out); + fputs(_(" -H show process hierarchy\n"), out); + fputs(_(" -j jobs format\n"), out); + fputs(_(" j BSD job control format\n"), out); + fputs(_(" -l long format\n"), out); + fputs(_(" l BSD long format\n"), out); + fputs(_(" -M, Z add security data (for SELinux)\n"), out); + fputs(_(" -O <format> preloaded with default columns\n"), out); + fputs(_(" O <format> as -O, with BSD personality\n"), out); + fputs(_(" -o, o, --format <format>\n" + " user-defined format\n"), out); + fputs(_(" -P add psr column\n"), out); + fputs(_(" s signal format\n"), out); + fputs(_(" u user-oriented format\n"), out); + fputs(_(" v virtual memory format\n"), out); + fputs(_(" X register format\n"), out); + fputs(_(" -y do not show flags, show rss vs. addr (used with -l)\n"), out); + fputs(_(" --context display security context (for SELinux)\n"), out); + fputs(_(" --headers repeat header lines, one per page\n"), out); + fputs(_(" --no-headers do not print header at all\n"), out); + fputs(_(" --cols, --columns, --width <num>\n" + " set screen width\n"), out); + fputs(_(" --rows, --lines <num>\n" + " set screen height\n"), out); + fputs(_(" --signames display signal masks using signal names\n"), out); + } + if (section == HELP_THD || section == HELP_ALL) { + fputs(_("\nShow threads:\n"), out); + fputs(_(" H as if they were processes\n"), out); + fputs(_(" -L possibly with LWP and NLWP columns\n"), out); + fputs(_(" -m, m after processes\n"), out); + fputs(_(" -T possibly with SPID column\n"), out); + } + if (section == HELP_MSC || section == HELP_ALL) { + fputs(_("\nMiscellaneous options:\n"), out); + fputs(_(" -c show scheduling class with -l option\n"), out); + fputs(_(" c show true command name\n"), out); + fputs(_(" e show the environment after command\n"), out); + fputs(_(" k, --sort specify sort order as: [+|-]key[,[+|-]key[,...]]\n"), out); + fputs(_(" L show format specifiers\n"), out); + fputs(_(" n display numeric uid and wchan\n"), out); + fputs(_(" S, --cumulative include some dead child process data\n"), out); + fputs(_(" -y do not show flags, show rss (only with -l)\n"), out); + fputs(_(" -V, V, --version display version information and exit\n"), out); + fputs(_(" -w, w unlimited output width\n"), out); + fprintf(out, _("\n" + " --%s <%s|%s|%s|%s|%s|%s>\n" + " display help and exit\n") + , the_word_help + , help_tab[HELP_SMP].word, help_tab[HELP_LST].word + , help_tab[HELP_OUT].word, help_tab[HELP_THD].word + , help_tab[HELP_MSC].word, help_tab[HELP_ALL].word); + } + if (section == HELP_default) { +/* Translation Notes for ps Help #2 --------------------------------- + . Most of the following c-format string is derived from the 6 + . pairs of words + chars mentioned above in "Notes for ps Help #1". + . + . In its full English form, help text would look like this: + . Try 'ps --help <simple|list|output|threads|misc|all>' + . or 'ps --help <s|l|o|t|m|a>' + . for additional help text. + . + . The word for "help" will be translated elsewhere. Thus, the only + . translations below will be: "Try", "or" and "for additional...". + . */ + fprintf(out, _("\n" + " Try '%s --%s <%s|%s|%s|%s|%s|%s>'\n" + " or '%s --%s <%s|%s|%s|%s|%s|%s>'\n" + " for additional help text.\n") + , myname, the_word_help + , help_tab[HELP_SMP].word, help_tab[HELP_LST].word + , help_tab[HELP_OUT].word, help_tab[HELP_THD].word + , help_tab[HELP_MSC].word, help_tab[HELP_ALL].word + , myname, the_word_help + , help_tab[HELP_SMP].abrv, help_tab[HELP_LST].abrv + , help_tab[HELP_OUT].abrv, help_tab[HELP_THD].abrv + , help_tab[HELP_MSC].abrv, help_tab[HELP_ALL].abrv); + } + fprintf(out, _("\nFor more details see ps(1).\n")); + exit(rc); +} + +/* Missing: + * + * -P e k + * + */ diff --git a/src/ps/output.c b/src/ps/output.c new file mode 100644 index 0000000..a4b3833 --- /dev/null +++ b/src/ps/output.c @@ -0,0 +1,2370 @@ +/* + * output.c - ps output definitions + * + * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net + * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011 Lukas Nykryn <lnykryn@redhat.com> + * Copyright © 1999-2004 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is really gross, and I know it. I looked into several + * alternate ways to deal with the mess, and they were all ugly. + * + * FreeBSD has a fancy hack using offsets into a struct -- that + * saves code but it is _really_ gross. See the PO macro below. + * + * We could have a second column width for wide output format. + * For example, Digital prints the real-time signals. + */ + +/* + * Data table idea: + * + * table 1 maps aix to specifier + * table 2 maps shortsort to specifier + * table 3 maps macro to specifiers + * table 4 maps specifier to title,datatype,offset,vendor,helptext + * table 5 maps datatype to justification,width,widewidth,sorting,printing + * + * Here, "datatype" could be user,uid,u16,pages,deltaT,signals,tty,longtty... + * It must be enough to determine printing and sorting. + * + * After the tables, increase width as needed to fit the header. + * + * Table 5 could go in a file with the output functions. + */ + +#include <ctype.h> +#if ENABLE_LIBSELINUX +#include <dlfcn.h> +#endif +#include <ctype.h> +#include <fcntl.h> +#include <grp.h> +#include <langinfo.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <wchar.h> +#include <wctype.h> + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/resource.h> +#include <sys/types.h> + +#include "c.h" + +#include "common.h" + +/* TODO: + * Stop assuming system time is local time. + */ + +#define COLWID 240 /* satisfy snprintf, which is faster than sprintf */ +#define SIGNAL_NAME_WIDTH 27 + +static unsigned max_rightward = OUTBUF_SIZE-1; /* space for RIGHT stuff */ +static unsigned max_leftward = OUTBUF_SIZE-1; /* space for LEFT stuff */ + + +static int wide_signals; /* true if we have room */ + +static time_t seconds_since_1970; + + +extern long Hertz; + + +static unsigned int boot_time(void) +{ + static unsigned int boot_time = 0; + struct stat_info *stat_info = NULL; + if (boot_time == 0) { + if (procps_stat_new(&stat_info) < 0) + xerrx(EXIT_FAILURE, _("Unable to get system boot time")); + boot_time = STAT_GET(stat_info, STAT_SYS_TIME_OF_BOOT, ul_int); + procps_stat_unref(&stat_info); + } + return boot_time; +} + +static unsigned long memory_total() +{ + static unsigned long memory_total = 0; + struct meminfo_info *mem_info = NULL; + + if (memory_total == 0) { + if (procps_meminfo_new(&mem_info) < 0) + xerrx(EXIT_FAILURE, + _("Unable to get total memory")); + memory_total = MEMINFO_GET(mem_info, MEMINFO_MEM_TOTAL, ul_int); + procps_meminfo_unref(&mem_info); + } + return memory_total; +} + +#define SECURE_ESCAPE_ARGS(dst, bytes, cells) do { \ + if ((bytes) <= 0) return 0; \ + *(dst) = '\0'; \ + if ((bytes) >= INT_MAX) return 0; \ + if ((cells) >= INT_MAX) return 0; \ + if ((cells) <= 0) return 0; \ +} while (0) + +// copy a string that doesn't need to be 'escaped' +static int escaped_copy(char *restrict dst, const char *restrict src, int bufsize, int *maxroom){ + int n; + + SECURE_ESCAPE_ARGS(dst, bufsize, *maxroom); + if (bufsize > *maxroom+1) + bufsize = *maxroom+1; + n = snprintf(dst, bufsize, "%s", src); + if (n < 0) { + *dst = '\0'; + return 0; + } + if (n >= bufsize) + n = bufsize-1; + *maxroom -= n; + return n; +} + +// duplicated from proc/escape.c so both can be made private +static int escape_str_utf8 (char *dst, const char *src, int bufsize, int *maxcells) { + int my_cells = 0; + int my_bytes = 0; + mbstate_t s; + + SECURE_ESCAPE_ARGS(dst, bufsize, *maxcells); + + memset(&s, 0, sizeof (s)); + + for(;;) { + wchar_t wc; + int len = 0; + + if(my_cells >= *maxcells || my_bytes+1 >= bufsize) + break; + + if (!(len = mbrtowc (&wc, src, MB_CUR_MAX, &s))) + /* 'str' contains \0 */ + break; + + if (len < 0) { + /* invalid multibyte sequence -- zeroize state */ + memset (&s, 0, sizeof (s)); + *(dst++) = '?'; + src++; + my_cells++; + my_bytes++; + + } else if (len==1) { + /* non-multibyte */ + *(dst++) = isprint(*src) ? *src : '?'; + src++; + my_cells++; + my_bytes++; + + } else if (!iswprint(wc)) { + /* multibyte - no printable */ + *(dst++) = '?'; + src+=len; + my_cells++; + my_bytes++; + + } else { + /* multibyte - maybe, kinda "printable" */ + int wlen = wcwidth(wc); + // Got space? + if (wlen > *maxcells-my_cells || len >= bufsize-(my_bytes+1)) break; + // safe multibyte + memcpy(dst, src, len); + dst += len; + src += len; + my_bytes += len; + if (wlen > 0) my_cells += wlen; + } + //fprintf(stdout, "cells: %d\n", my_cells); + } + *dst = '\0'; + + // fprintf(stderr, "maxcells: %d, my_cells; %d\n", *maxcells, my_cells); + + *maxcells -= my_cells; + return my_bytes; // bytes of text, excluding the NUL +} + +// duplicated from proc/escape.c so both can be made private +static int escape_str (char *dst, const char *src, int bufsize, int *maxcells) { + unsigned char c; + int my_cells = 0; + int my_bytes = 0; + const char codes[] = + "Z..............................." + "||||||||||||||||||||||||||||||||" + "||||||||||||||||||||||||||||||||" + "|||||||||||||||||||||||||||||||." + "????????????????????????????????" + "????????????????????????????????" + "????????????????????????????????" + "????????????????????????????????"; + static int utf_init=0; + + if(utf_init==0){ + /* first call -- check if UTF stuff is usable */ + char *enc = nl_langinfo(CODESET); + utf_init = enc && strcasecmp(enc, "UTF-8")==0 ? 1 : -1; + } + if (utf_init==1 && MB_CUR_MAX>1) { + /* UTF8 locales */ + return escape_str_utf8(dst, src, bufsize, maxcells); + } + + SECURE_ESCAPE_ARGS(dst, bufsize, *maxcells); + + if(bufsize > *maxcells+1) bufsize=*maxcells+1; // FIXME: assumes 8-bit locale + + for(;;){ + if(my_cells >= *maxcells || my_bytes+1 >= bufsize) + break; + c = (unsigned char) *(src++); + if(!c) break; + if(codes[c]!='|') c=codes[c]; + my_cells++; + my_bytes++; + *(dst++) = c; + } + *dst = '\0'; + + *maxcells -= my_cells; + return my_bytes; // bytes of text, excluding the NUL +} + +/***************************************************************************/ +/************ Lots of format functions, starting with the NOP **************/ + +// so popular it can't be "static" +int pr_nop(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(noop) + (void)pp; + return snprintf(outbuf, COLWID, "%c", '-'); +} + +/********* Unix 98 ************/ +/*** + +Only comm and args are allowed to contain blank characters; all others are +not. Any implementation-dependent variables will be specified in the system +documentation along with the default header and indicating if the field +may contain blank characters. + +Some headers do not have a standardized specifier! + +%CPU pcpu The % of cpu time used recently, with unspecified "recently". +ADDR The address of the process. +C Processor utilisation for scheduling. +CMD The command name, or everything with -f. +COMMAND args Command + args. May chop as desired. May use either version. +COMMAND comm argv[0] +ELAPSED etime Elapsed time since the process was started. [[dd-]hh:]mm:ss +F Flags (octal and additive) +GROUP group Effective group ID, prefer text over decimal. +NI nice Decimal system scheduling priority, see nice(1). +PGID pgid The decimal value of the process group ID. +PID pid Decimal PID. +PPID ppid Decimal PID. +PRI Priority. Higher numbers mean lower priority. +RGROUP rgroup Real group ID, prefer text over decimal. +RUSER ruser Real user ID, prefer text over decimal. +S The state of the process. +STIME Starting time of the process. +SZ The size in blocks of the core image of the process. +TIME time Cumulative CPU time. [dd-]hh:mm:ss +TT tty Name of tty in format used by who(1). +TTY The controlling terminal for the process. +UID UID, or name when -f +USER user Effective user ID, prefer text over decimal. +VSZ vsz Virtual memory size in decimal kB. +WCHAN Where waiting/sleeping or blank if running. + +The nice value is used to compute the priority. + +For some undefined ones, Digital does: + +F flag Process flags -- but in hex! +PRI pri Process priority +S state Symbolic process status +TTY tt,tty,tname,longtname -- all do "ttyp1", "console", "??" +UID uid Process user ID (effective UID) +WCHAN wchan Address of event on which a + +For some undefined ones, Sun does: + +ADDR addr memory address of the process +C c Processor utilization for scheduling (obsolete). +CMD +F f +S s state: OSRZT +STIME start time, printed w/o blanks. If 24h old, months & days +SZ size (in pages) of the swappable process's image in main memory +TTY +UID uid +WCHAN wchan + +For some undefined ones, SCO does: +ADDR addr Virtual address of the process' entry in the process table. +SZ swappable size in kB of the virtual data and stack +STIME stime hms or md time format +***/ + +/* Source & destination are known. Return bytes or screen characters? */ +// +// OldLinux FreeBSD HPUX +// ' ' ' ' ' ' ' ' +// 'L' ' \_ ' '`-' ' ' +// '+' ' \_ ' '|-' ' ' +// '|' ' | ' '| ' ' ' +// +static int forest_helper(char *restrict const outbuf){ + char *p = forest_prefix; + char *q = outbuf; + int rightward = max_rightward < OUTBUF_SIZE ? max_rightward : OUTBUF_SIZE-1; + *q = '\0'; + if(!*p) return 0; + /* Arrrgh! somebody defined unix as 1 */ + if(forest_type == 'u') goto unixy; + while(*p){ + if (rightward < 4) break; + switch(*p){ + case ' ': strcpy(q, " "); break; + case 'L': strcpy(q, " \\_ "); break; + case '+': strcpy(q, " \\_ "); break; + case '|': strcpy(q, " | "); break; + case '\0': return q-outbuf; /* redundant & not used */ + } + q += 4; + rightward -= 4; + p++; + } + return q-outbuf; /* gcc likes this here */ +unixy: + while(*p){ + if (rightward < 2) break; + switch(*p){ + case ' ': strcpy(q, " "); break; + case 'L': strcpy(q, " "); break; + case '+': strcpy(q, " "); break; + case '|': strcpy(q, " "); break; + case '\0': return q-outbuf; /* redundant & not used */ + } + q += 2; + rightward -= 2; + p++; + } + return q-outbuf; /* gcc likes this here */ +} + + +/* XPG4-UNIX, according to Digital: +The "args" and "command" specifiers show what was passed to the command. +Modifications to the arguments are not shown. +*/ + +/* + * pp->cmd short accounting name (comm & ucomm) + * pp->cmdline long name with args (args & command) + * pp->environ environment + */ + +// FIXME: some of these may hit the guard page in forest mode + +#define OUTBUF_SIZE_AT(endp) \ + (((endp) >= outbuf && (endp) < outbuf + OUTBUF_SIZE) ? (outbuf + OUTBUF_SIZE) - (endp) : 0) + +/* + * "args", "cmd", "command" are all the same: long unless c + * "comm", "ucmd", "ucomm" are all the same: short unless -f + * ( determinations are made in display.c, we mostly deal with results ) */ +static int pr_args(char *restrict const outbuf, const proc_t *restrict const pp){ + char *endp; + int rightward, fh; +setREL3(CMDLINE,CMD,ENVIRON) + endp = outbuf; + rightward = max_rightward; + fh = forest_helper(outbuf); + endp += fh; + rightward -= fh; + if (!bsd_c_option) + endp += escape_str(endp, rSv(CMDLINE, str, pp), OUTBUF_SIZE_AT(endp), &rightward); + else + endp += escape_str(endp, rSv(CMD, str, pp), OUTBUF_SIZE_AT(endp), &rightward); + if(bsd_e_option && rightward>1) { + char *e = rSv(ENVIRON, str, pp); + if(*e != '-' || *(e+1) != '\0') { + *endp++ = ' '; + rightward--; + escape_str(endp, e, OUTBUF_SIZE_AT(endp), &rightward); + } + } + return max_rightward-rightward; +} + +/* + * "args", "cmd", "command" are all the same: long unless c + * "comm", "ucmd", "ucomm" are all the same: short unless -f + * ( determinations are made in display.c, we mostly deal with results ) */ +static int pr_comm(char *restrict const outbuf, const proc_t *restrict const pp){ + char *endp; + int rightward, fh; +setREL3(CMD,CMDLINE,ENVIRON) + endp = outbuf; + rightward = max_rightward; + fh = forest_helper(outbuf); + endp += fh; + rightward -= fh; + if(unix_f_option) + endp += escape_str(endp, rSv(CMDLINE, str, pp), OUTBUF_SIZE_AT(endp), &rightward); + else + endp += escape_str(endp, rSv(CMD, str, pp), OUTBUF_SIZE_AT(endp), &rightward); + if(bsd_e_option && rightward>1) { + char *e = rSv(ENVIRON, str, pp); + if(*e != '-' || *(e+1) != '\0') { + *endp++ = ' '; + rightward--; + escape_str(endp, e, OUTBUF_SIZE_AT(endp), &rightward); + } + } + return max_rightward-rightward; +} + +static int pr_cgname(char *restrict const outbuf,const proc_t *restrict const pp) { + int rightward; +setREL1(CGNAME) + rightward = max_rightward; + escape_str(outbuf, rSv(CGNAME, str, pp), OUTBUF_SIZE, &rightward); + return max_rightward-rightward; +} + +static int pr_cgroup(char *restrict const outbuf,const proc_t *restrict const pp) { + int rightward; +setREL1(CGROUP) + rightward = max_rightward; + escape_str(outbuf, rSv(CGROUP, str, pp), OUTBUF_SIZE, &rightward); + return max_rightward-rightward; +} + +/* Non-standard, from SunOS 5 */ +static int pr_fname(char *restrict const outbuf, const proc_t *restrict const pp){ + char *endp; + int rightward, fh; +setREL1(CMD) + endp = outbuf; + rightward = max_rightward; + fh = forest_helper(outbuf); + endp += fh; + rightward -= fh; + if (rightward>8) /* 8=default, but forest maybe feeds more */ + rightward = 8; + endp += escape_str(endp, rSv(CMD, str, pp), OUTBUF_SIZE_AT(endp), &rightward); + //return endp - outbuf; + return max_rightward-rightward; +} + +#undef OUTBUF_SIZE_AT + +/* elapsed wall clock time, [[dd-]hh:]mm:ss format (not same as "time") */ +static int pr_etime(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned long t; + unsigned dd,hh,mm,ss; + char *cp; +setREL1(TIME_ELAPSED) + cp = outbuf; + t = rSv(TIME_ELAPSED, real, pp); + ss = t%60; + t /= 60; + mm = t%60; + t /= 60; + hh = t%24; + t /= 24; + dd = t; + cp +=( dd ? snprintf(cp, COLWID, "%u-", dd) : 0 ); + cp +=( (dd || hh) ? snprintf(cp, COLWID, "%02u:", hh) : 0 ); + cp += snprintf(cp, COLWID, "%02u:%02u", mm, ss) ; + return (int)(cp-outbuf); +} + +/* elapsed wall clock time in seconds */ +static int pr_etimes(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned t; +setREL1(TIME_ELAPSED) + t = rSv(TIME_ELAPSED, real, pp); + return snprintf(outbuf, COLWID, "%u", t); +} + +/* "Processor utilisation for scheduling." --- we use %cpu w/o fraction */ +static int pr_c(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned long long total_time; /* jiffies used by this process */ + unsigned pcpu; /* scaled %cpu, 99 means 99% */ + unsigned long long jiffies; /* jiffies of process life */ +setREL4(TICS_ALL,TICS_ALL_C,TIME_ELAPSED,UTILIZATION) + pcpu = 0; + if(include_dead_children) total_time = rSv(TICS_ALL_C, ull_int, pp); + else total_time = rSv(TICS_ALL, ull_int, pp); + jiffies = rSv(TIME_ELAPSED, real, pp) * Hertz; + if(jiffies) pcpu = (total_time * 100ULL) / jiffies; + if (pcpu > 99U) pcpu = 99U; + return snprintf(outbuf, COLWID, "%2u", pcpu); +} + +/* normal %CPU in ##.# format. */ +static int pr_pcpu(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned long long total_time; /* jiffies used by this process */ + unsigned pcpu; /* scaled %cpu, 999 means 99.9% */ + unsigned long long jiffies; /* jiffies of process life */ +setREL4(TICS_ALL,TICS_ALL_C,TIME_ELAPSED,UTILIZATION) + pcpu = 0; + if(include_dead_children) total_time = rSv(TICS_ALL_C, ull_int, pp); + else total_time = rSv(TICS_ALL, ull_int, pp); + jiffies = rSv(TIME_ELAPSED, real, pp) * Hertz; + if(jiffies) pcpu = (total_time * 1000ULL) / jiffies; + if (pcpu > 999U) + return snprintf(outbuf, COLWID, "%u", pcpu/10U); + return snprintf(outbuf, COLWID, "%u.%u", pcpu/10U, pcpu%10U); +} + +/* this is a "per-mill" format, like %cpu with no decimal point */ +static int pr_cp(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned long long total_time; /* jiffies used by this process */ + unsigned pcpu; /* scaled %cpu, 999 means 99.9% */ + unsigned long long jiffies; /* jiffies of process life */ +setREL4(TICS_ALL,TICS_ALL_C,TIME_ELAPSED,UTILIZATION) + pcpu = 0; + if(include_dead_children) total_time = rSv(TICS_ALL_C, ull_int, pp); + else total_time = rSv(TICS_ALL, ull_int, pp); + jiffies = rSv(TIME_ELAPSED, real, pp) * Hertz; + if(jiffies) pcpu = (total_time * 1000ULL) / jiffies; + if (pcpu > 999U) pcpu = 999U; + return snprintf(outbuf, COLWID, "%3u", pcpu); +} + +static int pr_pgid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_PGRP) + return snprintf(outbuf, COLWID, "%u", rSv(ID_PGRP, s_int, pp)); +} +static int pr_ppid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_PPID) + return snprintf(outbuf, COLWID, "%u", rSv(ID_PPID, s_int, pp)); +} + +/* cumulative CPU time, [dd-]hh:mm:ss format (not same as "etime") */ +static int pr_time(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned long t; + unsigned dd,hh,mm,ss; + int c; +setREL1(TIME_ALL) + t = rSv(TIME_ALL, real, pp); + ss = t%60; + t /= 60; + mm = t%60; + t /= 60; + hh = t%24; + t /= 24; + dd = t; + c =( dd ? snprintf(outbuf, COLWID, "%u-", dd) : 0 ); + c +=( snprintf(outbuf+c, COLWID, "%02u:%02u:%02u", hh, mm, ss) ); + return c; +} + +/* cumulative CPU time in seconds (not same as "etimes") */ +static int pr_times(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned long t; +setREL1(TIME_ALL) + t = rSv(TIME_ALL, real, pp); + return snprintf(outbuf, COLWID, "%lu", t); +} + +/* HP-UX puts this (I forget, vsz or vsize?) in kB and uses "sz" for pages. + * Unix98 requires "vsz" to be kB. + * Tru64 does both vsize and vsz like "1.23M" + * + * Our pp->vm_size is kB and our pp->vsize is pages. + * + * TODO: add flag for "1.23M" behavior, on this and other columns. + */ +static int pr_vsz(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(VM_SIZE) + return snprintf(outbuf, COLWID, "%lu", rSv(VM_SIZE, ul_int, pp)); +} + +////////////////////////////////////////////////////////////////////////////////////// + +// "PRI" is created by "opri", or by "pri" when -c is used. +// +// Unix98 only specifies that a high "PRI" is low priority. +// Sun and SCO add the -c behavior. Sun defines "pri" and "opri". +// Linux may use "priority" for historical purposes. +// +// According to the kernel's fs/proc/array.c and kernel/sched.c source, +// the kernel reports it in /proc via this: +// p->prio - MAX_RT_PRIO +// such that "RT tasks are offset by -200. Normal tasks are centered +// around 0, value goes from -16 to +15" but who knows if that is +// before or after the conversion... +// +// <linux/sched.h> says: +// MAX_RT_PRIO is currently 100. (so we see 0 in /proc) +// RT tasks have a p->prio of 0 to 99. (so we see -100 to -1) +// non-RT tasks are from 100 to 139. (so we see 0 to 39) +// Lower values have higher priority, as in the UNIX standard. +// +// In any case, pp->priority+100 should get us back to what the kernel +// has for p->prio. +// +// Test results with the "yes" program on a 2.6.x kernel: +// +// # ps -C19,_20 -o pri,opri,intpri,priority,ni,pcpu,pid,comm +// PRI PRI PRI PRI NI %CPU PID COMMAND +// 0 99 99 39 19 10.6 8686 19 +// 34 65 65 5 -20 94.7 8687 _20 +// +// Grrr. So the UNIX standard "PRI" must NOT be from "pri". +// Either of the others will do. We use "opri" for this. +// (and use "pri" when the "-c" option is used) +// Probably we should have Linux-specific "pri_for_l" and "pri_for_lc" +// +// sched_get_priority_min.2 says the Linux static priority is +// 1..99 for RT and 0 for other... maybe 100 is kernel-only? +// +// A nice range would be -99..0 for RT and 1..40 for normal, +// which is pp->priority+1. (3-digit max, positive is normal, +// negative or 0 is RT, and meets the standard for PRI) +// + +// legal as UNIX "PRI" +// "priority" (was -20..20, now -100..39) +static int pr_priority(char *restrict const outbuf, const proc_t *restrict const pp){ /* -20..20 */ +setREL1(PRIORITY) + return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY, s_int, pp)); +} + +// legal as UNIX "PRI" +// "intpri" and "opri" (was 39..79, now -40..99) +static int pr_opri(char *restrict const outbuf, const proc_t *restrict const pp){ /* 39..79 */ +setREL1(PRIORITY) + return snprintf(outbuf, COLWID, "%d", 60 + rSv(PRIORITY, s_int, pp)); +} + +// legal as UNIX "PRI" +// "pri_foo" -- match up w/ nice values of sleeping processes (-120..19) +static int pr_pri_foo(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(PRIORITY) + return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY, s_int, pp) - 20); +} + +// legal as UNIX "PRI" +// "pri_bar" -- makes RT pri show as negative (-99..40) +static int pr_pri_bar(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(PRIORITY) + return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY, s_int, pp) + 1); +} + +// legal as UNIX "PRI" +// "pri_baz" -- the kernel's ->prio value, as of Linux 2.6.8 (1..140) +static int pr_pri_baz(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(PRIORITY) + return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY, s_int, pp) + 100); +} + +// not legal as UNIX "PRI" +// "pri" (was 20..60, now 0..139) +static int pr_pri(char *restrict const outbuf, const proc_t *restrict const pp){ /* 20..60 */ +setREL1(PRIORITY) + return snprintf(outbuf, COLWID, "%d", 39 - rSv(PRIORITY, s_int, pp)); +} + +// not legal as UNIX "PRI" +// "pri_api" -- match up w/ RT API (-40..99) +static int pr_pri_api(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(PRIORITY) + return snprintf(outbuf, COLWID, "%d", -1 - rSv(PRIORITY, s_int, pp)); +} + +// Linux applies nice value in the scheduling policies (classes) +// SCHED_OTHER(0) and SCHED_BATCH(3). Ref: sched_setscheduler(2). +// Also print nice value for old kernels which didn't use scheduling +// policies (-1). +static int pr_nice(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(NICE,SCHED_CLASS) + if(rSv(SCHED_CLASS, s_int, pp)!=0 && rSv(SCHED_CLASS, s_int, pp)!=3 && rSv(SCHED_CLASS, s_int, pp)!=-1) return snprintf(outbuf, COLWID, "-"); + return snprintf(outbuf, COLWID, "%d", rSv(NICE, s_int, pp)); +} + +static int pr_oom_adj(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(OOM_ADJ) + return snprintf(outbuf, COLWID, "%d", rSv(OOM_ADJ, s_int, pp)); +} + +static int pr_oom(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(OOM_SCORE) + return snprintf(outbuf, COLWID, "%d", rSv(OOM_SCORE, s_int, pp)); +} +// HP-UX "cls": RT RR RR2 ???? HPUX FIFO KERN +// Solaris "class": SYS TS FX IA RT FSS (FIFO is RR w/ Inf quant) +// FIFO+RR share RT; FIFO has Inf quant +// IA=interactive; FX=fixed; TS=timeshare; SYS=system +// FSS=fairshare; INTS=interrupts +// Tru64 "policy": FF RR TS +// IRIX "class": RT TS B BC WL GN +// RT=real-time; TS=time-share; B=batch; BC=batch-critical +// WL=weightless; GN=gang-scheduled +// see miser(1) for this; PRI has some letter codes too +static int pr_class(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SCHED_CLASS) + switch(rSv(SCHED_CLASS, s_int, pp)){ + case -1: return snprintf(outbuf, COLWID, "-"); // not reported + case 0: return snprintf(outbuf, COLWID, "TS"); // SCHED_OTHER SCHED_NORMAL + case 1: return snprintf(outbuf, COLWID, "FF"); // SCHED_FIFO + case 2: return snprintf(outbuf, COLWID, "RR"); // SCHED_RR + case 3: return snprintf(outbuf, COLWID, "B"); // SCHED_BATCH + case 4: return snprintf(outbuf, COLWID, "ISO"); // reserved for SCHED_ISO (Con Kolivas) + case 5: return snprintf(outbuf, COLWID, "IDL"); // SCHED_IDLE + case 6: return snprintf(outbuf, COLWID, "DLN"); // SCHED_DEADLINE + case 7: return snprintf(outbuf, COLWID, "#7"); // + case 8: return snprintf(outbuf, COLWID, "#8"); // + case 9: return snprintf(outbuf, COLWID, "#9"); // + default: return snprintf(outbuf, COLWID, "?"); // unknown value + } +} + +// Based on "type", FreeBSD would do: +// REALTIME "real:%u", prio +// NORMAL "normal" +// IDLE "idle:%u", prio +// default "%u:%u", type, prio +// We just print the priority, and have other keywords for type. +static int pr_rtprio(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(SCHED_CLASS,PRIORITY_RT) + if(rSv(SCHED_CLASS, s_int, pp)==0 || rSv(SCHED_CLASS, s_int, pp)==-1) return snprintf(outbuf, COLWID, "-"); + return snprintf(outbuf, COLWID, "%d", rSv(PRIORITY_RT, s_int, pp)); +} + +static int pr_sched(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SCHED_CLASS) + if(rSv(SCHED_CLASS, s_int, pp)==-1) return snprintf(outbuf, COLWID, "-"); + return snprintf(outbuf, COLWID, "%d", rSv(SCHED_CLASS, s_int, pp)); +} + +//////////////////////////////////////////////////////////////////////////////// + +static int pr_wchan(char *restrict const outbuf, const proc_t *restrict const pp){ +/* + * Unix98 says "blank if running" and also "no blanks"! :-( + * Unix98 also says to use '-' if something is meaningless. + * Digital uses both '*' and '-', with undocumented differences. + * (the '*' for -1 (rare) and the '-' for 0) + * Sun claims to use a blank AND use '-', in the same man page. + * Perhaps "blank" should mean '-'. + * + * AIX uses '-' for running processes, the location when there is + * only one thread waiting in the kernel, and '*' when there is + * more than one thread waiting in the kernel. + * + * The output should be truncated to maximal columns width -- overflow + * is not supported for the "wchan". + */ + const char *w; + size_t len; +setREL1(WCHAN_NAME) + w = rSv(WCHAN_NAME, str, pp); + len = strlen(w); + if(len>max_rightward) len=max_rightward; + memcpy(outbuf, w, len); + outbuf[len] = '\0'; + return len; +} + +/* Terrible trunctuation, like BSD crap uses: I999 J999 K999 */ +/* FIXME: disambiguate /dev/tty69 and /dev/pts/69. */ +static int pr_tty4(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(TTY_NUMBER) + return snprintf(outbuf, COLWID, "%s", rSv(TTY_NUMBER, str, pp)); +} + +/* Unix98: format is unspecified, but must match that used by who(1). */ +static int pr_tty8(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(TTY_NAME) + return snprintf(outbuf, COLWID, "%s", rSv(TTY_NAME, str, pp)); +} + +#if 0 +/* This BSD state display may contain spaces, which is illegal. */ +static int pr_oldstate(char *restrict const outbuf, const proc_t *restrict const pp){ + return snprintf(outbuf, COLWID, "%s", status(pp)); +} +#endif + +// This state display is Unix98 compliant and has lots of info like BSD. +static int pr_stat(char *restrict const outbuf, const proc_t *restrict const pp){ + int end; + if (!outbuf) { + chkREL(STATE) + chkREL(NICE) + chkREL(VM_RSS_LOCKED) + chkREL(ID_SESSION) + chkREL(ID_TGID) + chkREL(NLWP) + chkREL(ID_PGRP) + chkREL(ID_TPGID) + return 0; + } + end = 0; + outbuf[end++] = rSv(STATE, s_ch, pp); +// if(rSv(RSS, ul_int, pp)==0 && rSv(STATE, s_ch, pp)!='Z') outbuf[end++] = 'W'; // useless "swapped out" + if(rSv(NICE, s_int, pp) < 0) outbuf[end++] = '<'; + if(rSv(NICE, s_int, pp) > 0) outbuf[end++] = 'N'; +// In this order, NetBSD would add: +// traced 'X' +// systrace 'x' +// exiting 'E' (not printed for zombies) +// vforked 'V' +// system 'K' (and do not print 'L' too) + if(rSv(VM_RSS_LOCKED, ul_int, pp)) outbuf[end++] = 'L'; + if(rSv(ID_SESSION, s_int, pp) == rSv(ID_TGID, s_int, pp)) outbuf[end++] = 's'; // session leader + if(rSv(NLWP, s_int, pp) > 1) outbuf[end++] = 'l'; // multi-threaded + if(rSv(ID_PGRP, s_int, pp) == rSv(ID_TPGID, s_int, pp)) outbuf[end++] = '+'; // in foreground process group + outbuf[end] = '\0'; + return end; +} + +/* This minimal state display is Unix98 compliant, like SCO and SunOS 5 */ +static int pr_s(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(STATE) + outbuf[0] = rSv(STATE, s_ch, pp); + outbuf[1] = '\0'; + return 1; +} + +static int pr_flag(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(FLAGS) + /* Unix98 requires octal flags */ + /* this user-hostile and volatile junk gets 1 character */ + return snprintf(outbuf, COLWID, "%o", (unsigned)(rSv(FLAGS, ul_int, pp)>>6U)&0x7U); +} + +// plus these: euid,ruid,egroup,rgroup (elsewhere in this file) + +/*********** non-standard ***********/ + +/*** BSD +sess session pointer +(SCO has:Process session leader ID as a decimal value. (SESSION)) +jobc job control count +cpu short-term cpu usage factor (for scheduling) +sl sleep time (in seconds; 127 = infinity) +re core residency time (in seconds; 127 = infinity) +pagein pageins (same as majflt) +lim soft memory limit +tsiz text size (in Kbytes) +***/ + +static int pr_stackp(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ADDR_STACK_START) + return snprintf(outbuf, COLWID, "%0*lx", (int)(2*sizeof(long)), rSv(ADDR_STACK_START, ul_int, pp)); +} + +static int pr_esp(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ADDR_CURR_ESP) + return snprintf(outbuf, COLWID, "%0*lx", (int)(2*sizeof(long)), rSv(ADDR_CURR_ESP, ul_int, pp)); +} + +static int pr_eip(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ADDR_CURR_EIP) + return snprintf(outbuf, COLWID, "%0*lx", (int)(2*sizeof(long)), rSv(ADDR_CURR_EIP, ul_int, pp)); +} + +static int pr_bsdtime(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned long long t; + unsigned u; +setREL2(TICS_ALL,TICS_ALL_C) + if(include_dead_children) t = rSv(TICS_ALL_C, ull_int, pp); + else t = rSv(TICS_ALL, ull_int, pp); + u = t / Hertz; + return snprintf(outbuf, COLWID, "%3u:%02u", u/60U, u%60U); +} + +static int pr_bsdstart(char *restrict const outbuf, const proc_t *restrict const pp){ + time_t start; + time_t seconds_ago; +setREL1(TICS_BEGAN) + start = boot_time() + rSv(TICS_BEGAN, ull_int, pp) / Hertz; + seconds_ago = seconds_since_1970 - start; + if(seconds_ago < 0) seconds_ago=0; + if(seconds_ago > 3600*24) snprintf(outbuf, COLWID, "%s", ctime(&start)+4); + else snprintf(outbuf, COLWID, "%s", ctime(&start)+10); + outbuf[6] = '\0'; + return 6; +} + +/* HP-UX puts this in pages and uses "vsz" for kB */ +static int pr_sz(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(VM_SIZE) + return snprintf(outbuf, COLWID, "%lu", rSv(VM_SIZE, ul_int, pp)/(page_size/1024)); +} + +/* + * FIXME: trs,drs,tsiz,dsiz,m_trs,m_drs,vm_exe,vm_data,trss + * I suspect some/all of those are broken. They seem to have been + * inherited by Linux and AIX from early BSD systems. FreeBSD only + * retains tsiz. The prefixed versions come from Debian. + * Sun and Digital have none of this crap. The code here comes + * from an old Linux ps, and might not be correct for ELF executables. + * + * AIX TRS size of resident-set (real memory) of text + * AIX TSIZ size of text (shared-program) image + * FreeBSD tsiz text size (in Kbytes) + * 4.3BSD NET/2 trss text resident set size (in Kbytes) + * 4.3BSD NET/2 tsiz text size (in Kbytes) + */ + +/* kB data size. See drs, tsiz & trs. */ +static int pr_dsiz(char *restrict const outbuf, const proc_t *restrict const pp){ + long dsiz; +setREL3(VSIZE_BYTES,ADDR_CODE_END,ADDR_CODE_START) + dsiz = 0; + if(rSv(VSIZE_BYTES, ul_int, pp)) dsiz += (rSv(VSIZE_BYTES, ul_int, pp) - rSv(ADDR_CODE_END, ul_int, pp) + rSv(ADDR_CODE_START, ul_int, pp)) >> 10; + return snprintf(outbuf, COLWID, "%ld", dsiz); +} + +/* kB text (code) size. See trs, dsiz & drs. */ +static int pr_tsiz(char *restrict const outbuf, const proc_t *restrict const pp){ + long tsiz; +setREL3(VSIZE_BYTES,ADDR_CODE_END,ADDR_CODE_START) + tsiz = 0; + if(rSv(VSIZE_BYTES, ul_int, pp)) tsiz += (rSv(ADDR_CODE_END, ul_int, pp) - rSv(ADDR_CODE_START, ul_int, pp)) >> 10; + return snprintf(outbuf, COLWID, "%ld", tsiz); +} + +/* kB _resident_ data size. See dsiz, tsiz & trs. */ +static int pr_drs(char *restrict const outbuf, const proc_t *restrict const pp){ + long drs; +setREL3(VSIZE_BYTES,ADDR_CODE_END,ADDR_CODE_START) + drs = 0; + if(rSv(VSIZE_BYTES, ul_int, pp)) drs += (rSv(VSIZE_BYTES, ul_int, pp) - rSv(ADDR_CODE_END, ul_int, pp) + rSv(ADDR_CODE_START, ul_int, pp)) >> 10; + return snprintf(outbuf, COLWID, "%ld", drs); +} + +/* kB text _resident_ (code) size. See tsiz, dsiz & drs. */ +static int pr_trs(char *restrict const outbuf, const proc_t *restrict const pp){ + long trs; +setREL3(VSIZE_BYTES,ADDR_CODE_END,ADDR_CODE_START) + trs = 0; + if(rSv(VSIZE_BYTES, ul_int, pp)) trs += (rSv(ADDR_CODE_END, ul_int, pp) - rSv(ADDR_CODE_START, ul_int, pp)) >> 10; + return snprintf(outbuf, COLWID, "%ld", trs); +} + +static int pr_swapable(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL3(VM_DATA,VM_STACK,VSIZE_BYTES) // that last enum will approximate sort needs + return snprintf(outbuf, COLWID, "%lu", rSv(VM_DATA, ul_int, pp) + rSv(VM_STACK, ul_int, pp)); +} + +/* nasty old Debian thing */ +static int pr_size(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(VSIZE_BYTES) + return snprintf(outbuf, COLWID, "%lu", rSv(VSIZE_BYTES, ul_int, pp)); +} + +static int pr_minflt(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(FLT_MIN,FLT_MIN_C) + unsigned long flt = rSv(FLT_MIN, ul_int, pp); + if(include_dead_children) flt = rSv(FLT_MIN_C, ul_int, pp); + return snprintf(outbuf, COLWID, "%lu", flt); +} + +static int pr_majflt(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(FLT_MAJ,FLT_MAJ_C) + unsigned long flt = rSv(FLT_MAJ, ul_int, pp); + if(include_dead_children) flt = rSv(FLT_MAJ_C, ul_int, pp); + return snprintf(outbuf, COLWID, "%lu", flt); +} + +static int pr_lim(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(RSS_RLIM) + if(rSv(RSS_RLIM, ul_int, pp) == RLIM_INFINITY){ + outbuf[0] = 'x'; + outbuf[1] = 'x'; + outbuf[2] = '\0'; + return 2; + } + return snprintf(outbuf, COLWID, "%5lu", rSv(RSS_RLIM, ul_int, pp) >> 10L); +} + +/* should print leading tilde ('~') if process is bound to the CPU */ +static int pr_psr(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(PROCESSOR) + return snprintf(outbuf, COLWID, "%d", rSv(PROCESSOR, s_int, pp)); +} + +static int pr_pss(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SMAP_PSS) + return snprintf(outbuf, COLWID, "%lu", rSv(SMAP_PSS, ul_int, pp)); +} + +static int pr_numa(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(PROCESSOR_NODE) + return snprintf(outbuf, COLWID, "%d", rSv(PROCESSOR_NODE, s_int, pp)); +} + +static int pr_rss(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(VM_RSS) + return snprintf(outbuf, COLWID, "%lu", rSv(VM_RSS, ul_int, pp)); +} + +/* pp->vm_rss * 1000 would overflow on 32-bit systems with 64 GB memory */ +static int pr_pmem(char *restrict const outbuf, const proc_t *restrict const pp){ + unsigned long pmem; +setREL1(VM_RSS) + pmem = 0; + pmem = rSv(VM_RSS, ul_int, pp) * 1000ULL / memory_total(); + if (pmem > 999) pmem = 999; + return snprintf(outbuf, COLWID, "%2u.%u", (unsigned)(pmem/10), (unsigned)(pmem%10)); +} + +// Format cannot be %c as the length changes depending on locale +#define DEFAULT_LSTART_FORMAT "%a %b %e %H:%M:%S %Y" +static int pr_lstart(char *restrict const outbuf, const proc_t *restrict const pp){ + time_t t; + struct tm start_time; + size_t len; +setREL1(TICS_BEGAN) + t = boot_time() + rSv(TICS_BEGAN, ull_int, pp) / Hertz; + if (localtime_r(&t, &start_time) == NULL) + return 0; + len = strftime(outbuf, COLWID, + (lstart_format?lstart_format:DEFAULT_LSTART_FORMAT), &start_time); + if (len <= 0 || len >= COLWID) + outbuf[len = 0] = '\0'; + return len; +} + +/* Unix98 specifies a STIME header for a column that shows the start + * time of the process, but does not specify a format or format specifier. + * From the general Unix98 rules, we know there must not be any spaces. + * Most systems violate that rule, though the Solaris documentation + * claims to print the column without spaces. (NOT!) + * + * So this isn't broken, but could be renamed to u98_std_stime, + * as long as it still shows as STIME when using the -f option. + */ +static int pr_stime(char *restrict const outbuf, const proc_t *restrict const pp){ + struct tm proc_time; + struct tm our_time; + time_t t; + const char *fmt; + int tm_year; + int tm_yday; + size_t len; +setREL1(TICS_BEGAN) + if (localtime_r(&seconds_since_1970, &our_time) == NULL) + return 0; + tm_year = our_time.tm_year; + tm_yday = our_time.tm_yday; + t = boot_time() + rSv(TICS_BEGAN, ull_int, pp) / Hertz; + if (localtime_r(&t, &proc_time) == NULL) + return 0; + fmt = "%H:%M"; /* 03:02 23:59 */ + if(tm_yday != proc_time.tm_yday) fmt = "%b%d"; /* Jun06 Aug27 */ + if(tm_year != proc_time.tm_year) fmt = "%Y"; /* 1991 2001 */ + len = strftime(outbuf, COLWID, fmt, &proc_time); + if(len <= 0 || len >= COLWID) outbuf[len = 0] = '\0'; + return len; +} + +static int pr_start(char *restrict const outbuf, const proc_t *restrict const pp){ + time_t t; + char *str; +setREL1(TICS_BEGAN) + t = boot_time() + rSv(TICS_BEGAN, ull_int, pp) / Hertz; + str = ctime(&t); + if(str[8]==' ') str[8]='0'; + if(str[11]==' ') str[11]='0'; + if((unsigned long)t+60*60*24 > (unsigned long)seconds_since_1970) + return snprintf(outbuf, COLWID, "%8.8s", str+11); + return snprintf(outbuf, COLWID, " %6.6s", str+4); +} + +static int help_pr_sig(char *restrict const outbuf, const char *restrict const sig){ + int ret; + const size_t len = strlen(sig); + + if (signal_names) { + int rightward; + rightward = max_rightward; + if ( (ret = print_signame(outbuf, sig, rightward)) > 0) + return ret; + } + + if(wide_signals){ + if(len>8) return snprintf(outbuf, COLWID, "%s", sig); + return snprintf(outbuf, COLWID, "00000000%s", sig); + } + if(len-strspn(sig,"0") > 8) + return snprintf(outbuf, COLWID, "<%s", sig+len-8); + if(len < 8) + return snprintf(outbuf, COLWID, "%s%s", "00000000"+len, sig); + return snprintf(outbuf, COLWID, "%s", sig+len-8); +} + +// This one is always thread-specific pending. (from Dragonfly BSD) +static int pr_tsig(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SIGPENDING) + return help_pr_sig(outbuf, rSv(SIGPENDING, str, pp)); +} +// This one is (wrongly?) thread-specific when printing thread lines, +// but process-pending otherwise. +static int pr_sig(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SIGNALS) + return help_pr_sig(outbuf, rSv(SIGNALS, str, pp)); +} +static int pr_sigmask(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SIGBLOCKED) + return help_pr_sig(outbuf, rSv(SIGBLOCKED, str, pp)); +} +static int pr_sigignore(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SIGIGNORE) + return help_pr_sig(outbuf, rSv(SIGIGNORE, str, pp)); +} +static int pr_sigcatch(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SIGCATCH) + return help_pr_sig(outbuf, rSv(SIGCATCH, str, pp)); +} + +static int pr_uss(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SMAP_PRV_TOTAL) + return snprintf(outbuf, COLWID, "%lu", rSv(SMAP_PRV_TOTAL, ul_int, pp)); +} + +//////////////////////////////////////////////////////////////////////////////// + +/* + * internal terms: ruid euid suid fuid + * kernel vars: uid euid suid fsuid + * command args: ruid uid svuid n/a + */ + +static int pr_egid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_EGID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_EGID, u_int, pp)); +} +static int pr_rgid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_RGID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_RGID, u_int, pp)); +} +static int pr_sgid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_SGID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_SGID, u_int, pp)); +} +static int pr_fgid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_FGID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_FGID, u_int, pp)); +} + +static int pr_euid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_EUID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_EUID, u_int, pp)); +} +static int pr_ruid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_RUID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_RUID, u_int, pp)); +} +static int pr_suid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_SUID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_SUID, u_int, pp)); +} +static int pr_fuid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_FUID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_FUID, u_int, pp)); +} +static int pr_luid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_LOGIN) + if(rSv(ID_LOGIN, s_int, pp)==-1) return snprintf(outbuf, COLWID, "-"); + return snprintf(outbuf, COLWID, "%d", rSv(ID_LOGIN, s_int, pp)); +} + +// The Open Group Base Specifications Issue 6 (IEEE Std 1003.1, 2004 Edition) +// requires that user and group names print as decimal numbers if there is +// not enough room in the column. However, we will now truncate such names +// and provide a visual hint of such truncation. Hopefully, this will reduce +// the volume of bug reports regarding that former 'feature'. +// +// The UNIX and POSIX way to change column width is to rename it: +// ps -o pid,user=CumbersomeUserNames -o comm +// The easy way is to directly specify the desired width: +// ps -o pid,user:19,comm +// +static int do_pr_name(char *restrict const outbuf, const char *restrict const name, unsigned u){ + if(!user_is_number){ + int rightward = OUTBUF_SIZE; /* max cells */ + int len; /* real cells */ + + escape_str(outbuf, name, OUTBUF_SIZE, &rightward); + len = OUTBUF_SIZE-rightward; + + if(len <= (int)max_rightward) + return len; /* returns number of cells */ + + // only use '+' when not on a multi-byte char, else show uid + if (max_rightward >= 1 && (unsigned)outbuf[max_rightward-1] < 127) { + len = max_rightward-1; + outbuf[len++] = '+'; + outbuf[len] = 0; + return len; + } + } + return snprintf(outbuf, COLWID, "%u", u); +} + +static int pr_ruser(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(ID_RUSER,ID_RUID) + return do_pr_name(outbuf, rSv(ID_RUSER, str, pp), rSv(ID_RUID, u_int, pp)); +} +static int pr_euser(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(ID_EUSER,ID_EUID) + return do_pr_name(outbuf, rSv(ID_EUSER, str, pp), rSv(ID_EUID, u_int, pp)); +} +static int pr_fuser(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(ID_FUSER,ID_FUID) + return do_pr_name(outbuf, rSv(ID_FUSER, str, pp), rSv(ID_FUID, u_int, pp)); +} +static int pr_suser(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(ID_SUSER,ID_SUID) + return do_pr_name(outbuf, rSv(ID_SUSER, str, pp), rSv(ID_SUID, u_int, pp)); +} +static int pr_egroup(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(ID_EGROUP,ID_EGID) + return do_pr_name(outbuf, rSv(ID_EGROUP, str, pp), rSv(ID_EGID, u_int, pp)); +} +static int pr_rgroup(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(ID_RGROUP,ID_RGID) + return do_pr_name(outbuf, rSv(ID_RGROUP, str, pp), rSv(ID_RGID, u_int, pp)); +} +static int pr_fgroup(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(ID_FGROUP,ID_FGID) + return do_pr_name(outbuf, rSv(ID_FGROUP, str, pp), rSv(ID_FGID, u_int, pp)); +} +static int pr_sgroup(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL2(ID_SGROUP,ID_SGID) + return do_pr_name(outbuf, rSv(ID_SGROUP, str, pp), rSv(ID_SGID, u_int, pp)); +} + +////////////////////////////////////////////////////////////////////////////////// + +// IO stats +static int pr_rbytes(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(IO_READ_BYTES) + return snprintf(outbuf, COLWID, "%lu", rSv(IO_READ_BYTES, ul_int, pp)); +} +static int pr_rchars(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(IO_READ_CHARS) + return snprintf(outbuf, COLWID, "%lu", rSv(IO_READ_CHARS, ul_int, pp)); +} +static int pr_rops(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(IO_READ_OPS) + return snprintf(outbuf, COLWID, "%lu", rSv(IO_READ_OPS, ul_int, pp)); +} +static int pr_wbytes(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(IO_WRITE_BYTES) + return snprintf(outbuf, COLWID, "%lu", rSv(IO_WRITE_BYTES, ul_int, pp)); +} +static int pr_wcbytes(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(IO_WRITE_CBYTES) + return snprintf(outbuf, COLWID, "%lu", rSv(IO_WRITE_CBYTES, ul_int, pp)); +} +static int pr_wchars(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(IO_WRITE_CHARS) + return snprintf(outbuf, COLWID, "%lu", rSv(IO_WRITE_CHARS, ul_int, pp)); +} +static int pr_wops(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(IO_WRITE_OPS) + return snprintf(outbuf, COLWID, "%lu", rSv(IO_WRITE_OPS, ul_int, pp)); +} + +////////////////////////////////////////////////////////////////////////////////// + +// PID pid, TGID tgid +static int pr_procs(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_TGID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_TGID, s_int, pp)); +} +// LWP lwp, SPID spid, TID tid +static int pr_tasks(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_PID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_PID, s_int, pp)); +} +// thcount THCNT +static int pr_nlwp(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(NLWP) + return snprintf(outbuf, COLWID, "%d", rSv(NLWP, s_int, pp)); +} + +static int pr_sess(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_SESSION) + return snprintf(outbuf, COLWID, "%d", rSv(ID_SESSION, s_int, pp)); +} + +static int pr_supgid(char *restrict const outbuf, const proc_t *restrict const pp){ + int rightward; +setREL1(SUPGIDS) + rightward = max_rightward; + escaped_copy(outbuf, rSv(SUPGIDS, str, pp), OUTBUF_SIZE, &rightward); + return max_rightward-rightward; +} + +static int pr_supgrp(char *restrict const outbuf, const proc_t *restrict const pp){ + int rightward; +setREL1(SUPGROUPS) + rightward = max_rightward; + escape_str(outbuf, rSv(SUPGROUPS, str, pp), OUTBUF_SIZE, &rightward); + return max_rightward-rightward; +} + +static int pr_tpgid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(ID_TPGID) + return snprintf(outbuf, COLWID, "%d", rSv(ID_TPGID, s_int, pp)); +} + +/* SGI uses "cpu" to print the processor ID with header "P" */ +static int pr_sgi_p(char *restrict const outbuf, const proc_t *restrict const pp){ /* FIXME */ +setREL2(STATE,PROCESSOR) + if(rSv(STATE, s_ch, pp) == 'R') return snprintf(outbuf, COLWID, "%u", rSv(PROCESSOR, u_int, pp)); + return snprintf(outbuf, COLWID, "*"); +} + +/* full path to executable */ +static int pr_exe(char *restrict const outbuf, const proc_t *restrict const pp){ + int rightward; +setREL1(EXE) + rightward = max_rightward; + escape_str(outbuf, rSv(EXE, str, pp), OUTBUF_SIZE, &rightward); + return max_rightward-rightward; +} + +/* %cpu utilization over task lifetime, as ##.### format */ +static int pr_utilization(char *restrict const outbuf, const proc_t *restrict const pp){ +double cu; +setREL1(UTILIZATION) + cu = rSv(UTILIZATION, real, pp); + /* this check is really just for us (the ps program) since we will be very + short lived and the library might reflect 100% or even more utilization */ + if (cu > 99.0) cu = 99.999; + return snprintf(outbuf, COLWID, "%#.3f", cu); +} + +/* %cpu utilization (plus dead children) over task lifetime, as ##.### format */ +static int pr_utilization_c(char *restrict const outbuf, const proc_t *restrict const pp){ +double cu; +setREL1(UTILIZATION_C) + cu = rSv(UTILIZATION_C, real, pp); + /* this check is really just for us (the ps program) since we will be very + short lived and the library might reflect 100% or even more utilization */ + if (cu > 99.0) cu = 99.999; + return snprintf(outbuf, COLWID, "%#.3f", cu); +} + +/************************* Systemd stuff ********************************/ +static int pr_sd_unit(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SD_UNIT) + return snprintf(outbuf, COLWID, "%s", rSv(SD_UNIT, str, pp)); +} + +static int pr_sd_session(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SD_SESS) + return snprintf(outbuf, COLWID, "%s", rSv(SD_SESS, str, pp)); +} + +static int pr_sd_ouid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SD_OUID) + return snprintf(outbuf, COLWID, "%s", rSv(SD_OUID, str, pp)); +} + +static int pr_sd_machine(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SD_MACH) + return snprintf(outbuf, COLWID, "%s", rSv(SD_MACH, str, pp)); +} + +static int pr_sd_uunit(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SD_UUNIT) + return snprintf(outbuf, COLWID, "%s", rSv(SD_UUNIT, str, pp)); +} + +static int pr_sd_seat(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SD_SEAT) + return snprintf(outbuf, COLWID, "%s", rSv(SD_SEAT, str, pp)); +} + +static int pr_sd_slice(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(SD_SLICE) + return snprintf(outbuf, COLWID, "%s", rSv(SD_SLICE, str, pp)); +} +/************************ Linux namespaces ******************************/ + +#define _pr_ns(NAME, ID)\ +static int pr_##NAME(char *restrict const outbuf, const proc_t *restrict const pp) {\ + setREL1(NS_ ## ID) \ + if (rSv(NS_ ## ID, ul_int, pp)) \ + return snprintf(outbuf, COLWID, "%lu", rSv(NS_ ## ID, ul_int, pp)); \ + else \ + return snprintf(outbuf, COLWID, "-"); \ +} +_pr_ns(cgroupns, CGROUP); +_pr_ns(ipcns, IPC); +_pr_ns(mntns, MNT); +_pr_ns(netns, NET); +_pr_ns(pidns, PID); +_pr_ns(timens, TIME); +_pr_ns(userns, USER); +_pr_ns(utsns, UTS); +#undef _pr_ns + +/************************ Linux containers ******************************/ +static int pr_lxcname(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(LXCNAME) + return snprintf(outbuf, COLWID, "%s", rSv(LXCNAME, str, pp)); +} + +/****************** FLASK & seLinux security stuff **********************/ +// move the bulk of this to libproc sometime +// This needs more study, considering: +// 1. the static linking option (maybe disable this in that case) +// 2. the -z and -Z option issue +// 3. width of output +static int pr_context(char *restrict const outbuf, const proc_t *restrict const pp){ + static void (*ps_freecon)(char*); + static int (*ps_getpidcon)(pid_t pid, char **context); +#if ENABLE_LIBSELINUX + static int (*ps_is_selinux_enabled)(void); + static int tried_load; +#endif + static int selinux_enabled; + size_t len; + char *context; +setREL1(ID_TGID) + +#if ENABLE_LIBSELINUX + if(!ps_getpidcon && !tried_load){ + void *handle = dlopen("libselinux.so.1", RTLD_NOW); + if(handle){ + ps_freecon = dlsym(handle, "freecon"); + if(dlerror()) + ps_freecon = 0; + dlerror(); + ps_getpidcon = dlsym(handle, "getpidcon"); + if(dlerror()) + ps_getpidcon = 0; + ps_is_selinux_enabled = dlsym(handle, "is_selinux_enabled"); + if(dlerror()) + ps_is_selinux_enabled = 0; + else + selinux_enabled = ps_is_selinux_enabled(); + } + tried_load++; + } +#endif + if(ps_getpidcon && selinux_enabled && !ps_getpidcon(rSv(ID_TGID, s_int, pp), &context)){ + size_t max_len = OUTBUF_SIZE-1; + len = strlen(context); + if(len > max_len) len = max_len; + memcpy(outbuf, context, len); + if (len >= 1 && outbuf[len-1] == '\n') --len; + outbuf[len] = '\0'; + ps_freecon(context); + }else{ + char filename[48]; + ssize_t num_read; + int fd; + + snprintf(filename, sizeof filename, "/proc/%d/attr/current", rSv(ID_TGID, s_int, pp)); + + if ((fd = open(filename, O_RDONLY, 0)) != -1) { + num_read = read(fd, outbuf, OUTBUF_SIZE-1); + close(fd); + if (num_read > 0) { + outbuf[num_read] = '\0'; + len = 0; + while(isprint(outbuf[len])) + len++; + outbuf[len] = '\0'; + if(len) + return len; + } + } + outbuf[0] = '-'; + outbuf[1] = '\0'; + len = 1; + } + return len; +} + +/************************ Linux autogroups ******************************/ +static int pr_agid(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(AUTOGRP_ID) + return snprintf(outbuf, COLWID, "%d", rSv(AUTOGRP_ID, s_int, pp)); +} +static int pr_agnice(char *restrict const outbuf, const proc_t *restrict const pp){ +setREL1(AUTOGRP_NICE) + return snprintf(outbuf, COLWID, "%d", rSv(AUTOGRP_NICE, s_int, pp)); +} + +////////////////////////////// Test code ///////////////////////////////// + +// like "args" +static int pr_t_unlimited(char *restrict const outbuf, const proc_t *restrict const pp){ + static const char *const vals[] = {"[123456789-12345] <defunct>","ps","123456789-123456"}; + if (!outbuf) return 0; + (void)pp; + snprintf(outbuf, max_rightward+1, "%s", vals[lines_to_next_header%3u]); + return strlen(outbuf); +} +static int pr_t_unlimited2(char *restrict const outbuf, const proc_t *restrict const pp){ + static const char *const vals[] = {"unlimited", "[123456789-12345] <defunct>","ps","123456789-123456"}; + if (!outbuf) return 0; + (void)pp; + snprintf(outbuf, max_rightward+1, "%s", vals[lines_to_next_header%4u]); + return strlen(outbuf); +} + +// like "etime" +static int pr_t_right(char *restrict const outbuf, const proc_t *restrict const pp){ + static const char *const vals[] = {"999-23:59:59","99-23:59:59","9-23:59:59","59:59"}; + if (!outbuf) return 0; + (void)pp; + return snprintf(outbuf, COLWID, "%s", vals[lines_to_next_header%4u]); +} +static int pr_t_right2(char *restrict const outbuf, const proc_t *restrict const pp){ + static const char *const vals[] = {"999-23:59:59","99-23:59:59","9-23:59:59"}; + if (!outbuf) return 0; + (void)pp; + return snprintf(outbuf, COLWID, "%s", vals[lines_to_next_header%3u]); +} + +// like "tty" +static int pr_t_left(char *restrict const outbuf, const proc_t *restrict const pp){ + static const char *const vals[] = {"tty7","pts/9999","iseries/vtty42","ttySMX0","3270/tty4"}; + if (!outbuf) return 0; + (void)pp; + return snprintf(outbuf, COLWID, "%s", vals[lines_to_next_header%5u]); +} +static int pr_t_left2(char *restrict const outbuf, const proc_t *restrict const pp){ + static const char *const vals[] = {"tty7","pts/9999","ttySMX0","3270/tty4"}; + if (!outbuf) return 0; + (void)pp; + return snprintf(outbuf, COLWID, "%s", vals[lines_to_next_header%4u]); +} + +/***************************************************************************/ +/*************************** other stuff ***********************************/ + +/* + * Old header specifications. + * + * short Up " PID TTY STAT TIME COMMAND" + * long l Pp " FLAGS UID PID PPID PRI NI SIZE RSS WCHAN STA TTY TIME COMMAND + * user u up "USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND + * jobs j gPp " PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND + * sig s p " UID PID SIGNAL BLOCKED IGNORED CATCHED STAT TTY TIME COMMAND + * vm v r " PID TTY STAT TIME PAGEIN TSIZ DSIZ RSS LIM %MEM COMMAND + * m m r " PID TTY MAJFLT MINFLT TRS DRS SIZE SWAP RSS SHRD LIB DT COMMAND + * regs X p "NR PID STACK ESP EIP TMOUT ALARM STAT TTY TIME COMMAND + */ + +/* + * Unix98 requires that the heading for tty is TT, though XPG4, Digital, + * and BSD use TTY. The Unix98 headers are: + * args,comm,etime,group,nice,pcpu,pgid + * pid,ppid,rgroup,ruser,time,tty,user,vsz + * + * BSD c: "command" becomes accounting name ("comm" or "ucomm") + * BSD n: "user" becomes "uid" and "wchan" becomes "nwchan" (number) + */ + +/* Justification control for flags field. */ +#define USER CF_USER // left if text, right if numeric +#define LEFT CF_LEFT +#define RIGHT CF_RIGHT +#define UNLIMITED CF_UNLIMITED +#define WCHAN CF_WCHAN // left if text, right if numeric +#define SIGNAL CF_SIGNAL // right in 9, or 16 if room +#define PIDMAX CF_PIDMAX +#define TO CF_PRINT_THREAD_ONLY +#define PO CF_PRINT_PROCESS_ONLY +#define ET CF_PRINT_EVERY_TIME +#define AN CF_PRINT_AS_NEEDED // no idea + + +/* TODO + * pull out annoying BSD aliases into another table (to macro table?) + * add sorting functions here (to unify names) + */ + +/* temporary hack -- mark new stuff grabbed from Debian ps */ +#define LNx LNX + +/* Note: upon conversion to the <pids> API the numerous former sort provisions + for otherwise non-printable fields (pr_nop) have been retained. And, + since the new library can sort on any item, many previously printable + but unsortable fields have now been made sortable. */ +/* there are about 211 listed */ +/* Many of these are placeholders for unsupported options. */ +static const format_struct format_array[] = { /* + .spec .head .pr .sr .width .vendor .flags */ +{"%cpu", "%CPU", pr_pcpu, PIDS_UTILIZATION, 4, BSD, ET|RIGHT}, /*pcpu*/ +{"%mem", "%MEM", pr_pmem, PIDS_VM_RSS, 4, BSD, PO|RIGHT}, /*pmem*/ +{"_left", "LLLLLLLL", pr_t_left, PIDS_noop, 8, TST, ET|LEFT}, +{"_left2", "L2L2L2L2", pr_t_left2, PIDS_noop, 8, TST, ET|LEFT}, +{"_right", "RRRRRRRRRRR", pr_t_right, PIDS_noop, 11, TST, ET|RIGHT}, +{"_right2", "R2R2R2R2R2R", pr_t_right2, PIDS_noop, 11, TST, ET|RIGHT}, +{"_unlimited","U", pr_t_unlimited, PIDS_noop, 16, TST, ET|UNLIMITED}, +{"_unlimited2","U2", pr_t_unlimited2, PIDS_noop, 16, TST, ET|UNLIMITED}, +{"acflag", "ACFLG", pr_nop, PIDS_noop, 5, XXX, AN|RIGHT}, /*acflg*/ +{"acflg", "ACFLG", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, /*acflag*/ +{"addr", "ADDR", pr_nop, PIDS_noop, 4, XXX, AN|RIGHT}, +{"addr_1", "ADDR", pr_nop, PIDS_noop, 1, LNX, AN|LEFT}, +{"ag_id", "AGID", pr_agid, PIDS_AUTOGRP_ID, 5, LNX, AN|RIGHT}, +{"ag_nice", "AGNI", pr_agnice, PIDS_AUTOGRP_NICE, 4, LNX, AN|RIGHT}, +{"alarm", "ALARM", pr_nop, PIDS_noop, 5, LNX, AN|RIGHT}, +{"argc", "ARGC", pr_nop, PIDS_noop, 4, LNX, PO|RIGHT}, +{"args", "COMMAND", pr_args, PIDS_CMDLINE, 27, U98, PO|UNLIMITED}, /*command*/ +{"atime", "TIME", pr_time, PIDS_TIME_ALL, 8, SOE, ET|RIGHT}, /*cputime*/ /* was 6 wide */ +{"blocked", "BLOCKED", pr_sigmask, PIDS_SIGBLOCKED, 9, BSD, TO|SIGNAL},/*sigmask*/ +{"bnd", "BND", pr_nop, PIDS_noop, 1, AIX, TO|RIGHT}, +{"bsdstart", "START", pr_bsdstart, PIDS_TICS_BEGAN, 6, LNX, ET|RIGHT}, +{"bsdtime", "TIME", pr_bsdtime, PIDS_TICS_ALL, 6, LNX, ET|RIGHT}, +{"c", "C", pr_c, PIDS_UTILIZATION, 2, SUN, ET|RIGHT}, +{"caught", "CAUGHT", pr_sigcatch, PIDS_SIGCATCH, 9, BSD, TO|SIGNAL}, /*sigcatch*/ +{"cgname", "CGNAME", pr_cgname, PIDS_CGNAME, 27, LNX, PO|UNLIMITED}, +{"cgroup", "CGROUP", pr_cgroup, PIDS_CGROUP, 27, LNX, PO|UNLIMITED}, +{"cgroupns", "CGROUPNS",pr_cgroupns, PIDS_NS_CGROUP, 10, LNX, ET|RIGHT}, +{"class", "CLS", pr_class, PIDS_SCHED_CLASS, 3, XXX, TO|LEFT}, +{"cls", "CLS", pr_class, PIDS_SCHED_CLASS, 3, HPU, TO|RIGHT}, /*says HPUX or RT*/ +{"cmaj_flt", "-", pr_nop, PIDS_noop, 1, LNX, AN|RIGHT}, +{"cmd", "CMD", pr_args, PIDS_CMDLINE, 27, DEC, PO|UNLIMITED}, /*ucomm*/ +{"cmin_flt", "-", pr_nop, PIDS_noop, 1, LNX, AN|RIGHT}, +{"cnswap", "-", pr_nop, PIDS_noop, 1, LNX, AN|RIGHT}, +{"comm", "COMMAND", pr_comm, PIDS_CMD, 15, U98, PO|UNLIMITED}, /*ucomm*/ +{"command", "COMMAND", pr_args, PIDS_CMDLINE, 27, XXX, PO|UNLIMITED}, /*args*/ +{"context", "CONTEXT", pr_context, PIDS_ID_TGID, 31, LNX, ET|LEFT}, +{"cp", "CP", pr_cp, PIDS_UTILIZATION, 3, DEC, ET|RIGHT}, /*cpu*/ +{"cpu", "CPU", pr_nop, PIDS_noop, 3, BSD, AN|RIGHT}, /* FIXME ... HP-UX wants this as the CPU number for SMP? */ +{"cpuid", "CPUID", pr_psr, PIDS_PROCESSOR, 5, BSD, TO|RIGHT}, // OpenBSD: 8 wide! +{"cputime", "TIME", pr_time, PIDS_TIME_ALL, 8, DEC, ET|RIGHT}, /*time*/ +{"cputimes", "TIME", pr_times, PIDS_TIME_ALL, 8, LNX, ET|RIGHT}, /*time*/ +{"ctid", "CTID", pr_nop, PIDS_noop, 5, SUN, ET|RIGHT}, // resource contracts? +{"cuc", "%CUC", pr_utilization_c, PIDS_UTILIZATION_C, 7, XXX, AN|RIGHT}, +{"cursig", "CURSIG", pr_nop, PIDS_noop, 6, DEC, AN|RIGHT}, +{"cutime", "-", pr_nop, PIDS_TICS_USER_C, 1, LNX, AN|RIGHT}, +{"cuu", "%CUU", pr_utilization, PIDS_UTILIZATION, 6, XXX, AN|RIGHT}, +{"cwd", "CWD", pr_nop, PIDS_noop, 3, LNX, AN|LEFT}, +{"drs", "DRS", pr_drs, PIDS_VSIZE_BYTES, 5, LNX, PO|RIGHT}, +{"dsiz", "DSIZ", pr_dsiz, PIDS_VSIZE_BYTES, 4, LNX, PO|RIGHT}, +{"egid", "EGID", pr_egid, PIDS_ID_EGID, 5, LNX, ET|RIGHT}, +{"egroup", "EGROUP", pr_egroup, PIDS_ID_EGROUP, 8, LNX, ET|USER}, +{"eip", "EIP", pr_eip, PIDS_ADDR_CURR_EIP, (int)(2*sizeof(long)), LNX, TO|RIGHT}, +{"emul", "EMUL", pr_nop, PIDS_noop, 13, BSD, PO|LEFT}, /* "FreeBSD ELF32" and such */ +{"end_code", "E_CODE", pr_nop, PIDS_ADDR_CODE_END, (int)(2*sizeof(long)), LNx, PO|RIGHT}, // sortable, but unprintable ?? +{"environ","ENVIRONMENT",pr_nop, PIDS_noop, 11, LNx, PO|UNLIMITED}, +{"esp", "ESP", pr_esp, PIDS_ADDR_CURR_ESP, (int)(2*sizeof(long)), LNX, TO|RIGHT}, +{"etime", "ELAPSED", pr_etime, PIDS_TIME_ELAPSED, 11, U98, ET|RIGHT}, /* was 7 wide */ +{"etimes", "ELAPSED", pr_etimes, PIDS_TIME_ELAPSED, 7, BSD, ET|RIGHT}, /* FreeBSD */ +{"euid", "EUID", pr_euid, PIDS_ID_EUID, 5, LNX, ET|RIGHT}, +{"euser", "EUSER", pr_euser, PIDS_ID_EUSER, 8, LNX, ET|USER}, +{"exe", "EXE", pr_exe, PIDS_EXE, 27, LNX, PO|UNLIMITED}, +{"f", "F", pr_flag, PIDS_FLAGS, 1, XXX, ET|RIGHT}, /*flags*/ +{"fgid", "FGID", pr_fgid, PIDS_FLAGS, 5, LNX, ET|RIGHT}, +{"fgroup", "FGROUP", pr_fgroup, PIDS_ID_FGROUP, 8, LNX, ET|USER}, +{"flag", "F", pr_flag, PIDS_FLAGS, 1, DEC, ET|RIGHT}, +{"flags", "F", pr_flag, PIDS_FLAGS, 1, BSD, ET|RIGHT}, /*f*/ /* was FLAGS, 8 wide */ +{"fname", "COMMAND", pr_fname, PIDS_CMD, 8, SUN, PO|LEFT}, +{"fsgid", "FSGID", pr_fgid, PIDS_ID_FGID, 5, LNX, ET|RIGHT}, +{"fsgroup", "FSGROUP", pr_fgroup, PIDS_ID_FGROUP, 8, LNX, ET|USER}, +{"fsuid", "FSUID", pr_fuid, PIDS_ID_FUID, 5, LNX, ET|RIGHT}, +{"fsuser", "FSUSER", pr_fuser, PIDS_ID_FUSER, 8, LNX, ET|USER}, +{"fuid", "FUID", pr_fuid, PIDS_ID_FUID, 5, LNX, ET|RIGHT}, +{"fuser", "FUSER", pr_fuser, PIDS_ID_FUSER, 8, LNX, ET|USER}, +{"gid", "GID", pr_egid, PIDS_ID_EGID, 5, SUN, ET|RIGHT}, +{"group", "GROUP", pr_egroup, PIDS_ID_EGROUP, 8, U98, ET|USER}, +{"ignored", "IGNORED", pr_sigignore, PIDS_SIGIGNORE, 9, BSD, TO|SIGNAL},/*sigignore*/ +{"inblk", "INBLK", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, /*inblock*/ +{"inblock", "INBLK", pr_nop, PIDS_noop, 5, DEC, AN|RIGHT}, /*inblk*/ +{"intpri", "PRI", pr_opri, PIDS_PRIORITY, 3, HPU, TO|RIGHT}, +{"ipcns", "IPCNS", pr_ipcns, PIDS_NS_IPC, 10, LNX, ET|RIGHT}, +{"jid", "JID", pr_nop, PIDS_noop, 1, SGI, PO|RIGHT}, +{"jobc", "JOBC", pr_nop, PIDS_noop, 4, XXX, AN|RIGHT}, +{"ktrace", "KTRACE", pr_nop, PIDS_noop, 8, BSD, AN|RIGHT}, +{"ktracep", "KTRACEP", pr_nop, PIDS_noop, 8, BSD, AN|RIGHT}, +{"label", "LABEL", pr_context, PIDS_ID_TGID, 31, SGI, ET|LEFT}, +{"lastcpu", "C", pr_psr, PIDS_PROCESSOR, 3, BSD, TO|RIGHT}, // DragonFly +{"lim", "LIM", pr_lim, PIDS_RSS_RLIM, 5, BSD, AN|RIGHT}, +{"login", "LOGNAME", pr_nop, PIDS_noop, 8, BSD, AN|LEFT}, /*logname*/ /* double check */ +{"logname", "LOGNAME", pr_nop, PIDS_noop, 8, XXX, AN|LEFT}, /*login*/ +{"longtname", "TTY", pr_tty8, PIDS_TTY_NAME, 8, DEC, PO|LEFT}, +{"lsession", "SESSION", pr_sd_session, PIDS_SD_SESS, 11, LNX, ET|LEFT}, +{"lstart", "STARTED", pr_lstart, PIDS_TICS_BEGAN, 24, XXX, ET|RIGHT}, +{"luid", "LUID", pr_luid, PIDS_ID_LOGIN, 5, LNX, ET|RIGHT}, /* login ID */ +{"luser", "LUSER", pr_nop, PIDS_noop, 8, LNX, ET|USER}, /* login USER */ +{"lwp", "LWP", pr_tasks, PIDS_ID_PID, 5, SUN, TO|PIDMAX|RIGHT}, +{"lxc", "LXC", pr_lxcname, PIDS_LXCNAME, 8, LNX, ET|LEFT}, +{"m_drs", "DRS", pr_drs, PIDS_VSIZE_BYTES, 5, LNx, PO|RIGHT}, +{"m_dt", "DT", pr_nop, PIDS_noop, 4, LNx, PO|RIGHT}, +{"m_lrs", "LRS", pr_nop, PIDS_noop, 5, LNx, PO|RIGHT}, +{"m_resident", "RES", pr_nop, PIDS_MEM_RES_PGS, 5, LNx, PO|RIGHT}, +{"m_share", "SHRD", pr_nop, PIDS_MEM_SHR_PGS, 5, LNx, PO|RIGHT}, +{"m_size", "SIZE", pr_size, PIDS_VSIZE_BYTES, 5, LNX, PO|RIGHT}, +{"m_swap", "SWAP", pr_nop, PIDS_noop, 5, LNx, PO|RIGHT}, +{"m_trs", "TRS", pr_trs, PIDS_VSIZE_BYTES, 5, LNx, PO|RIGHT}, +{"machine", "MACHINE", pr_sd_machine, PIDS_SD_MACH, 31, LNX, ET|LEFT}, +{"maj_flt", "MAJFL", pr_majflt, PIDS_FLT_MAJ, 6, LNX, AN|RIGHT}, +{"majflt", "MAJFLT", pr_majflt, PIDS_FLT_MAJ, 6, XXX, AN|RIGHT}, +{"min_flt", "MINFL", pr_minflt, PIDS_FLT_MIN, 6, LNX, AN|RIGHT}, +{"minflt", "MINFLT", pr_minflt, PIDS_FLT_MIN, 6, XXX, AN|RIGHT}, +{"mntns", "MNTNS", pr_mntns, PIDS_NS_MNT, 10, LNX, ET|RIGHT}, +{"msgrcv", "MSGRCV", pr_nop, PIDS_noop, 6, XXX, AN|RIGHT}, +{"msgsnd", "MSGSND", pr_nop, PIDS_noop, 6, XXX, AN|RIGHT}, +{"mwchan", "MWCHAN", pr_nop, PIDS_noop, 6, BSD, TO|WCHAN}, /* mutex (FreeBSD) */ +{"netns", "NETNS", pr_netns, PIDS_NS_NET, 10, LNX, ET|RIGHT}, +{"ni", "NI", pr_nice, PIDS_NICE, 3, BSD, TO|RIGHT}, /*nice*/ +{"nice", "NI", pr_nice, PIDS_NICE, 3, U98, TO|RIGHT}, /*ni*/ +{"nivcsw", "IVCSW", pr_nop, PIDS_noop, 5, XXX, AN|RIGHT}, +{"nlwp", "NLWP", pr_nlwp, PIDS_NLWP, 4, SUN, PO|RIGHT}, +{"nsignals", "NSIGS", pr_nop, PIDS_noop, 5, DEC, AN|RIGHT}, /*nsigs*/ +{"nsigs", "NSIGS", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, /*nsignals*/ +{"nswap", "NSWAP", pr_nop, PIDS_noop, 5, XXX, AN|RIGHT}, +{"numa", "NUMA", pr_numa, PIDS_PROCESSOR_NODE, 4, XXX, AN|RIGHT}, +{"nvcsw", "VCSW", pr_nop, PIDS_noop, 5, XXX, AN|RIGHT}, +{"nwchan", "WCHAN", pr_nop, PIDS_noop, 6, XXX, TO|RIGHT}, +{"oom", "OOM", pr_oom, PIDS_OOM_SCORE, 4, XXX, TO|RIGHT}, +{"oomadj", "OOMADJ", pr_oom_adj, PIDS_OOM_ADJ, 5, XXX, TO|RIGHT}, +{"opri", "PRI", pr_opri, PIDS_PRIORITY, 3, SUN, TO|RIGHT}, +{"osz", "SZ", pr_nop, PIDS_noop, 2, SUN, PO|RIGHT}, +{"oublk", "OUBLK", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, /*oublock*/ +{"oublock", "OUBLK", pr_nop, PIDS_noop, 5, DEC, AN|RIGHT}, /*oublk*/ +{"ouid", "OWNER", pr_sd_ouid, PIDS_SD_OUID, 5, LNX, ET|LEFT}, +{"p_ru", "P_RU", pr_nop, PIDS_noop, 6, BSD, AN|RIGHT}, +{"paddr", "PADDR", pr_nop, PIDS_noop, 6, BSD, AN|RIGHT}, +{"pagein", "PAGEIN", pr_majflt, PIDS_FLT_MAJ, 6, XXX, AN|RIGHT}, +{"pcpu", "%CPU", pr_pcpu, PIDS_UTILIZATION, 4, U98, ET|RIGHT}, /*%cpu*/ +{"pending", "PENDING", pr_sig, PIDS_SIGNALS, 9, BSD, ET|SIGNAL}, /*sig*/ +{"pgid", "PGID", pr_pgid, PIDS_ID_PGRP, 5, U98, PO|PIDMAX|RIGHT}, +{"pgrp", "PGRP", pr_pgid, PIDS_ID_PGRP, 5, LNX, PO|PIDMAX|RIGHT}, +{"pid", "PID", pr_procs, PIDS_ID_TGID, 5, U98, PO|PIDMAX|RIGHT}, +{"pidns", "PIDNS", pr_pidns, PIDS_NS_PID, 10, LNX, ET|RIGHT}, +{"pmem", "%MEM", pr_pmem, PIDS_VM_RSS, 4, XXX, PO|RIGHT}, /* %mem */ +{"poip", "-", pr_nop, PIDS_noop, 1, BSD, AN|RIGHT}, +{"policy", "POL", pr_class, PIDS_SCHED_CLASS, 3, DEC, TO|LEFT}, +{"ppid", "PPID", pr_ppid, PIDS_ID_PPID, 5, U98, PO|PIDMAX|RIGHT}, +{"pri", "PRI", pr_pri, PIDS_PRIORITY, 3, XXX, TO|RIGHT}, +{"pri_api", "API", pr_pri_api, PIDS_PRIORITY, 3, LNX, TO|RIGHT}, +{"pri_bar", "BAR", pr_pri_bar, PIDS_PRIORITY, 3, LNX, TO|RIGHT}, +{"pri_baz", "BAZ", pr_pri_baz, PIDS_PRIORITY, 3, LNX, TO|RIGHT}, +{"pri_foo", "FOO", pr_pri_foo, PIDS_PRIORITY, 3, LNX, TO|RIGHT}, +{"priority", "PRI", pr_priority, PIDS_PRIORITY, 3, LNX, TO|RIGHT}, +{"prmgrp", "PRMGRP", pr_nop, PIDS_noop, 12, HPU, PO|RIGHT}, +{"prmid", "PRMID", pr_nop, PIDS_noop, 12, HPU, PO|RIGHT}, +{"project", "PROJECT", pr_nop, PIDS_noop, 12, SUN, PO|LEFT}, // see prm* andctid +{"projid", "PROJID", pr_nop, PIDS_noop, 5, SUN, PO|RIGHT}, +{"pset", "PSET", pr_nop, PIDS_noop, 4, DEC, TO|RIGHT}, +{"psr", "PSR", pr_psr, PIDS_PROCESSOR, 3, DEC, TO|RIGHT}, +{"pss", "PSS", pr_pss, PIDS_SMAP_PSS, 5, XXX, PO|RIGHT}, +{"psxpri", "PPR", pr_nop, PIDS_noop, 3, DEC, TO|RIGHT}, +{"rbytes", "RBYTES", pr_rbytes, PIDS_IO_READ_BYTES, 5, LNX, TO|RIGHT}, +{"rchars", "RCHARS", pr_rchars, PIDS_IO_READ_CHARS, 5, LNX, TO|RIGHT}, +{"re", "RE", pr_nop, PIDS_noop, 3, BSD, AN|RIGHT}, +{"resident", "RES", pr_nop, PIDS_MEM_RES_PGS, 5, LNX, PO|RIGHT}, +{"rgid", "RGID", pr_rgid, PIDS_ID_RGID, 5, XXX, ET|RIGHT}, +{"rgroup", "RGROUP", pr_rgroup, PIDS_ID_RGROUP, 8, U98, ET|USER}, /* was 8 wide */ +{"rlink", "RLINK", pr_nop, PIDS_noop, 8, BSD, AN|RIGHT}, +{"rops", "ROPS", pr_rops, PIDS_IO_READ_OPS, 5, LNX, TO|RIGHT}, +{"rss", "RSS", pr_rss, PIDS_VM_RSS, 5, XXX, PO|RIGHT}, /* was 5 wide */ +{"rssize", "RSS", pr_rss, PIDS_VM_RSS, 5, DEC, PO|RIGHT}, /*rsz*/ +{"rsz", "RSZ", pr_rss, PIDS_VM_RSS, 5, BSD, PO|RIGHT}, /*rssize*/ +{"rtprio", "RTPRIO", pr_rtprio, PIDS_PRIORITY_RT, 6, BSD, TO|RIGHT}, +{"ruid", "RUID", pr_ruid, PIDS_ID_RUID, 5, XXX, ET|RIGHT}, +{"ruser", "RUSER", pr_ruser, PIDS_ID_RUSER, 8, U98, ET|USER}, +{"s", "S", pr_s, PIDS_STATE, 1, SUN, TO|LEFT}, /*stat,state*/ +{"sched", "SCH", pr_sched, PIDS_SCHED_CLASS, 3, AIX, TO|RIGHT}, +{"scnt", "SCNT", pr_nop, PIDS_noop, 4, DEC, AN|RIGHT}, /* man page misspelling of scount? */ +{"scount", "SC", pr_nop, PIDS_noop, 4, AIX, AN|RIGHT}, /* scnt==scount, DEC claims both */ +{"seat", "SEAT", pr_sd_seat, PIDS_SD_SEAT, 11, LNX, ET|LEFT}, +{"sess", "SESS", pr_sess, PIDS_ID_SESSION, 5, XXX, PO|PIDMAX|RIGHT}, +{"session", "SESS", pr_sess, PIDS_ID_SESSION, 5, LNX, PO|PIDMAX|RIGHT}, +{"sgi_p", "P", pr_sgi_p, PIDS_STATE, 1, LNX, TO|RIGHT}, /* "cpu" number */ +{"sgi_rss", "RSS", pr_rss, PIDS_VM_RSS, 4, LNX, PO|LEFT}, /* SZ:RSS */ +{"sgid", "SGID", pr_sgid, PIDS_ID_SGID, 5, LNX, ET|RIGHT}, +{"sgroup", "SGROUP", pr_sgroup, PIDS_ID_SGROUP, 8, LNX, ET|USER}, +{"share", "-", pr_nop, PIDS_noop, 1, LNX, PO|RIGHT}, +{"sid", "SID", pr_sess, PIDS_ID_SESSION, 5, XXX, PO|PIDMAX|RIGHT}, /* Sun & HP */ +{"sig", "PENDING", pr_sig, PIDS_SIGNALS, 9, XXX, ET|SIGNAL}, /*pending -- Dragonfly uses this for whole-proc and "tsig" for thread */ +{"sig_block", "BLOCKED", pr_sigmask, PIDS_SIGBLOCKED, 9, LNX, TO|SIGNAL}, +{"sig_catch", "CATCHED", pr_sigcatch, PIDS_SIGCATCH, 9, LNX, TO|SIGNAL}, +{"sig_ignore", "IGNORED",pr_sigignore, PIDS_SIGIGNORE, 9, LNX, TO|SIGNAL}, +{"sig_pend", "SIGNAL", pr_sig, PIDS_SIGNALS, 9, LNX, ET|SIGNAL}, +{"sigcatch", "CAUGHT", pr_sigcatch, PIDS_SIGCATCH, 9, XXX, TO|SIGNAL}, /*caught*/ +{"sigignore", "IGNORED", pr_sigignore, PIDS_SIGIGNORE, 9, XXX, TO|SIGNAL}, /*ignored*/ +{"sigmask", "BLOCKED", pr_sigmask, PIDS_SIGBLOCKED, 9, XXX, TO|SIGNAL}, /*blocked*/ +{"size", "SIZE", pr_swapable, PIDS_VSIZE_BYTES, 5, SCO, PO|RIGHT}, +{"sl", "SL", pr_nop, PIDS_noop, 3, XXX, AN|RIGHT}, +{"slice", "SLICE", pr_sd_slice, PIDS_SD_SLICE, 31, LNX, ET|LEFT}, +{"spid", "SPID", pr_tasks, PIDS_ID_PID, 5, SGI, TO|PIDMAX|RIGHT}, +{"stackp", "STACKP", pr_stackp, PIDS_ADDR_STACK_START, (int)(2*sizeof(long)), LNX, PO|RIGHT}, /*start_stack*/ +{"start", "STARTED", pr_start, PIDS_TICS_BEGAN, 8, XXX, ET|RIGHT}, +{"start_code", "S_CODE", pr_nop, PIDS_ADDR_CODE_START, (int)(2*sizeof(long)), LNx, PO|RIGHT}, // sortable, but unprintable ?? +{"start_stack", "STACKP",pr_stackp, PIDS_ADDR_STACK_START, (int)(2*sizeof(long)), LNX, PO|RIGHT}, /*stackp*/ +{"start_time", "START", pr_stime, PIDS_TICS_BEGAN, 5, LNx, ET|RIGHT}, +{"stat", "STAT", pr_stat, PIDS_STATE, 4, BSD, TO|LEFT}, /*state,s*/ +{"state", "S", pr_s, PIDS_STATE, 1, XXX, TO|LEFT}, /*stat,s*/ /* was STAT */ +{"status", "STATUS", pr_nop, PIDS_noop, 6, DEC, AN|RIGHT}, +{"stime", "STIME", pr_stime, PIDS_TICS_BEGAN, 5, XXX, ET|RIGHT}, /* was 6 wide */ +{"suid", "SUID", pr_suid, PIDS_ID_SUID, 5, LNx, ET|RIGHT}, +{"supgid", "SUPGID", pr_supgid, PIDS_SUPGIDS, 20, LNX, PO|UNLIMITED}, +{"supgrp", "SUPGRP", pr_supgrp, PIDS_SUPGROUPS, 40, LNX, PO|UNLIMITED}, +{"suser", "SUSER", pr_suser, PIDS_ID_SUSER, 8, LNx, ET|USER}, +{"svgid", "SVGID", pr_sgid, PIDS_ID_SGID, 5, XXX, ET|RIGHT}, +{"svgroup", "SVGROUP", pr_sgroup, PIDS_ID_SGROUP, 8, LNX, ET|USER}, +{"svuid", "SVUID", pr_suid, PIDS_ID_SUID, 5, XXX, ET|RIGHT}, +{"svuser", "SVUSER", pr_suser, PIDS_ID_SUSER, 8, LNX, ET|USER}, +{"systime", "SYSTEM", pr_nop, PIDS_noop, 6, DEC, ET|RIGHT}, +{"sz", "SZ", pr_sz, PIDS_VM_SIZE, 5, HPU, PO|RIGHT}, +{"taskid", "TASKID", pr_nop, PIDS_noop, 5, SUN, TO|PIDMAX|RIGHT}, // is this a thread ID? +{"tdev", "TDEV", pr_nop, PIDS_noop, 4, XXX, AN|RIGHT}, +{"tgid", "TGID", pr_procs, PIDS_ID_TGID, 5, LNX, PO|PIDMAX|RIGHT}, +{"thcount", "THCNT", pr_nlwp, PIDS_NLWP, 5, AIX, PO|RIGHT}, +{"tid", "TID", pr_tasks, PIDS_ID_PID, 5, AIX, TO|PIDMAX|RIGHT}, +{"time", "TIME", pr_time, PIDS_TIME_ALL, 8, U98, ET|RIGHT}, /*cputime*/ /* was 6 wide */ +{"timens", "TIMENS", pr_timens, PIDS_NS_TIME, 10, LNX, ET|RIGHT}, +{"timeout", "TMOUT", pr_nop, PIDS_noop, 5, LNX, AN|RIGHT}, // 2.0.xx era +{"times", "TIME", pr_times, PIDS_TIME_ALL, 8, LNX, ET|RIGHT}, +{"tmout", "TMOUT", pr_nop, PIDS_noop, 5, LNX, AN|RIGHT}, // 2.0.xx era +{"tname", "TTY", pr_tty8, PIDS_TTY_NAME, 8, DEC, PO|LEFT}, +{"tpgid", "TPGID", pr_tpgid, PIDS_ID_TPGID, 5, XXX, PO|PIDMAX|RIGHT}, +{"trs", "TRS", pr_trs, PIDS_VSIZE_BYTES, 4, AIX, PO|RIGHT}, +{"trss", "TRSS", pr_trs, PIDS_VSIZE_BYTES, 4, BSD, PO|RIGHT}, /* 4.3BSD NET/2 */ +{"tsess", "TSESS", pr_nop, PIDS_noop, 5, BSD, PO|PIDMAX|RIGHT}, +{"tsession", "TSESS", pr_nop, PIDS_noop, 5, DEC, PO|PIDMAX|RIGHT}, +{"tsid", "TSID", pr_nop, PIDS_noop, 5, BSD, PO|PIDMAX|RIGHT}, +{"tsig", "PENDING", pr_tsig, PIDS_SIGPENDING, 9, BSD, ET|SIGNAL}, /* Dragonfly used this for thread-specific, and "sig" for whole-proc */ +{"tsiz", "TSIZ", pr_tsiz, PIDS_VSIZE_BYTES, 4, BSD, PO|RIGHT}, +{"tt", "TT", pr_tty8, PIDS_TTY_NAME, 8, BSD, PO|LEFT}, +{"tty", "TT", pr_tty8, PIDS_TTY_NAME, 8, U98, PO|LEFT}, /* Unix98 requires "TT" but has "TTY" too. :-( */ /* was 3 wide */ +{"tty4", "TTY", pr_tty4, PIDS_TTY_NAME, 4, LNX, PO|LEFT}, +{"tty8", "TTY", pr_tty8, PIDS_TTY_NAME, 8, LNX, PO|LEFT}, +{"u_procp", "UPROCP", pr_nop, PIDS_noop, 6, DEC, AN|RIGHT}, +{"ucmd", "CMD", pr_comm, PIDS_CMD, 15, DEC, PO|UNLIMITED}, /*ucomm*/ +{"ucomm", "COMMAND", pr_comm, PIDS_CMD, 15, XXX, PO|UNLIMITED}, /*comm*/ +{"uid", "UID", pr_euid, PIDS_ID_EUID, 5, XXX, ET|RIGHT}, +{"uid_hack", "UID", pr_euser, PIDS_ID_EUSER, 8, XXX, ET|USER}, +{"umask", "UMASK", pr_nop, PIDS_noop, 5, DEC, AN|RIGHT}, +{"uname", "USER", pr_euser, PIDS_ID_EUSER, 8, DEC, ET|USER}, /* man page misspelling of user? */ +{"unit", "UNIT", pr_sd_unit, PIDS_SD_UNIT, 31, LNX, ET|LEFT}, +{"upr", "UPR", pr_nop, PIDS_noop, 3, BSD, TO|RIGHT}, /*usrpri*/ +{"uprocp", "UPROCP", pr_nop, PIDS_noop, 8, BSD, AN|RIGHT}, +{"user", "USER", pr_euser, PIDS_ID_EUSER, 8, U98, ET|USER}, /* BSD n forces this to UID */ +{"userns", "USERNS", pr_userns, PIDS_NS_USER, 10, LNX, ET|RIGHT}, +{"usertime", "USER", pr_nop, PIDS_noop, 4, DEC, ET|RIGHT}, +{"usrpri", "UPR", pr_nop, PIDS_noop, 3, DEC, TO|RIGHT}, /*upr*/ +{"uss", "USS", pr_uss, PIDS_SMAP_PRV_TOTAL, 5, XXX, PO|RIGHT}, +{"util", "C", pr_c, PIDS_UTILIZATION, 2, SGI, ET|RIGHT}, // not sure about "C" +{"utime", "UTIME", pr_nop, PIDS_TICS_USER, 6, LNx, ET|RIGHT}, +{"utsns", "UTSNS", pr_utsns, PIDS_NS_UTS, 10, LNX, ET|RIGHT}, +{"uunit", "UUNIT", pr_sd_uunit, PIDS_SD_UUNIT, 31, LNX, ET|LEFT}, +{"vm_data", "DATA", pr_nop, PIDS_VM_DATA, 5, LNx, PO|RIGHT}, +{"vm_exe", "EXE", pr_nop, PIDS_VM_EXE, 5, LNx, PO|RIGHT}, +{"vm_lib", "LIB", pr_nop, PIDS_VM_LIB, 5, LNx, PO|RIGHT}, +{"vm_lock", "LCK", pr_nop, PIDS_VM_RSS_LOCKED, 3, LNx, PO|RIGHT}, +{"vm_stack", "STACK", pr_nop, PIDS_VM_STACK, 5, LNx, PO|RIGHT}, +{"vsize", "VSZ", pr_vsz, PIDS_VSIZE_BYTES, 6, DEC, PO|RIGHT}, /*vsz*/ +{"vsz", "VSZ", pr_vsz, PIDS_VM_SIZE, 6, U98, PO|RIGHT}, /*vsize*/ +{"wbytes", "WBYTES", pr_wbytes, PIDS_IO_WRITE_BYTES, 5, LNX, TO|RIGHT}, +{"wcbytes", "WCBYTES", pr_wcbytes, PIDS_IO_WRITE_CBYTES, 5, LNX, TO|RIGHT}, +{"wchan", "WCHAN", pr_wchan, PIDS_WCHAN_NAME, 6, XXX, TO|WCHAN}, /* BSD n forces this to nwchan */ /* was 10 wide */ +{"wchars", "WCHARS", pr_wchars, PIDS_IO_WRITE_CHARS, 5, LNX, TO|RIGHT}, +{"wname", "WCHAN", pr_wchan, PIDS_WCHAN_NAME, 6, SGI, TO|WCHAN}, /* opposite of nwchan */ +{"wops", "WOPS", pr_wops, PIDS_IO_WRITE_OPS, 5, LNX, TO|RIGHT}, +{"xstat", "XSTAT", pr_nop, PIDS_noop, 5, BSD, AN|RIGHT}, +{"zone", "ZONE", pr_context, PIDS_ID_TGID, 31, SUN, ET|LEFT}, // Solaris zone == Linux context? +{"zoneid", "ZONEID", pr_nop, PIDS_noop, 31, SUN, ET|RIGHT}, // Linux only offers context names +{"~", "-", pr_nop, PIDS_noop, 1, LNX, AN|RIGHT} /* NULL would ruin alphabetical order */ +}; + +#undef USER +#undef LEFT +#undef RIGHT +#undef UNLIMITED +#undef WCHAN +#undef SIGNAL +#undef PIDMAX +#undef PO +#undef TO +#undef AN +#undef ET + +static const int format_array_count = sizeof(format_array)/sizeof(format_struct); + + +/****************************** Macro formats *******************************/ +/* First X field may be NR, which is p->start_code>>26 printed with %2ld */ +/* That seems useless though, and Debian already killed it. */ +/* The ones marked "Digital" have the name defined, not just the data. */ +static const macro_struct macro_array[] = { +{"DFMT", "pid,tname,state,cputime,cmd"}, /* Digital's default */ +{"DefBSD", "pid,tname,stat,bsdtime,args"}, /* Our BSD default */ +{"DefSysV", "pid,tname,time,cmd"}, /* Our SysV default */ +{"END_BSD", "state,tname,cputime,comm"}, /* trailer for O */ +{"END_SYS5", "state,tname,time,command"}, /* trailer for -O */ +{"F5FMT", "uname,pid,ppid,c,start,tname,time,cmd"}, /* Digital -f */ + +{"FB_", "pid,tt,stat,time,command"}, /* FreeBSD default */ +{"FB_j", "user,pid,ppid,pgid,sess,jobc,stat,tt,time,command"}, /* FreeBSD j */ +{"FB_l", "uid,pid,ppid,cpu,pri,nice,vsz,rss,wchan,stat,tt,time,command"}, /* FreeBSD l */ +{"FB_u", "user,pid,pcpu,pmem,vsz,rss,tt,stat,start,time,command"}, /* FreeBSD u */ +{"FB_v", "pid,stat,time,sl,re,pagein,vsz,rss,lim,tsiz,pcpu,pmem,command"}, /* FreeBSD v */ + +{"FD_", "pid,tty,time,comm"}, /* Fictional Debian SysV default */ +{"FD_f", "user,pid,ppid,start_time,tty,time,comm"}, /* Fictional Debian -f */ +{"FD_fj", "user,pid,ppid,start_time,tty,time,pgid,sid,comm"}, /* Fictional Debian -jf */ +{"FD_j", "pid,tty,time,pgid,sid,comm"}, /* Fictional Debian -j */ +{"FD_l", "flags,state,uid,pid,ppid,priority,nice,vsz,wchan,tty,time,comm"}, /* Fictional Debian -l */ +{"FD_lj", "flags,state,uid,pid,ppid,priority,nice,vsz,wchan,tty,time,pgid,sid,comm"}, /* Fictional Debian -jl */ + +{"FL5FMT", "f,state,uid,pid,ppid,pcpu,pri,nice,rss,wchan,start,time,command"}, /* Digital -fl */ + +{"FLASK_context", "pid,context,command"}, /* Flask Linux context, --context */ + +{"HP_", "pid,tty,time,comm"}, /* HP default */ +{"HP_f", "user,pid,ppid,cpu,stime,tty,time,args"}, /* HP -f */ +{"HP_fl", "flags,state,user,pid,ppid,cpu,intpri,nice,addr,sz,wchan,stime,tty,time,args"}, /* HP -fl */ +{"HP_l", "flags,state,uid,pid,ppid,cpu,intpri,nice,addr,sz,wchan,tty,time,comm"}, /* HP -l */ + +{"J390", "pid,sid,pgrp,tname,atime,args"}, /* OS/390 -j */ +{"JFMT", "user,pid,ppid,pgid,sess,jobc,state,tname,cputime,command"}, /* Digital j and -j */ +{"L5FMT", "f,state,uid,pid,ppid,c,pri,nice,addr,sz,wchan,tt,time,ucmd"}, /* Digital -l */ +{"LFMT", "uid,pid,ppid,cp,pri,nice,vsz,rss,wchan,state,tname,cputime,command"}, /* Digital l */ + +{"OL_X", "pid,start_stack,esp,eip,timeout,alarm,stat,tname,bsdtime,args"}, /* Old i386 Linux X */ +{"OL_j", "ppid,pid,pgid,sid,tname,tpgid,stat,uid,bsdtime,args"}, /* Old Linux j */ +{"OL_l", "flags,uid,pid,ppid,priority,nice,vsz,rss,wchan,stat,tname,bsdtime,args"}, /* Old Linux l */ +{"OL_m", "pid,tname,majflt,minflt,m_trs,m_drs,m_size,m_swap,rss,m_share,vm_lib,m_dt,args"}, /* Old Linux m */ +{"OL_s", "uid,pid,pending,sig_block,sig_ignore,caught,stat,tname,bsdtime,args"}, /* Old Linux s */ +{"OL_u", "user,pid,pcpu,pmem,vsz,rss,tname,stat,start_time,bsdtime,args"}, /* Old Linux u */ +{"OL_v", "pid,tname,stat,bsdtime,maj_flt,m_trs,m_drs,rss,pmem,args"}, /* Old Linux v */ + +{"RD_", "pid,tname,state,bsdtime,comm"}, /* Real Debian default */ +{"RD_f", "uid,pid,ppid,start_time,tname,bsdtime,args"}, /* Real Debian -f */ +{"RD_fj", "uid,pid,ppid,start_time,tname,bsdtime,pgid,sid,args"}, /* Real Debian -jf */ +{"RD_j", "pid,tname,state,bsdtime,pgid,sid,comm"}, /* Real Debian -j */ +{"RD_l", "flags,state,uid,pid,ppid,priority,nice,wchan,tname,bsdtime,comm"}, /* Real Debian -l */ +{"RD_lj", "flags,state,uid,pid,ppid,priority,nice,wchan,tname,bsdtime,pgid,sid,comm"}, /* Real Debian -jl */ + +{"RUSAGE", "minflt,majflt,nswap,inblock,oublock,msgsnd,msgrcv,nsigs,nvcsw,nivcsw"}, /* Digital -o "RUSAGE" */ +{"SCHED", "user,pcpu,pri,usrpri,nice,psxpri,psr,policy,pset"}, /* Digital -o "SCHED" */ +{"SFMT", "uid,pid,cursig,sig,sigmask,sigignore,sigcatch,stat,tname,command"}, /* Digital s */ + +{"Std_f", "uid_hack,pid,ppid,c,stime,tname,time,cmd"}, /* new -f */ +{"Std_fl", "f,s,uid_hack,pid,ppid,c,opri,ni,addr,sz,wchan,stime,tname,time,cmd"}, /* -fl */ +{"Std_l", "f,s,uid,pid,ppid,c,opri,ni,addr,sz,wchan,tname,time,ucmd"}, /* new -l */ + +{"THREAD", "user,pcpu,pri,scnt,wchan,usertime,systime"}, /* Digital -o "THREAD" */ +{"UFMT", "uname,pid,pcpu,pmem,vsz,rss,tt,state,start,time,command"}, /* Digital u */ +{"VFMT", "pid,tt,state,time,sl,pagein,vsz,rss,pcpu,pmem,command"}, /* Digital v */ +{"~", "~"} /* NULL would ruin alphabetical order */ +}; + +static const int macro_array_count = sizeof(macro_array)/sizeof(macro_struct); + + +/*************************** AIX formats ********************/ +/* Convert AIX format codes to normal format specifiers. */ +static const aix_struct aix_array[] = { +{'C', "pcpu", "%CPU"}, +{'G', "group", "GROUP"}, +{'P', "ppid", "PPID"}, +{'U', "user", "USER"}, +{'a', "args", "COMMAND"}, +{'c', "comm", "COMMAND"}, +{'g', "rgroup", "RGROUP"}, +{'n', "nice", "NI"}, +{'p', "pid", "PID"}, +{'r', "pgid", "PGID"}, +{'t', "etime", "ELAPSED"}, +{'u', "ruser", "RUSER"}, +{'x', "time", "TIME"}, +{'y', "tty", "TTY"}, +{'z', "vsz", "VSZ"}, +{'~', "~", "~"} /* NULL would ruin alphabetical order */ +}; + + +/********************* sorting ***************************/ +/* Convert short sorting codes to normal format specifiers. */ +static const shortsort_struct shortsort_array[] = { +{'C', "pcpu" }, +{'G', "tpgid" }, +{'J', "cstime" }, +/* {'K', "stime" }, */ /* conflict, system vs. start time */ +{'M', "maj_flt" }, +{'N', "cmaj_flt" }, +{'P', "ppid" }, +{'R', "resident" }, +{'S', "share" }, +{'T', "start_time" }, +{'U', "uid" }, /* euid */ +{'c', "cmd" }, +{'f', "flags" }, +{'g', "pgrp" }, +{'j', "cutime" }, +{'k', "utime" }, +{'m', "min_flt" }, +{'n', "cmin_flt" }, +{'o', "session" }, +{'p', "pid" }, +{'r', "rss" }, +{'s', "size" }, +{'t', "tty" }, +{'u', "user" }, +{'v', "vsize" }, +{'y', "priority" }, /* nice */ +{'~', "~" } /* NULL would ruin alphabetical order */ +}; + + +/*********** print format_array **********/ +/* called by the parser in another file */ +void print_format_specifiers(void){ + const format_struct *walk = format_array; + while(*(walk->spec) != '~'){ + if(walk->pr != pr_nop) printf("%-12.12s %-8.8s\n", walk->spec, walk->head); + walk++; + } +} + +/************ comparison functions for bsearch *************/ + +static int compare_format_structs(const void *a, const void *b){ + return strcmp(((const format_struct*)a)->spec,((const format_struct*)b)->spec); +} + +static int compare_macro_structs(const void *a, const void *b){ + return strcmp(((const macro_struct*)a)->spec,((const macro_struct*)b)->spec); +} + +/******** look up structs as needed by the sort & format parsers ******/ + +const shortsort_struct *search_shortsort_array(const int findme){ + const shortsort_struct *walk = shortsort_array; + while(walk->desc != '~'){ + if(walk->desc == findme) return walk; + walk++; + } + return NULL; +} + +const aix_struct *search_aix_array(const int findme){ + const aix_struct *walk = aix_array; + while(walk->desc != '~'){ + if(walk->desc == findme) return walk; + walk++; + } + return NULL; +} + +const format_struct *search_format_array(const char *findme){ + format_struct key; + key.spec = findme; + return bsearch(&key, format_array, format_array_count, + sizeof(format_struct), compare_format_structs + ); +} + +const macro_struct *search_macro_array(const char *findme){ + macro_struct key; + key.spec = findme; + return bsearch(&key, macro_array, macro_array_count, + sizeof(macro_struct), compare_macro_structs + ); +} + +static unsigned int active_cols; /* some multiple of screen_cols */ + +/***** Last chance, avoid needless trunctuation. */ +static void check_header_width(void){ + format_node *walk = format_list; + unsigned int total = 0; + int was_normal = 0; + unsigned int i = 0; + unsigned int sigs = 0; + while(walk){ + switch((walk->flags) & CF_JUST_MASK){ + default: + total += walk->width; + total += was_normal; + was_normal = 1; + break; + case CF_SIGNAL: + sigs++; + if (signal_names) { + if (walk->width < SIGNAL_NAME_WIDTH) + walk->width = SIGNAL_NAME_WIDTH; + walk->flags = CF_UNLIMITED; + if (walk->next) + total += walk->width; + else + total += 3; + } else { + total += walk->width; + } + total += was_normal; + was_normal = 1; + break; + case CF_UNLIMITED: /* could chop this a bit */ + if(walk->next) total += walk->width; + else total += 3; /* not strlen(walk->name) */ + total += was_normal; + was_normal = 1; + break; + case 0: /* AIX */ + total += walk->width; + was_normal = 0; + break; + } + walk = walk->next; + } + for(;;){ + i++; + active_cols = screen_cols * i; + if(active_cols>=total) break; + if(screen_cols*i >= OUTBUF_SIZE/2) break; /* can't go over */ + } + wide_signals = (total+sigs*7 <= active_cols); +} + + +/********** show one process (NULL proc prints header) **********/ + +//#define SPACE_AMOUNT page_size +#define SPACE_AMOUNT 144 + +static char *saved_outbuf; + +void show_one_proc(const proc_t *restrict const p, const format_node *restrict fmt){ + /* unknown: maybe set correct & actual to 1, remove +/- 1 below */ + int correct = 0; /* screen position we should be at */ + int actual = 0; /* screen position we are at */ + int amount = 0; /* amount of text that this data is */ + int leftpad = 0; /* amount of space this column _could_ need */ + int space = 0; /* amount of space we actually need to print */ + int dospace = 0; /* previous column determined that we need a space */ + int legit = 0; /* legitimately stolen extra space */ + int sz = 0; /* real size of data in outbuffer */ + int tmpspace = 0; + char *restrict const outbuf = saved_outbuf; + static int did_stuff = 0; /* have we ever printed anything? */ + + if(-1==(long)p){ /* true only once, at the end */ + if(did_stuff) return; + /* have _never_ printed anything, but might need a header */ + if(!--lines_to_next_header){ + lines_to_next_header = header_gap; + show_one_proc(NULL,fmt); + } + /* fprintf(stderr, "No processes available.\n"); */ /* legal? */ + exit(1); + } + if(p){ /* not header, maybe we should call ourselves for it */ + if(!--lines_to_next_header){ + lines_to_next_header = header_gap; + show_one_proc(NULL,fmt); + } + } + did_stuff = 1; + if(active_cols>(int)OUTBUF_SIZE) fprintf(stderr,_("fix bigness error\n")); + + /* print row start sequence */ + for(;;){ + legit = 0; + /* set width suggestion which might be ignored */ +// if(likely(fmt->next)) max_rightward = fmt->width; +// else max_rightward = active_cols-((correct>actual) ? correct : actual); + + if(fmt->next){ + max_rightward = fmt->width; + tmpspace = 0; + }else{ + tmpspace = correct-actual; + if (tmpspace<1){ + tmpspace = dospace; + max_rightward = active_cols-actual-tmpspace; + }else{ + max_rightward = active_cols - ( (correct>actual) ? correct : actual ); + } + } + if(max_rightward <= 0) max_rightward = 0; + else if(max_rightward >= OUTBUF_SIZE) max_rightward = OUTBUF_SIZE-1; + + max_leftward = fmt->width + actual - correct; /* TODO check this */ + if(max_leftward <= 0) max_leftward = 0; + else if(max_leftward >= OUTBUF_SIZE) max_leftward = OUTBUF_SIZE-1; + +// fprintf(stderr, "cols: %d, max_rightward: %d, max_leftward: %d, actual: %d, correct: %d\n", +// active_cols, max_rightward, max_leftward, actual, correct); + + /* prepare data and calculate leftpad */ + if(p && fmt->pr) amount = (*fmt->pr)(outbuf,p); + else amount = snprintf(outbuf, OUTBUF_SIZE, "%s", fmt->name); /* AIX or headers */ + + if(amount < 0) outbuf[amount = 0] = '\0'; + else if(amount >= OUTBUF_SIZE) outbuf[amount = OUTBUF_SIZE-1] = '\0'; + + switch((fmt->flags) & CF_JUST_MASK){ + case 0: /* for AIX, assigned outside this file */ + leftpad = 0; + break; + case CF_LEFT: /* bad */ + leftpad = 0; + break; + case CF_RIGHT: /* OK */ + leftpad = fmt->width - amount; + if(leftpad < 0) leftpad = 0; + break; + case CF_SIGNAL: + /* if the screen is wide enough, use full 16-character output */ + if(wide_signals){ + leftpad = 16 - amount; + legit = 7; + }else{ + leftpad = 9 - amount; + } + if(leftpad < 0) leftpad = 0; + break; + case CF_USER: /* bad */ + leftpad = fmt->width - amount; + if(leftpad < 0) leftpad = 0; + if(!user_is_number) leftpad = 0; + break; + case CF_WCHAN: /* bad */ + if(wchan_is_number){ + leftpad = fmt->width - amount; + if(leftpad < 0) leftpad = 0; + break; + }else{ + if ((active_cols-actual-tmpspace)<1) + outbuf[1] = '\0'; /* oops, we (mostly) lose this column... */ + leftpad = 0; + break; + } + case CF_UNLIMITED: + { + if(active_cols-actual-tmpspace < 1) + outbuf[1] = '\0'; /* oops, we (mostly) lose this column... */ + leftpad = 0; + break; + } + default: + fprintf(stderr, _("bad alignment code\n")); + break; + } + /* At this point: + * + * correct from previous column + * actual from previous column + * amount not needed (garbage due to chopping) + * leftpad left padding for this column alone (not make-up or gap) + * space not needed (will recalculate now) + * dospace if we require space between this and the prior column + * legit space we were allowed to steal, and thus did steal + */ + space = correct - actual + leftpad; + if(space<1) space=dospace; + if(space>SPACE_AMOUNT) space=SPACE_AMOUNT; // only so much available + + /* real size -- don't forget in 'amount' is number of cells */ + outbuf[OUTBUF_SIZE-1] = '\0'; + sz = strlen(outbuf); + + /* print data, set x position stuff */ + if(!fmt->next){ + /* Last column. Write padding + data + newline all together. */ + outbuf[sz] = '\n'; + fwrite(outbuf-space, space+sz+1, 1, stdout); + break; + } + /* Not the last column. Write padding + data together. */ + fwrite(outbuf-space, space+sz, 1, stdout); + actual += space+amount; + correct += fmt->width; + correct += legit; /* adjust for SIGNAL expansion */ + if(fmt->pr && fmt->next->pr){ /* neither is AIX filler */ + correct++; + dospace = 1; + }else{ + dospace = 0; + } + fmt = fmt->next; + /* At this point: + * + * correct screen position we should be at + * actual screen position we are at + * amount not needed + * leftpad not needed + * space not needed + * dospace if have determined that we need a space next time + * legit not needed + */ + } +} + + +void init_output(void) +{ + int outbuf_pages; + char *outbuf; + + // add page_size-1 to round up + outbuf_pages = (OUTBUF_SIZE+SPACE_AMOUNT+page_size-1)/page_size; + outbuf = mmap( + 0, + page_size * (outbuf_pages+1), // 1 more, for guard page at high addresses + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); + + if(outbuf == MAP_FAILED) + catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + + memset(outbuf, ' ', SPACE_AMOUNT); + if(SPACE_AMOUNT==page_size) + mprotect(outbuf, page_size, PROT_READ); + mprotect(outbuf + page_size*outbuf_pages, page_size, PROT_NONE); // guard page + saved_outbuf = outbuf + SPACE_AMOUNT; + // available space: page_size*outbuf_pages-SPACE_AMOUNT + seconds_since_1970 = time(NULL); + + check_header_width(); +} diff --git a/src/ps/parser.c b/src/ps/parser.c new file mode 100644 index 0000000..1f50a7a --- /dev/null +++ b/src/ps/parser.c @@ -0,0 +1,1268 @@ +/* + * parser.c - ps command options parser + * + * Copyright © 2012-2023 Jim Warner <james.warner@comcast.net + * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2012-2014 Jaromir Capik <jcapik@redhat.com> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 1998-2003 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Ought to have debug print stuff like this: + * #define Print(fmt, args...) printf("Debug: " fmt, ## args) + */ + +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdbool.h> + +#include <sys/stat.h> +#include <sys/types.h> + +#include "c.h" +#include "xalloc.h" + +#include "common.h" + +#define ARG_GNU 0 +#define ARG_END 1 +#define ARG_PGRP 2 +#define ARG_SYSV 3 +#define ARG_PID 4 +#define ARG_BSD 5 +#define ARG_FAIL 6 +#define ARG_SESS 7 + +static int w_count = 0; + +static int ps_argc; /* global argc */ +static char **ps_argv; /* global argv */ +static int thisarg; /* index into ps_argv */ +static char *flagptr; /* current location in ps_argv[thisarg] */ +static int force_bsd = 0; /* set when normal parsing fails */ + +#define exclusive(x) if((ps_argc != 2) || strcmp(ps_argv[1],x)) \ + return _("the option is exclusive: " x) + +/********** utility functions **********/ +static void display_ps_version(void) +{ + fprintf(stdout, PROCPS_NG_VERSION); +} + + +/* + * Both "-Oppid" and "-O ppid" should be legal, though Unix98 + * does not require it. BSD and Digital Unix allow both. + * Return the argument or NULL; + */ +static const char *get_opt_arg(void){ + if(*(flagptr+1)){ /* argument is part of ps_argv[thisarg] */ + return flagptr+1; + } + if(thisarg+2 > ps_argc) return NULL; /* there is nothing left */ + /* argument follows ps_argv[thisarg] */ + if(*(ps_argv[thisarg+1]) == '\0') return NULL; + return ps_argv[++thisarg]; +} + +/********** parse lists (of UID, tty, GID, PID...) **********/ + +static const char *parse_pid(char *str, sel_union *ret){ + char *endp; + unsigned long num; + num = strtoul(str, &endp, 0); + if(*endp != '\0') return _("process ID list syntax error"); + if(num<1) return _("process ID out of range"); + if(num > 0x7fffffffUL) return _("process ID out of range"); + ret->pid = num; + return 0; +} + +static const char *parse_uid(char *str, sel_union *ret){ + struct passwd *passwd_data; + char *endp; + unsigned long num; + num = strtoul(str, &endp, 0); + if(*endp != '\0'){ /* hmmm, try as login name */ + passwd_data = getpwnam(str); + if(!passwd_data){ + if(!negate_selection) return _("user name does not exist"); + num = -1; + } + else + num = passwd_data->pw_uid; + } + if(!negate_selection && (num > 0xfffffffeUL)) return _("user ID out of range"); + ret->uid = num; + return 0; +} + +static const char *parse_gid(char *str, sel_union *ret){ + struct group *group_data; + char *endp; + unsigned long num; + num = strtoul(str, &endp, 0); + if(*endp != '\0'){ /* hmmm, try as login name */ + group_data = getgrnam(str); + if(!group_data){ + if(!negate_selection) return _("group name does not exist"); + num = -1; + } + else + num = group_data->gr_gid; + } + if(!negate_selection && (num > 0xfffffffeUL)) return _("group ID out of range"); + ret->gid = num; + return 0; +} + +static const char *parse_cmd(char *str, sel_union *ret){ + strncpy(ret->cmd, str, sizeof ret->cmd); // strncpy pads to end + ret->cmd[sizeof(ret->cmd)-1] = '\0'; // but let's be safe + return 0; +} + +static const char *parse_tty(char *str, sel_union *ret){ + struct stat sbuf; + char path[4096]; + if(str[0]=='/'){ + if(stat(str, &sbuf) >= 0) goto found_it; + return _("TTY could not be found"); + } +#define lookup(p) \ + snprintf(path,4096,p,str); \ + if(stat(path, &sbuf) >= 0) goto found_it + + lookup("/dev/pts/%s"); /* New Unix98 ptys go first */ + lookup("/dev/%s"); + lookup("/dev/tty%s"); + lookup("/dev/pty%s"); + lookup("/dev/%snsole"); /* "co" means "console", maybe do all VCs too? */ + if(!strcmp(str,"-")){ /* "-" means no tty (from AIX) */ + ret->tty = 0; /* processes w/o tty */ + return 0; + } + if(!strcmp(str,"?")){ /* "?" means no tty, which bash eats (Reno BSD?) */ + ret->tty = 0; /* processes w/o tty */ + return 0; + } + if(!*(str+1) && (stat(str,&sbuf)>=0)){ /* Kludge! Assume bash ate '?'. */ + ret->tty = 0; /* processes w/o tty */ + return 0; + } +#undef lookup + return _("TTY could not be found"); +found_it: + if(!S_ISCHR(sbuf.st_mode)) return _("list member was not a TTY"); + ret->tty = sbuf.st_rdev; + return 0; +} + +/* + * Used to parse lists in a generic way. (function pointers) + */ +static const char *parse_list(const char *arg, const char *(*parse_fn)(char *, sel_union *) ){ + selection_node *node; + char *buf; /* temp copy of arg to hack on */ + char *sep_loc; /* separator location: " \t," */ + char *walk; + int items; + int need_item; + const char *err; /* error code that could or did happen */ + /*** prepare to operate ***/ + node = xmalloc(sizeof(selection_node)); + node->n = 0; + node->u = NULL; + buf = strdup(arg); + /*** sanity check and count items ***/ + need_item = 1; /* true */ + items = 0; + walk = buf; + err = _("improper list"); + do{ + switch(*walk){ + case ' ': case ',': case '\t': case '\0': + if(need_item) goto parse_error; + need_item=1; + break; + default: + if(need_item && items<INT_MAX) items++; + need_item=0; + } + } while (*++walk); + if(need_item) goto parse_error; + node->n = items; + node->u = xcalloc(items, sizeof(sel_union)); + /*** actually parse the list ***/ + walk = buf; + while(items--){ + sep_loc = strpbrk(walk," ,\t"); + if(sep_loc) *sep_loc = '\0'; + if(( err=(parse_fn)(walk, node->u+items) )) goto parse_error; + walk = sep_loc + 1; /* point to next item, if any */ + } + free(buf); + node->next = selection_list; + selection_list = node; + return NULL; +parse_error: + free(buf); + free(node->u); + free(node); + return err; +} + +/***************** parse SysV options, including Unix98 *****************/ +static const char *parse_sysv_option(void){ + const char *arg; + const char *err; + + flagptr = ps_argv[thisarg]; + while(*++flagptr){ + switch(*flagptr){ + case 'A': + trace("-A selects all processes\n"); + all_processes = 1; + break; + case 'C': /* end */ + trace("-C select by process name\n"); /* Why only HP/UX and us? */ + arg=get_opt_arg(); + if(!arg) return _("list of command names must follow -C"); + err=parse_list(arg, parse_cmd); + if(err) return err; + selection_list->typecode = SEL_COMM; + return NULL; /* can't have any more options */ + case 'D': + trace("-D sets lstart date format\n"); + arg = get_opt_arg(); + if (!arg) return _("date format must follow -D"); + if (lstart_format) free(lstart_format); + lstart_format = strdup(arg); + break; + case 'F': /* DYNIX/ptx -f plus sz,rss,psr=ENG between c and stime */ + trace("-F does fuller listing\n"); + format_modifiers |= FM_F; + format_flags |= FF_Uf; + unix_f_option = 1; /* does this matter? */ + break; + case 'G': /* end */ + trace("-G select by RGID (supports names)\n"); + arg=get_opt_arg(); + if(!arg) return _("list of real groups must follow -G"); + err=parse_list(arg, parse_gid); + if(err) return err; + selection_list->typecode = SEL_RGID; + return NULL; /* can't have any more options */ + case 'H': /* another nice HP/UX feature */ + trace("-H process hierarchy (like ASCII art forest option)\n"); + forest_type = 'u'; + break; +#if 0 + case 'J': // specify list of job IDs in hex (IRIX) -- like HP "-R" maybe? + trace("-J select by job ID\n"); // want a JID ("jid") for "-j" too + arg=get_opt_arg(); + if(!arg) return _("list of jobs must follow -J"); + err=parse_list(arg, parse_jid); + if(err) return err; + selection_list->typecode = SEL_JID; + return NULL; /* can't have any more options */ +#endif + case 'L': /* */ + /* In spite of the insane 2-level thread system, Sun appears to + * have made this option Linux-compatible. If a process has N + * threads, ps will produce N lines of output. (not N+1 lines) + * Zombies are the only exception, with NLWP==0 and 1 output line. + * SCO UnixWare uses -L too. + */ + trace("-L print LWP (thread) info\n"); + thread_flags |= TF_U_L; +// format_modifiers |= FM_L; + break; + case 'M': // typically the SELinux context + trace("-M print security label for Mandatory Access Control\n"); + format_modifiers |= FM_M; + break; + case 'N': + trace("-N negates\n"); + negate_selection = 1; + break; + case 'O': /* end */ + trace("-O is preloaded -o\n"); + arg=get_opt_arg(); + if(!arg) return _("format or sort specification must follow -O"); + defer_sf_option(arg, SF_U_O); + return NULL; /* can't have any more options */ + case 'P': /* SunOS 5 "psr" or unknown HP/UX feature */ + trace("-P adds columns of PRM info (HP-UX), PSR (SunOS), or capabilities (IRIX)\n"); + format_modifiers |= FM_P; + break; +#if 0 + case 'R': // unknown HP/UX feature, like IRIX "-J" maybe? + trace("-R select by PRM group\n"); + arg=get_opt_arg(); + if(!arg) return _("list of PRM groups must follow -R"); + err=parse_list(arg, parse_prm); + if(err) return err; + selection_list->typecode = SEL_PRM; + return NULL; /* can't have any more options */ +#endif + case 'T': + /* IRIX 6.5 docs suggest POSIX threads get shown individually. + * This would make -T be like -L, -m, and m. (but an extra column) + * Testing (w/ normal processes) shows 1 line/process, not 2. + * Also, testing shows PID==SPID for all normal processes. + */ + trace("-T adds strange SPID column (old sproc() threads?)\n"); + thread_flags |= TF_U_T; +// format_modifiers |= FM_T; + break; + case 'U': /* end */ + trace("-U select by RUID (supports names)\n"); + arg=get_opt_arg(); + if(!arg) return _("list of real users must follow -U"); + err=parse_list(arg, parse_uid); + if(err) return err; + selection_list->typecode = SEL_RUID; + return NULL; /* can't have any more options */ + case 'V': /* single */ + trace("-V prints version\n"); + exclusive("-V"); + display_ps_version(); + exit(0); + // This must be verified against SVR4-MP. (UnixWare or Powermax) + // Leave it undocumented until that problem is solved. + case 'Z': /* full Mandatory Access Control level info */ + trace("-Z shows full MAC info\n"); + format_modifiers |= FM_M; + break; + case 'a': + trace("-a select all with a tty, but omit session leaders\n"); + simple_select |= SS_U_a; + break; + case 'c': + /* HP-UX and SunOS 5 scheduling info modifier */ + trace("-c changes scheduling info\n"); + format_modifiers |= FM_c; + break; + case 'd': + trace("-d select all, but omit session leaders\n"); + simple_select |= SS_U_d; + break; + case 'e': + trace("-e selects all processes\n"); + all_processes = 1; + break; + case 'f': + trace("-f does full listing\n"); + format_flags |= FF_Uf; + unix_f_option = 1; /* does this matter? */ + break; + case 'g': /* end */ + trace("-g selects by session leader OR by group name\n"); + arg=get_opt_arg(); + if(!arg) return _("list of session leaders OR effective group names must follow -g"); + err=parse_list(arg, parse_pid); + if(!err){ + selection_list->typecode = SEL_SESS; + return NULL; /* can't have any more options */ + } + err=parse_list(arg, parse_gid); + if(!err){ + selection_list->typecode = SEL_EGID; + return NULL; /* can't have any more options */ + } + return _("list of session leaders OR effective group IDs was invalid"); + case 'j': + trace("-j jobs format\n"); + /* old Debian used RD_j and Digital uses JFMT */ + if(sysv_j_format) format_flags |= FF_Uj; + else format_modifiers |= FM_j; + break; + case 'l': + trace("-l long format\n"); + format_flags |= FF_Ul; + break; + case 'm': + trace("-m shows threads\n"); + /* note that AIX shows 2 lines for a normal process */ + thread_flags |= TF_U_m; + break; + case 'o': /* end */ + /* Unix98 has gross behavior regarding this. From the following: */ + /* ps -o pid,nice=NICE,tty=TERMINAL,comm */ + /* The result must be 2 columns: "PID NICE,tty=TERMINAL,comm" */ + /* Yes, the second column has the name "NICE,tty=TERMINAL,comm" */ + /* This parser looks for any excuse to ignore that braindamage. */ + trace("-o user-defined format\n"); + arg=get_opt_arg(); + if(!arg) return _("format specification must follow -o"); + defer_sf_option(arg, SF_U_o); + return NULL; /* can't have any more options */ + case 'p': /* end */ + trace("-p select by PID\n"); + arg=get_opt_arg(); + if(!arg) return _("list of process IDs must follow -p"); + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_PID; + return NULL; /* can't have any more options */ + case 'q': /* end */ + trace("-q quick select by PID.\n"); + arg=get_opt_arg(); + if(!arg) return "List of process IDs must follow -q."; + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_PID_QUICK; + return NULL; /* can't have any more options */ +#if 0 + case 'r': + trace("-r some Digital Unix thing about warnings...\n"); + trace(" or SCO's option to chroot() for new /proc and /dev\n"); + return _("the -r option is reserved"); + break; +#endif + case 's': /* end */ + trace("-s select processes belonging to the sessions given\n"); + arg=get_opt_arg(); + if(!arg) return _("list of session IDs must follow -s"); + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_SESS; + return NULL; /* can't have any more options */ + case 't': /* end */ + trace("-t select by tty\n"); + arg=get_opt_arg(); + if(!arg) return _("list of terminals (pty, tty...) must follow -t"); + err=parse_list(arg, parse_tty); + if(err) return err; + selection_list->typecode = SEL_TTY; + return NULL; /* can't have any more options */ + case 'u': /* end */ + trace("-u select by user effective ID (supports names)\n"); + arg=get_opt_arg(); + if(!arg) return _("list of users must follow -u"); + err=parse_list(arg, parse_uid); + if(err) return err; + selection_list->typecode = SEL_EUID; + return NULL; /* can't have any more options */ + case 'w': + trace("-w wide output\n"); + w_count++; + break; + case 'x': /* behind personality until "ps -ax" habit is uncommon */ + if(personality & PER_SVR4_x){ + // Same as -y, but for System V Release 4 MP + trace("-x works like Sun Solaris & SCO Unixware -y option\n"); + format_modifiers |= FM_y; + break; + } + if(personality & PER_HPUX_x){ + trace("-x extends the command line\n"); + w_count += 2; + unix_f_option = 1; + break; + } + return _("must set personality to get -x option"); + case 'y': /* Sun's -l hack (also: Irix "lnode" resource control info) */ + trace("-y print lnone info in UID/USER column or do Sun -l hack\n"); + format_modifiers |= FM_y; + break; +#if 0 + // This must be verified against SVR4-MP (UnixWare or Powermax) + case 'z': /* alias of Mandatory Access Control level info */ + trace("-z shows aliased MAC info\n"); + format_modifiers |= FM_M; + break; + // Solaris 10 does this + case 'z': /* select by zone */ + trace("-z secects by zone\n"); + arg=get_opt_arg(); + if(!arg) return _("list of zones (contexts, labels, whatever?) must follow -z"); + err=parse_list(arg, parse_zone); + if(err) return err; + selection_list->typecode = SEL_ZONE; + return NULL; /* can't have any more options */ +#endif + case '-': + return _("embedded '-' among SysV options makes no sense"); + break; + case '\0': + catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + break; + default: + return _("unsupported SysV option"); + } /* switch */ + } /* while */ + return NULL; +} + +/************************* parse BSD options **********************/ +static const char *parse_bsd_option(void){ + const char *arg; + const char *err; + + flagptr = ps_argv[thisarg]; /* assume we _have_ a '-' */ + if(flagptr[0]=='-'){ + if(!force_bsd) return _("cannot happen - problem #1"); + }else{ + flagptr--; /* off beginning, will increment before use */ + if(personality & PER_FORCE_BSD){ + if(!force_bsd) return _("cannot happen - problem #2"); + }else{ + if(force_bsd) return _("second chance parse failed, not BSD or SysV"); + } + } + + while(*++flagptr){ + switch(*flagptr){ + case '0' ... '9': /* end */ + trace("0..9 pld BSD-style select by process ID\n"); + arg=flagptr; + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_PID; + return NULL; /* can't have any more options */ +#if 0 + case 'A': + /* maybe this just does a larger malloc() ? */ + trace("A increases the argument space (Digital Unix)\n"); + return _("option A is reserved"); + break; + case 'C': + /* should divide result by 1-(e**(foo*log(bar))) */ + trace("C use raw CPU time for %%CPU instead of decaying ave\n"); + return _("option C is reserved"); + break; +#endif + case 'H': // The FreeBSD way (NetBSD:s OpenBSD:k FreeBSD:H -- NIH???) + trace("H print LWP (thread) info\n"); // was: Use /vmcore as c-dumpfile\n"); + thread_flags |= TF_B_H; + //format_modifiers |= FM_L; // FIXME: determine if we need something like this + break; + case 'L': /* single */ + trace("L list all format specifiers\n"); + exclusive("L"); + print_format_specifiers(); + exit(0); + case 'M': // undocumented for now: these are proliferating! + trace("M MacOS X thread display, like AIX/Tru64\n"); + thread_flags |= TF_B_m; + break; + case 'O': /* end */ + trace("O like o + defaults, add new columns after PID, also sort\n"); + arg=get_opt_arg(); + if(!arg) return _("format or sort specification must follow O"); + defer_sf_option(arg, SF_B_O); + return NULL; /* can't have any more options */ + break; + case 'S': + trace("S include dead kids in sum\n"); + include_dead_children = 1; + break; + case 'T': + trace("T select all processes on this terminal\n"); + /* put our tty on a tiny list */ + { + selection_node *node; + node = xmalloc(sizeof(selection_node)); + node->u = xmalloc(sizeof(sel_union)); + node->u[0].tty = cached_tty; + node->typecode = SEL_TTY; + node->n = 1; + node->next = selection_list; + selection_list = node; + } + break; + case 'U': /* end */ + trace("U select processes for specified users\n"); + arg=get_opt_arg(); + if(!arg) return _("list of users must follow U"); + err=parse_list(arg, parse_uid); + if(err) return err; + selection_list->typecode = SEL_EUID; + return NULL; /* can't have any more options */ + case 'V': /* single */ + trace("V show version info\n"); + exclusive("V"); + display_ps_version(); + exit(0); + case 'W': + trace("W N/A get swap info from ... not /dev/drum.\n"); + return _("obsolete W option not supported (you have a /dev/drum?)"); + break; + case 'X': + trace("X old Linux i386 register format\n"); + format_flags |= FF_LX; + break; + case 'Z': /* FreeBSD does MAC like SGI's Irix does it */ + trace("Z print security label for Mandatory Access Control.\n"); + format_modifiers |= FM_M; + break; + case 'a': + trace("a select all w/tty, including other users\n"); + simple_select |= SS_B_a; + break; + case 'c': + trace("c true command name\n"); + bsd_c_option = 1; + break; +// case 'd': +// trace("d FreeBSD-style tree\n"); +// forest_type = 'f'; +// break; + case 'e': + trace("e environment\n"); + bsd_e_option = 1; + break; + case 'f': + trace("f ASCII art forest\n"); + forest_type = 'b'; + break; + case 'g': + trace("g _all_, even group leaders\n"); + simple_select |= SS_B_g; + break; + case 'h': + trace("h repeat header\n"); + if(header_type) return _("only one heading option may be specified"); + if(personality & PER_BSD_h) header_type = HEAD_MULTI; + else header_type = HEAD_NONE; + break; + case 'j': + trace("j job control format\n"); + format_flags |= FF_Bj; + break; + case 'k': + // OpenBSD: don't hide "kernel threads" -- like the swapper? + // trace("k Print LWP (thread) info.\n"); // was: Use /vmcore as c-dumpfile\n"); + + // NetBSD, and soon (?) FreeBSD: sort-by-keyword + trace("k specify sorting keywords\n"); + arg=get_opt_arg(); + if(!arg) return _("long sort specification must follow 'k'"); + defer_sf_option(arg, SF_G_sort); + return NULL; /* can't have any more options */ + case 'l': + trace("l display long format\n"); + format_flags |= FF_Bl; + break; + case 'm': + trace("m all threads, sort on mem use, show mem info\n"); + if(personality & PER_OLD_m){ + format_flags |= FF_Lm; + break; + } + if(personality & PER_BSD_m){ + defer_sf_option("pmem", SF_B_m); + break; + } + thread_flags |= TF_B_m; + break; + case 'n': + trace("n numeric output for WCHAN, and USER replaced by UID\n"); + wchan_is_number = 1; + user_is_number = 1; + /* TODO add tty_is_number too? */ + break; + case 'o': /* end */ + trace("o specify user-defined format\n"); + arg=get_opt_arg(); + if(!arg) return _("format specification must follow o"); + defer_sf_option(arg, SF_B_o); + return NULL; /* can't have any more options */ + case 'p': /* end */ + trace("p select by process ID\n"); + arg=get_opt_arg(); + if(!arg) return _("list of process IDs must follow p"); + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_PID; + return NULL; /* can't have any more options */ + case 'q': /* end */ + trace("q Quick select by process ID\n"); + arg=get_opt_arg(); + if(!arg) return "List of process IDs must follow q."; + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_PID_QUICK; + return NULL; /* can't have any more options */ + case 'r': + trace("r select running processes\n"); + running_only = 1; + break; + case 's': + trace("s display signal format\n"); + format_flags |= FF_Bs; + break; + case 't': /* end */ + trace("t select by tty\n"); + /* List of terminals (tty, pty...) _should_ follow t. */ + arg=get_opt_arg(); + if(!arg){ + /* Wow, obsolete BSD syntax. Put our tty on a tiny list. */ + selection_node *node; + node = xmalloc(sizeof(selection_node)); + node->u = xmalloc(sizeof(sel_union)); + node->u[0].tty = cached_tty; + node->typecode = SEL_TTY; + node->n = 1; + node->next = selection_list; + selection_list = node; + return NULL; + } + err=parse_list(arg, parse_tty); + if(err) return err; + selection_list->typecode = SEL_TTY; + return NULL; /* can't have any more options */ + case 'u': + trace("u display user-oriented\n"); + format_flags |= FF_Bu; + break; + case 'v': + trace("v display virtual memory\n"); + format_flags |= FF_Bv; + break; + case 'w': + trace("w wide output\n"); + w_count++; + break; + case 'x': + trace("x select processes without controlling ttys\n"); + simple_select |= SS_B_x; + break; + case '-': + return _("embedded '-' among BSD options makes no sense"); + break; + case '\0': + catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + break; + default: + return _("unsupported option (BSD syntax)"); + } /* switch */ + } /* while */ + return NULL; +} + +/*************** gnu long options **********************/ + +/* + * Return the argument or NULL + */ +static const char *grab_gnu_arg(void){ + switch(*flagptr){ /* argument is part of ps_argv[thisarg] */ + default: + return NULL; /* something bad */ + case '=': case ':': + if(*++flagptr) return flagptr; /* found it */ + return NULL; /* empty '=' or ':' */ + case '\0': /* try next argv[] */ + ; + } + if(thisarg+2 > ps_argc) return NULL; /* there is nothing left */ + /* argument follows ps_argv[thisarg] */ + if(*(ps_argv[thisarg+1]) == '\0') return NULL; + return ps_argv[++thisarg]; +} + +typedef struct gnu_table_struct { + const char *name; /* long option name */ + const void *jump; /* See gcc extension info. :-) */ +} gnu_table_struct; + +static int compare_gnu_table_structs(const void *a, const void *b){ + return strcmp(((const gnu_table_struct*)a)->name,((const gnu_table_struct*)b)->name); +} + +/* Option arguments are after ':', after '=', or in argv[n+1] */ +static const char *parse_gnu_option(void){ + const char *arg; + const char *err; + char *s; + size_t sl; + char buf[16]; + gnu_table_struct findme = { buf, NULL}; + gnu_table_struct *found; + static const gnu_table_struct gnu_table[] = { + {"Group", &&case_Group}, /* rgid */ + {"User", &&case_User}, /* ruid */ + {"cols", &&case_cols}, + {"columns", &&case_columns}, + {"context", &&case_context}, + {"cumulative", &&case_cumulative}, + {"date-format", &&case_dateformat}, + {"deselect", &&case_deselect}, /* -N */ + {"forest", &&case_forest}, /* f -H */ + {"format", &&case_format}, + {"group", &&case_group}, /* egid */ + {"header", &&case_header}, + {"headers", &&case_headers}, + {"heading", &&case_heading}, + {"headings", &&case_headings}, +//{"help", &&case_help}, /* now TRANSLATABLE ! */ + {"info", &&case_info}, + {"lines", &&case_lines}, + {"no-header", &&case_no_header}, + {"no-headers", &&case_no_headers}, + {"no-heading", &&case_no_heading}, + {"no-headings", &&case_no_headings}, + {"noheader", &&case_noheader}, + {"noheaders", &&case_noheaders}, + {"noheading", &&case_noheading}, + {"noheadings", &&case_noheadings}, + {"pid", &&case_pid}, + {"ppid", &&case_ppid}, + {"quick-pid", &&case_pid_quick}, + {"rows", &&case_rows}, + {"sid", &&case_sid}, + {"signames", &&case_signames}, + {"sort", &&case_sort}, + {"tty", &&case_tty}, + {"user", &&case_user}, /* euid */ + {"version", &&case_version}, + {"width", &&case_width}, + }; + const int gnu_table_count = sizeof(gnu_table)/sizeof(gnu_table_struct); + + s = ps_argv[thisarg]+2; + sl = strcspn(s,":="); + if(sl > 15) return _("unknown gnu long option"); + strncpy(buf, s, sl); + buf[sl] = '\0'; + flagptr = s+sl; + + found = bsearch(&findme, gnu_table, gnu_table_count, + sizeof(gnu_table_struct), compare_gnu_table_structs + ); + + if(!found) { + if (!strcmp(buf, the_word_help)) + goto case_help; + return _("unknown gnu long option"); + } + + goto *(found->jump); /* See gcc extension info. :-) */ + + case_Group: + trace("--Group\n"); + arg = grab_gnu_arg(); + if(!arg) return _("list of real groups must follow --Group"); + err=parse_list(arg, parse_gid); + if(err) return err; + selection_list->typecode = SEL_RGID; + return NULL; + case_User: + trace("--User\n"); + arg = grab_gnu_arg(); + if(!arg) return _("list of real users must follow --User"); + err=parse_list(arg, parse_uid); + if(err) return err; + selection_list->typecode = SEL_RUID; + return NULL; + case_cols: + case_width: + case_columns: + trace("--cols\n"); + arg = grab_gnu_arg(); + if(arg && *arg){ + long t; + char *endptr; + t = strtol(arg, &endptr, 0); + if(!*endptr && (t>0) && (t<2000000000)){ + screen_cols = (int)t; + return NULL; + } + } + return _("number of columns must follow --cols, --width, or --columns"); + case_cumulative: + trace("--cumulative\n"); + if(s[sl]) return _("option --cumulative does not take an argument"); + include_dead_children = 1; + return NULL; + case_dateformat: + arg=grab_gnu_arg(); + if (!arg) return _("date format must follow --date-format"); + if (lstart_format) free(lstart_format); + lstart_format = strdup(arg); + return NULL; + case_deselect: + trace("--deselect\n"); + if(s[sl]) return _("option --deselect does not take an argument"); + negate_selection = 1; + return NULL; + case_no_header: + case_no_headers: + case_no_heading: + case_no_headings: + case_noheader: + case_noheaders: + case_noheading: + case_noheadings: + trace("--noheaders\n"); + if(s[sl]) return _("option --no-heading does not take an argument"); + if(header_type) return _("only one heading option may be specified"); + header_type = HEAD_NONE; + return NULL; + case_header: + case_headers: + case_heading: + case_headings: + trace("--headers\n"); + if(s[sl]) return _("option --heading does not take an argument"); + if(header_type) return _("only one heading option may be specified"); + header_type = HEAD_MULTI; + return NULL; + case_forest: + trace("--forest\n"); + if(s[sl]) return _("option --forest does not take an argument"); + forest_type = 'g'; + return NULL; + case_format: + trace("--format\n"); + arg=grab_gnu_arg(); + if(!arg) return _("format specification must follow --format"); + defer_sf_option(arg, SF_G_format); + return NULL; + case_group: + trace("--group\n"); + arg = grab_gnu_arg(); + if(!arg) return _("list of effective groups must follow --group"); + err=parse_list(arg, parse_gid); + if(err) return err; + selection_list->typecode = SEL_EGID; + return NULL; + case_help: + trace("--help\n"); + arg = grab_gnu_arg(); + do_help(arg, EXIT_SUCCESS); + case_info: + trace("--info\n"); + exclusive("--info"); + self_info(); + exit(0); + return NULL; + case_pid: + trace("--pid\n"); + arg = grab_gnu_arg(); + if(!arg) return _("list of process IDs must follow --pid"); + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_PID; + return NULL; + case_pid_quick: + trace("--quick-pid\n"); + arg = grab_gnu_arg(); + if(!arg) return "List of process IDs must follow --quick-pid."; + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_PID_QUICK; + return NULL; + case_ppid: + trace("--ppid\n"); + arg = grab_gnu_arg(); + if(!arg) return _("list of process IDs must follow --ppid"); + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_PPID; + return NULL; + case_rows: + case_lines: + trace("--rows\n"); + arg = grab_gnu_arg(); + if(arg && *arg){ + long t; + char *endptr; + t = strtol(arg, &endptr, 0); + if(!*endptr && (t>0) && (t<2000000000)){ + screen_rows = (int)t; + return NULL; + } + } + return _("number of rows must follow --rows or --lines"); + case_sid: + trace("--sid\n"); + arg = grab_gnu_arg(); + if(!arg) return _("some sid thing(s) must follow --sid"); + err=parse_list(arg, parse_pid); + if(err) return err; + selection_list->typecode = SEL_SESS; + return NULL; + case_signames: + trace("--signames\n"); + signal_names = TRUE; + return NULL; + case_sort: + trace("--sort\n"); + arg=grab_gnu_arg(); + if(!arg) return _("long sort specification must follow --sort"); + defer_sf_option(arg, SF_G_sort); + return NULL; + case_tty: + trace("--tty\n"); + arg = grab_gnu_arg(); + if(!arg) return _("list of ttys must follow --tty"); + err=parse_list(arg, parse_tty); + if(err) return err; + selection_list->typecode = SEL_TTY; + return NULL; + case_user: + trace("--user\n"); + arg = grab_gnu_arg(); + if(!arg) return _("list of effective users must follow --user"); + err=parse_list(arg, parse_uid); + if(err) return err; + selection_list->typecode = SEL_EUID; + return NULL; + case_version: + trace("--version\n"); + exclusive("--version"); + display_ps_version(); + exit(0); + return NULL; + case_context: + trace("--context\n"); + format_flags |= FF_Fc; + return NULL; +} + +/*************** process trailing PIDs **********************/ +static const char *parse_trailing_pids(void){ + selection_node *pidnode; /* pid */ + selection_node *grpnode; /* process group */ + selection_node *sidnode; /* session */ + char **argp; /* pointer to pointer to text of PID */ + const char *err; /* error code that could or did happen */ + int i; + + i = ps_argc - thisarg; /* how many trailing PIDs, SIDs, PGRPs?? */ + argp = ps_argv + thisarg; + thisarg = ps_argc - 1; /* we must be at the end now */ + + pidnode = xmalloc(sizeof(selection_node)); + pidnode->u = xcalloc(i, sizeof(sel_union)); /* waste is insignificant */ + pidnode->n = 0; + + grpnode = xmalloc(sizeof(selection_node)); + grpnode->u = xcalloc(i,sizeof(sel_union)); /* waste is insignificant */ + grpnode->n = 0; + + sidnode = xmalloc(sizeof(selection_node)); + sidnode->u = xcalloc(i, sizeof(sel_union)); /* waste is insignificant */ + sidnode->n = 0; + + while(i--){ + char *data; + data = *(argp++); + switch(*data){ + default: err = parse_pid( data, pidnode->u + pidnode->n++); break; + case '-': err = parse_pid(++data, grpnode->u + grpnode->n++); break; + case '+': err = parse_pid(++data, sidnode->u + sidnode->n++); break; + } + if(err) return err; /* the node gets freed with the list */ + } + + if(pidnode->n){ + pidnode->next = selection_list; + selection_list = pidnode; + selection_list->typecode = SEL_PID; + } /* else free both parts */ + + if(grpnode->n){ + grpnode->next = selection_list; + selection_list = grpnode; + selection_list->typecode = SEL_PGRP; + } /* else free both parts */ + + if(sidnode->n){ + sidnode->next = selection_list; + selection_list = sidnode; + selection_list->typecode = SEL_SESS; + } /* else free both parts */ + + return NULL; +} + +/************** misc stuff ***********/ + +static void reset_parser(void){ + w_count = 0; +} + +static int arg_type(const char *str){ + int tmp = str[0]; + if((tmp>='a') && (tmp<='z')) return ARG_BSD; + if((tmp>='A') && (tmp<='Z')) return ARG_BSD; + if((tmp>='0') && (tmp<='9')) return ARG_PID; + if(tmp=='+') return ARG_SESS; + if(tmp!='-') return ARG_FAIL; + tmp = str[1]; + if((tmp>='a') && (tmp<='z')) return ARG_SYSV; + if((tmp>='A') && (tmp<='Z')) return ARG_SYSV; + if((tmp>='0') && (tmp<='9')) return ARG_PGRP; + if(tmp!='-') return ARG_FAIL; + tmp = str[2]; + if((tmp>='a') && (tmp<='z')) return ARG_GNU; + if((tmp>='A') && (tmp<='Z')) return ARG_GNU; + if(tmp=='\0') return ARG_END; + return ARG_FAIL; +} + +/* First assume sysv, because that is the POSIX and Unix98 standard. */ +static const char *parse_all_options(void){ + const char *err = NULL; + int at; + while(++thisarg < ps_argc){ + trace("parse_all_options calling arg_type for \"%s\"\n", ps_argv[thisarg]); + at = arg_type(ps_argv[thisarg]); + trace("ps_argv[thisarg] is %s\n", ps_argv[thisarg]); + switch(at){ + case ARG_GNU: + err = parse_gnu_option(); + break; + case ARG_SYSV: + if(!force_bsd){ /* else go past case ARG_BSD */ + err = parse_sysv_option(); + break; + case ARG_BSD: + if(force_bsd && !(personality & PER_FORCE_BSD)) return _("way bad"); + } + prefer_bsd_defaults = 1; + err = parse_bsd_option(); + break; + case ARG_PGRP: + case ARG_SESS: + case ARG_PID: + prefer_bsd_defaults = 1; + err = parse_trailing_pids(); + break; + case ARG_END: + case ARG_FAIL: + trace(" FAIL/END on [%s]\n",ps_argv[thisarg]); + return _("garbage option"); + break; + default: + printf(" ? %s\n",ps_argv[thisarg]); + return _("something broke"); + } /* switch */ + if(err) return err; + } /* while */ + return NULL; +} + +static void choose_dimensions(void){ + if(w_count && (screen_cols<132)) screen_cols=132; + if(w_count>1) screen_cols=OUTBUF_SIZE; + /* perhaps --html and --null should set unlimited width */ +} + +static const char *thread_option_check(void){ + if(!thread_flags){ + thread_flags = TF_show_proc; + return NULL; + } + + if(forest_type){ + return _("thread display conflicts with forest display"); + } + //thread_flags |= TF_no_forest; + + if((thread_flags&TF_B_H) && (thread_flags&(TF_B_m|TF_U_m))) + return _("thread flags conflict; can't use H with m or -m"); + if((thread_flags&TF_B_m) && (thread_flags&TF_U_m)) + return _("thread flags conflict; can't use both m and -m"); + if((thread_flags&TF_U_L) && (thread_flags&TF_U_T)) + return _("thread flags conflict; can't use both -L and -T"); + + if(thread_flags&TF_B_H) thread_flags |= (TF_show_proc|TF_loose_tasks); + if(thread_flags&(TF_B_m|TF_U_m)) thread_flags |= (TF_show_proc|TF_show_task|TF_show_both); + + if(thread_flags&(TF_U_T|TF_U_L)){ + if(thread_flags&(TF_B_m|TF_U_m|TF_B_H)){ + // Got a thread style, so format modification is a requirement? + // Maybe -T/-L has H thread style though. (sorting interaction?) + //return _("Huh? Tell procps@freelists.org what you expected."); + thread_flags |= TF_must_use; + }else{ + // using -L/-T thread style, so format from elsewhere is OK + thread_flags |= TF_show_task; // or like the H option? + //thread_flags |= TF_no_sort; + } + } + + return NULL; +} + +int arg_parse(int argc, char *argv[]){ + const char *err = NULL; + const char *err2 = NULL; + ps_argc = argc; + ps_argv = argv; + thisarg = 0; + + if(personality & PER_FORCE_BSD) goto try_bsd; + + err = parse_all_options(); + if(err) goto try_bsd; + err = thread_option_check(); + if(err) goto try_bsd; + err = process_sf_options(); + if(err) goto try_bsd; + err = select_bits_setup(); + if(err) goto try_bsd; + + choose_dimensions(); + return 0; + +try_bsd: + trace("--------- now try BSD ------\n"); + + reset_global(); + reset_parser(); + reset_sortformat(); + format_flags = 0; + ps_argc = argc; + ps_argv = argv; + thisarg = 0; + /* no need to reset flagptr */ + force_bsd=1; + prefer_bsd_defaults=1; + if(!( (PER_OLD_m|PER_BSD_m) & personality )) /* if default m setting... */ + personality |= PER_OLD_m; /* Prefer old Linux over true BSD. */ + /* Do not set PER_FORCE_BSD! It is tested below. */ + + err2 = parse_all_options(); + if(err2) goto total_failure; + err2 = thread_option_check(); + if(err2) goto total_failure; + err2 = process_sf_options(); + if(err2) goto total_failure; + err2 = select_bits_setup(); + if(err2) goto total_failure; + + choose_dimensions(); + return 0; + +total_failure: + reset_parser(); + if(personality & PER_FORCE_BSD) fprintf(stderr, _("error: %s\n"), err2); + else fprintf(stderr, _("error: %s\n"), err); + do_help(NULL, EXIT_FAILURE); +} diff --git a/src/ps/regression b/src/ps/regression new file mode 100644 index 0000000..c71c826 --- /dev/null +++ b/src/ps/regression @@ -0,0 +1,26 @@ +-u 500 -o pid,ppid,fname,comm,args # right margin trouble +-u 500 -o pid,ppid,fname,comm,args,wchan,wchan,wchan,wchan,wchan,nice,wchan +-u 500 -o pid,pid,pid,pid,user,user,user,args # had trouble +-u 500 -o user,user,user,pid,pid,pid,pid,args # no trouble! + +Test with each type of field (RIGHT,LEFT,UNLIMITED...) hanging off the +edge of the screen and each type of field to the left of the one that +hangs off the edge. + +Test "ps ef" as _both_ normal user and root. Especially after su! + +On a 108-col screen, try "ps alx" and "ps alx | cat" + +These ought to be the same: +CMD_ENV=old ps -m +CMD_ENV=old ps m + +These ought to be the same: +CMD_ENV=old ps -X +CMD_ENV=old ps X +ps X +ps -X # needs to be a non-SysV option + +This should fail: +ps x -x + diff --git a/src/ps/select.c b/src/ps/select.c new file mode 100644 index 0000000..ab71040 --- /dev/null +++ b/src/ps/select.c @@ -0,0 +1,162 @@ +/* + * select.c - ps process selection + * + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net + * Copyright © 2004-2020 Craig Small <csmall@dropbear.xyz + * Copyright © 1998-2002 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "common.h" + +//#define process_group_leader(p) (rSv(ID_PID, s_int, p) == rSv(ID_TGID, s_int, p)) +//#define some_other_user(p) (rSv(ID_EUID, u_int, p) != cached_euid) +#define has_our_euid(p) (rSv(ID_EUID, u_int, p) == cached_euid) +#define on_our_tty(p) (rSv(TTY, s_int, p) == cached_tty) +#define running(p) (rSv(STATE, s_ch, p) == 'R' || rSv(STATE, s_ch, p) == 'D') +#define session_leader(p) (rSv(ID_SESSION, s_int, p) == rSv(ID_TGID, s_int, p)) +#define without_a_tty(p) (!rSv(TTY, s_int, p)) + +static unsigned long select_bits = 0; + +/***** prepare select_bits for use */ +const char *select_bits_setup(void){ + int switch_val = 0; + /* don't want a 'g' screwing up simple_select */ + if(!simple_select && !prefer_bsd_defaults){ + select_bits = 0xaa00; /* the STANDARD selection */ + return NULL; + } + /* For every BSD but SunOS, the 'g' option is a NOP. (enabled by default) */ + if( !(personality & PER_NO_DEFAULT_g) && !(simple_select&(SS_U_a|SS_U_d)) ) + switch_val = simple_select|SS_B_g; + else + switch_val = simple_select; + switch(switch_val){ + /* UNIX options */ + case SS_U_a | SS_U_d: select_bits = 0x3f3f; break; /* 3333 or 3f3f */ + case SS_U_a: select_bits = 0x0303; break; /* 0303 or 0f0f */ + case SS_U_d: select_bits = 0x3333; break; + /* SunOS 4 only (others have 'g' enabled all the time) */ + case 0: select_bits = 0x0202; break; + case SS_B_a: select_bits = 0x0303; break; + case SS_B_x : select_bits = 0x2222; break; + case SS_B_x | SS_B_a: select_bits = 0x3333; break; + /* General BSD options */ + case SS_B_g : select_bits = 0x0a0a; break; + case SS_B_g | SS_B_a: select_bits = 0x0f0f; break; + case SS_B_g | SS_B_x : select_bits = 0xaaaa; break; + case SS_B_g | SS_B_x | SS_B_a: /* convert to -e instead of using 0xffff */ + all_processes = 1; + simple_select = 0; + break; + default: + return _("process selection options conflict"); + break; + } + return NULL; +} + +/***** selected by simple option? */ +static int table_accept(proc_t *buf){ + unsigned proc_index; + proc_index = (has_our_euid(buf) <<0) + | (session_leader(buf) <<1) + | (without_a_tty(buf) <<2) + | (on_our_tty(buf) <<3); + return (select_bits & (1<<proc_index)); +} + +/***** selected by some kind of list? */ +static int proc_was_listed(proc_t *buf){ + selection_node *sn = selection_list; + int i; + if(!sn) return 0; + while(sn){ + switch(sn->typecode){ + default: + catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + +#define return_if_match(foo,bar) \ + i=sn->n; while(i--) \ + if((unsigned)foo == (unsigned)(*(sn->u+i)).bar) \ + return 1 + + break; case SEL_RUID: return_if_match(rSv(ID_RUID, u_int, buf),uid); + break; case SEL_EUID: return_if_match(rSv(ID_EUID, u_int, buf),uid); + break; case SEL_SUID: return_if_match(rSv(ID_SUID, u_int, buf),uid); + break; case SEL_FUID: return_if_match(rSv(ID_FUID, u_int, buf),uid); + + break; case SEL_RGID: return_if_match(rSv(ID_RGID, u_int, buf),gid); + break; case SEL_EGID: return_if_match(rSv(ID_EGID, u_int, buf),gid); + break; case SEL_SGID: return_if_match(rSv(ID_SGID, u_int, buf),gid); + break; case SEL_FGID: return_if_match(rSv(ID_FGID, u_int, buf),gid); + + break; case SEL_PGRP: return_if_match(rSv(ID_PGRP, s_int, buf),pid); + break; case SEL_PID : return_if_match(rSv(ID_TGID, s_int, buf),pid); + break; case SEL_PID_QUICK : return_if_match(rSv(ID_TGID, s_int, buf),pid); + break; case SEL_PPID: return_if_match(rSv(ID_PPID, s_int, buf),ppid); + break; case SEL_TTY : return_if_match(rSv(TTY, s_int, buf),tty); + break; case SEL_SESS: return_if_match(rSv(ID_SESSION, s_int, buf),pid); + + break; + case SEL_COMM: + i=sn->n; + while(i--) { + /* special case, comm is 16 characters but match is longer */ + if (strlen(rSv(CMD, str, buf)) == 15 && strlen((*(sn->u+i)).cmd) >= 15) + if(!strncmp( rSv(CMD, str, buf), (*(sn->u+i)).cmd, 15 )) return 1; + if(!strncmp( rSv(CMD, str, buf), (*(sn->u+i)).cmd, 63 )) return 1; + } + + +#undef return_if_match + + } + sn = sn->next; + } + return 0; +} + + +/***** This must satisfy Unix98 and as much BSD as possible */ +int want_this_proc(proc_t *buf){ + int accepted_proc = 1; /* assume success */ + /* elsewhere, convert T to list, U sets x implicitly */ + + /* handle -e -A */ + if(all_processes) goto finish; + + /* use table for -a a d g x */ + if((simple_select || !selection_list)) + if(table_accept(buf)) goto finish; + + /* search lists */ + if(proc_was_listed(buf)) goto finish; + + /* fail, fall through to loose ends */ + accepted_proc = 0; + + /* do r N */ +finish: + if(running_only && !running(buf)) accepted_proc = 0; + if(negate_selection) return !accepted_proc; + return accepted_proc; +} diff --git a/src/ps/signames.c b/src/ps/signames.c new file mode 100644 index 0000000..129a4c2 --- /dev/null +++ b/src/ps/signames.c @@ -0,0 +1,170 @@ +/* + * signames.c - ps signal names + * + * Copyright © 2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2020 Luis Chamberlain <mcgrof@kernel.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <stdbool.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <dirent.h> +#include <ctype.h> +#include <pwd.h> +#include <inttypes.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include "common.h" +#include "signals.h" + +/* For libraries like musl */ +#ifndef __SIGRTMIN +#define __SIGRTMIN SIGRTMIN +#endif +#ifndef __SIGRTMAX +#define __SIGRTMAX SIGRTMAX +#endif + +/* + * The actual list of unsupported signals varies by operating system. This + * program is Linux specific as it processes /proc/ for signal information and + * there is no generic way to extract each process signal information for each + * OS. This program also relies on Linux glibc defines to figure out which + * signals are reserved for use by libc and then which ones are real time + * specific. + */ + +/* + * As per glibc: + * + * A system that defines real time signals overrides __SIGRTMAX to be something + * other than __SIGRTMIN. This also means we can count on __SIGRTMIN being the + * first real time signal, meaning what Linux programs it for your architecture + * in the kernel. SIGRTMIN then will be the application specific first real + * time signal, that is, on top of libc. The values in between + * + * __SIGRTMIN .. SIGRTMIN + * + * are used by * libc, typically for helping threading implementation. + */ +static const char *sigstat_strsignal_abbrev(int sig, char *abbrev, size_t len) +{ + memset(abbrev, '\0', len); + + if (sig == 0 || sig >= NSIG) { + snprintf(abbrev, len, "BOGUS_%02d", sig - _NSIG); + return abbrev; + } + + /* + * The standard lower signals we can count on this being the kernel + * specific SIGRTMIN. + */ + if (sig < __SIGRTMIN) { + const char *signame = NULL; +#ifdef HAVE_SIGABBREV_NP + signame = sigabbrev_np(sig); +#else + signame = signal_number_to_name(sig); +#endif /* HAVE_SIGABBREV_NP */ + if (signame != NULL && signame[0] != '\0') { + strncpy(abbrev, signame, len); + return abbrev; + } + } + + /* This means your system should *not* have realtime signals */ + if (__SIGRTMAX == __SIGRTMIN) { + snprintf(abbrev, len, "INVALID_%02d", sig); + return abbrev; + } + + /* + * If we're dealing with a libc real time signal start counting + * after libc's version of SIGRTMIN + */ + if (sig >= SIGRTMIN) { + if (sig == SIGRTMIN) + snprintf(abbrev, len, "RTMIN"); + else if (sig == SIGRTMAX) + snprintf(abbrev, len, "RTMAX"); + else + snprintf(abbrev, len, "RTMIN+%02d", sig - SIGRTMIN); + } else + snprintf(abbrev, len, "LIBC+%02d", sig - __SIGRTMIN); + + return abbrev; +} + +/* + * For instance SIGTERM is 15, but its actual mask value is + * 1 << (15-1) = 0x4000 + */ +static uint64_t mask_sig_val_num(int signum) +{ + return ((uint64_t) 1 << (signum -1)); +} + +int print_signame(char *restrict const outbuf, const char *restrict const sig, const size_t len_in) +{ + unsigned int i; + char abbrev[PATH_MAX]; + unsigned int n = 0; + char *c = outbuf; + size_t len = len_in; + uint64_t mask, mask_in; + uint64_t test_val = 0; + + if (1 != sscanf(sig, "%" PRIu64, &mask_in)) + return 0; + mask = mask_in; + + for (i=1; i < NSIG; i++) { + test_val = mask_sig_val_num(i); + if (test_val & mask) { + n = strlen(sigstat_strsignal_abbrev(i, abbrev, PATH_MAX)); + if (n+1 >= len) { // +1 for the '+' + strcpy(c, "+"); + len -= 1; + c += 1; + break; + } else { + n = snprintf(c, len, (c==outbuf)?"%s":",%s", + sigstat_strsignal_abbrev(i, abbrev, + PATH_MAX)); + len -= n; + c+=n; + } + } + } + if (c == outbuf) { + n = snprintf(c, len, "%c", '-'); + len -= n; + c += n; + } + return (int) (c-outbuf); +} diff --git a/src/ps/sortformat.c b/src/ps/sortformat.c new file mode 100644 index 0000000..a76ddee --- /dev/null +++ b/src/ps/sortformat.c @@ -0,0 +1,949 @@ +/* + * sortformat.c - ps output sorting + * + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net + * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 1998-2004 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include <sys/types.h> + +#include "misc.h" +#include "xalloc.h" + +#include "common.h" + +static sf_node *sf_list = NULL; /* deferred sorting and formatting */ +static int have_gnu_sort = 0; /* if true, "O" must be format */ +static int already_parsed_sort = 0; /* redundantly set in & out of fn */ +static int already_parsed_format = 0; + + +/**************** Parse single format specifier *******************/ +static format_node *do_one_spec(const char *spec, const char *override){ + const format_struct *fs; + const macro_struct *ms; + + fs = search_format_array(spec); + if(fs){ + int w1, w2; + format_node *thisnode; + thisnode = xmalloc(sizeof(format_node)); + if(fs->flags & CF_PIDMAX){ + w1 = (int)procps_pid_length(); + w2 = strlen(fs->head); + if(w2>w1) w1=w2; // FIXME w/ separate header/body column sizing + }else{ + w1 = fs->width; + } + if(override){ + w2 = strlen(override); + thisnode->width = (w1>w2)?w1:w2; + thisnode->name = strdup(override); + }else{ + thisnode->width = w1; + thisnode->name = strdup(fs->head); + } + thisnode->pr = fs->pr; + thisnode->vendor = fs->vendor; + thisnode->flags = fs->flags; + thisnode->next = NULL; + return thisnode; + } + + /* That failed, so try it as a macro. */ + ms = search_macro_array(spec); + if(ms){ + format_node *list = NULL; + format_node *newnode; + const char *walk; + int dist; + char buf[16]; /* trust strings will be short (from above, not user) */ + walk = ms->head; + while(*walk){ + dist = strcspn(walk, ", "); + strncpy(buf,walk,dist); + buf[dist] = '\0'; + newnode = do_one_spec(buf,override); /* call self, assume success */ + newnode->next = list; + list = newnode; + walk += dist; + if(*walk) walk++; + } + return list; + } + return NULL; /* bad, spec not found */ +} + + +/************ must wrap user format in default *************/ +static void O_wrap(sf_node *sfn, int otype){ + format_node *fnode; + format_node *endp; + const char *trailer; + + trailer = (otype=='b') ? "END_BSD" : "END_SYS5" ; + + fnode = do_one_spec("pid",NULL); + if(!fnode)catastrophic_failure(__FILE__, __LINE__, _("seriously crashing: goodbye cruel world")); + endp = sfn->f_cooked; while(endp->next) endp = endp->next; /* find end */ + endp->next = fnode; + + fnode = do_one_spec(trailer,NULL); + if(!fnode)catastrophic_failure(__FILE__, __LINE__, _("seriously crashing: goodbye cruel world")); + endp = fnode; while(endp->next) endp = endp->next; /* find end */ + endp->next = sfn->f_cooked; + sfn->f_cooked = fnode; +} + +/****************************************************************** + * Used to parse option AIX field descriptors. + * Put each completed format_node onto the list starting at ->f_cooked + */ +static const char *aix_format_parse(sf_node *sfn){ + char *buf; /* temp copy of arg to hack on */ + char *walk; + int items; + + /*** sanity check and count items ***/ + items = 0; + walk = sfn->sf; + /* state machine */ { + int c = *walk++; + initial: + if(c=='%') goto get_desc; + if(!c) goto looks_ok; + /* get_text: */ + items++; + get_more: + c = *walk++; + if(c=='%') goto get_desc; + if(c==' ') goto get_more; + if(c) goto aix_oops; + goto looks_ok; + get_desc: + items++; + c = *walk++; + if(c&&c!=' ') goto initial; + return _("missing AIX field descriptor"); + aix_oops: + return _("improper AIX field descriptor"); + looks_ok: + ; + } + + /*** sanity check passed ***/ + buf = strdup(sfn->sf); + walk = sfn->sf; + + while(items--){ + format_node *fnode; /* newly allocated */ + format_node *endp; /* for list manipulation */ + + if(*walk == '%'){ + const aix_struct *aix; + walk++; + if(*walk == '%') + return _("missing AIX field descriptor"); + aix = search_aix_array(*walk); + walk++; + if(!aix){ + free(buf); + return _("unknown AIX field descriptor"); + } + fnode = do_one_spec(aix->spec, aix->head); + if(!fnode){ + free(buf); + return _("AIX field descriptor processing bug"); + } + } else { + size_t len; + len = strcspn(walk, "%"); + memcpy(buf,walk,len); + buf[len] = '\0'; + walk += len; + fnode = xmalloc(sizeof(format_node)); + fnode->width = len < INT_MAX ? len : INT_MAX; + fnode->name = strdup(buf); + fnode->pr = NULL; /* checked for */ + fnode->vendor = AIX; + fnode->flags = CF_PRINT_EVERY_TIME; + fnode->next = NULL; + } + + endp = fnode; while(endp->next) endp = endp->next; /* find end */ + endp->next = sfn->f_cooked; + sfn->f_cooked = fnode; + } + free(buf); + already_parsed_format = 1; + return NULL; +} + +/*************************************************************** + * Used to parse option O lists. Option O is shared between + * sorting and formatting. Users may expect one or the other. + * Put each completed format_node onto the list starting at ->f_cooked + */ +static const char *format_parse(sf_node *sfn){ + char *buf; /* temp copy of arg to hack on */ + char *sep_loc; /* separator location: " \t,\n" */ + char *walk; + const char *err; /* error code that could or did happen */ + format_node *fnode; + int items; + int need_item; + static char errbuf[80]; /* for variable-text error message */ + + /*** prepare to operate ***/ + buf = strdup(sfn->sf); + + /*** sanity check and count items ***/ + need_item = 1; /* true */ + items = 0; + walk = buf; + do{ + switch(*walk){ + case ' ': case ',': case '\t': case '\n': case '\0': + /* Linux extension: allow \t and \n as delimiters */ + if(need_item){ + free(buf); + goto improper; + } + need_item=1; + break; + default: + if(need_item) items++; + need_item=0; + } + } while (*++walk); + + if(!items){ + free(buf); + goto empty; + } +#ifdef STRICT_LIST + if(need_item){ /* can't have trailing deliminator */ + free(buf); + goto improper; + } +#else + if(need_item){ /* allow 1 trailing deliminator */ + *--walk='\0'; /* remove the trailing deliminator */ + } +#endif + /*** actually parse the list ***/ + walk = buf; + while(items--){ + format_node *endp; + char *equal_loc; + char *colon_loc; + if(!walk) catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + sep_loc = strpbrk(walk," ,\t\n"); + /* if items left, then sep_loc is not in header override */ + if(items && sep_loc) *sep_loc = '\0'; + equal_loc = strpbrk(walk,"="); + if(equal_loc){ /* if header override */ + *equal_loc = '\0'; + equal_loc++; + } + colon_loc = strpbrk(walk,":"); + if(colon_loc){ /* if width override */ + *colon_loc = '\0'; + colon_loc++; + if(strspn(colon_loc,"0123456789") != strlen(colon_loc) || *colon_loc=='0' || !*colon_loc || atoi(colon_loc) <= 0){ + free(buf); + goto badwidth; + } + } + fnode = do_one_spec(walk,equal_loc); + if(!fnode){ + if(!*errbuf){ /* if didn't already create an error string */ + snprintf( + errbuf, + sizeof(errbuf), + _("unknown user-defined format specifier \"%s\""), + walk + ); + } + free(buf); + goto unknown; + } + if(colon_loc){ + if(fnode->next){ + free(buf); + goto notmacro; + } + // FIXME: enforce signal width to 8, 9, or 16 (grep: SIGNAL wide_signals) + fnode->width = atoi(colon_loc); // already verified to be a number + if(fnode->width <= 0) catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + } + endp = fnode; while(endp->next) endp = endp->next; /* find end */ + endp->next = sfn->f_cooked; + sfn->f_cooked = fnode; + walk = sep_loc ? sep_loc + 1 : NULL; /* point to next item, if any */ + } + free(buf); + already_parsed_format = 1; + return NULL; + + /* errors may cause a retry looking for AIX format codes */ + if(0) unknown: err=errbuf; + if(0) empty: err=_("empty format list"); + if(0) improper: err=_("improper format list"); + if(0) badwidth: err=_("column widths must be unsigned decimal numbers"); + if(0) notmacro: err=_("can not set width for a macro (multi-column) format specifier"); + if(strchr(sfn->sf,'%')) err = aix_format_parse(sfn); + return err; +} + +/**************** Parse single sort specifier *******************/ +static sort_node *do_one_sort_spec(const char *spec){ + const format_struct *fs; + enum pids_sort_order reverse = PIDS_SORT_ASCEND; + if(*spec == '-'){ + reverse = PIDS_SORT_DESCEND; + spec++; + } else if(*spec == '+'){ + spec++; + } + fs = search_format_array(spec); + if(fs){ + sort_node *thisnode; + thisnode = xmalloc(sizeof(sort_node)); + thisnode->sr = fs->sr; + // next is a special pointer, called to help with rel enums + thisnode->xe = (int(*)(char*,proc_t*))fs->pr; + thisnode->reverse = reverse; + thisnode->next = NULL; + return thisnode; + } + return NULL; /* bad, spec not found */ +} + + +/************************************************************** + * Used to parse long sorting options. + * Put each completed sort_node onto the list starting at ->s_cooked + */ +static const char *long_sort_parse(sf_node *sfn){ + char *buf; /* temp copy of arg to hack on */ + char *sep_loc; /* separator location: " \t,\n" */ + char *walk; + sort_node *snode; + int items; + int need_item; + + /*** prepare to operate ***/ + buf = strdup(sfn->sf); + + /*** sanity check and count items ***/ + need_item = 1; /* true */ + items = 0; + walk = buf; + do{ + switch(*walk){ + case ' ': case ',': case '\t': case '\n': case '\0': + if(need_item){ + free(buf); + return _("improper sort list"); + } + need_item=1; + break; + default: + if(need_item) items++; + need_item=0; + } + } while (*++walk); + if(!items){ + free(buf); + return _("empty sort list"); + } +#ifdef STRICT_LIST + if(need_item){ /* can't have trailing deliminator */ + free(buf); + return _("improper sort list"); + } +#else + if(need_item){ /* allow 1 trailing deliminator */ + *--walk='\0'; /* remove the trailing deliminator */ + } +#endif + /*** actually parse the list ***/ + walk = buf; + while(items--){ + sort_node *endp; + sep_loc = strpbrk(walk," ,\t\n"); + if(sep_loc) *sep_loc = '\0'; + snode = do_one_sort_spec(walk); + if(!snode){ + free(buf); + return _("unknown sort specifier"); + } + endp = snode; while(endp->next) endp = endp->next; /* find end */ + endp->next = sfn->s_cooked; + sfn->s_cooked = snode; + walk = sep_loc + 1; /* point to next item, if any */ + } + free(buf); + already_parsed_sort = 1; + return NULL; +} + + + + + + +/************ pre-parse short sorting option *************/ +/* Errors _must_ be detected so that the "O" option can try to + * reparse as formatting codes. + */ +static const char *verify_short_sort(const char *arg){ + const char all[] = "CGJKMNPRSTUcfgjkmnoprstuvy+-"; + char checkoff[256]; + int i; + const char *walk; + int tmp; + if(strspn(arg,all) != strlen(arg)) return _("bad sorting code"); + for(i=256; i--;) checkoff[i] = 0; + walk = arg; + for(;;){ + tmp = *walk; + if(tmp < 0 || (size_t)tmp >= sizeof(checkoff)) return _("bad sorting code"); + switch(tmp){ + case '\0': + return NULL; /* looks good */ + case '+': + case '-': + tmp = *(walk+1); + if(!tmp || tmp=='+' || tmp=='-') return _("bad sorting code"); + break; + case 'P': + if(forest_type) return _("PPID sort and forest output conflict"); + /* fall through */ + default: + if(checkoff[tmp]) return _("bad sorting code"); /* repeated */ + /* ought to check against already accepted sort options */ + checkoff[tmp] = 1; + break; + } + walk++; + } +} + + + +/************ parse short sorting option *************/ +static const char *short_sort_parse(sf_node *sfn){ + enum pids_sort_order direction = PIDS_SORT_ASCEND; + const char *walk; + int tmp; + sort_node *snode; + sort_node *endp; + const struct shortsort_struct *ss; + walk = sfn->sf; + for(;;){ + tmp = *walk; + switch(tmp){ + case '\0': + already_parsed_sort = 1; + return NULL; + case '+': + direction = PIDS_SORT_ASCEND; + break; + case '-': + direction = PIDS_SORT_DESCEND; + break; + default: + ss = search_shortsort_array(tmp); + if(!ss) return _("unknown sort specifier"); + snode = do_one_sort_spec(ss->spec); + if(!snode) return _("unknown sort specifier"); + snode->reverse = direction; + endp = snode; while(endp->next) endp = endp->next; /* find end */ + endp->next = sfn->s_cooked; + sfn->s_cooked = snode; + direction = 0; + break; + } + walk++; + } +} + +/******************* high-level below here *********************/ + + +/* + * Used to parse option O lists. Option O is shared between + * sorting and formatting. Users may expect one or the other. + * Recursion is to preserve original order. + */ +static const char *parse_O_option(sf_node *sfn){ + const char *err; /* error code that could or did happen */ + + if(sfn->next){ + err = parse_O_option(sfn->next); + if(err) return err; + } + + switch(sfn->sf_code){ + case SF_B_o: case SF_G_format: case SF_U_o: /*** format ***/ + err = format_parse(sfn); + if(!err) already_parsed_format = 1; + break; + case SF_U_O: /*** format ***/ + /* Can have -l -f f u... set already_parsed_format like DEC does */ + if(already_parsed_format) return _("option -O can not follow other format options"); + err = format_parse(sfn); + if(err) return err; + already_parsed_format = 1; + O_wrap(sfn,'u'); /* must wrap user format in default */ + break; + case SF_B_O: /*** both ***/ + if(have_gnu_sort || already_parsed_sort) err = _("multiple sort options"); + else err = verify_short_sort(sfn->sf); + if(!err){ /* success as sorting code */ + short_sort_parse(sfn); + already_parsed_sort = 1; + return NULL; + } + if(already_parsed_format){ + err = _("option O is neither first format nor sort order"); + break; + } + if(!format_parse(sfn)){ /* if success as format code */ + already_parsed_format = 1; + O_wrap(sfn,'b'); /* must wrap user format in default */ + return NULL; + } + break; + case SF_G_sort: case SF_B_m: /*** sort ***/ + if(already_parsed_sort) err = _("multiple sort options"); + else err = long_sort_parse(sfn); + already_parsed_sort = 1; + break; + default: /*** junk ***/ + catastrophic_failure(__FILE__, __LINE__, _("please report this bug")); + } + return err; /* could be NULL */ +} + + +/************ Main parser calls this to save lists for later **********/ +/* store data for later and return 1 if arg looks non-standard */ +int defer_sf_option(const char *arg, int source){ + sf_node *sfn; + char buf[16]; + int dist; + const format_struct *fs; + int need_item = 1; + + sfn = xmalloc(sizeof(sf_node)); + sfn->sf = strdup(arg); + sfn->sf_code = source; + sfn->s_cooked = NULL; + sfn->f_cooked = NULL; + sfn->next = sf_list; + sf_list = sfn; + + if(source == SF_G_sort) have_gnu_sort = 1; + + /* Now try to find an excuse to ignore broken Unix98 parsing. */ + if(source != SF_U_o) return 1; /* Wonderful! Already non-Unix98. */ + do{ + switch(*arg){ + case ' ': case ',': case '\0': /* no \t\n\r support in Unix98 */ + if(need_item) return 1; /* something wrong */ + need_item=1; + break; + case '=': + if(need_item) return 1; /* something wrong */ + return 0; /* broken Unix98 parsing is required */ + default: + if(!need_item) break; + need_item=0; + dist = strcspn(arg,", ="); + if(dist>15) return 1; /* something wrong, sort maybe? */ + strncpy(buf,arg,dist); /* no '\0' on end */ + buf[dist] = '\0'; /* fix that problem */ + fs = search_format_array(buf); + if(!fs) return 1; /* invalid spec, macro or sort maybe? */ + if(fs->vendor) return 1; /* Wonderful! Legal non-Unix98 spec. */ + } + } while (*++arg); + + return 0; /* boring, Unix98 is no change */ +} + +/***** Since ps is not long-lived, the memory leak can be ignored. ******/ +void reset_sortformat(void){ + sf_list = NULL; /* deferred sorting and formatting */ + format_list = NULL; /* digested formatting options */ + sort_list = NULL; /* digested sorting options (redundant?) */ + have_gnu_sort = 0; + already_parsed_sort = 0; + already_parsed_format = 0; +} + + +/***** Search format_list for findme, then insert putme after findme. ****/ +static int fmt_add_after(const char *findme, format_node *putme){ + format_node *walk; + if(!strcmp(format_list->name, findme)){ + putme->next = format_list->next; + format_list->next = putme; + return 1; /* success */ + } + walk = format_list; + while(walk->next){ + if(!strcmp(walk->next->name, findme)){ + putme->next = walk->next->next; + walk->next->next = putme; + return 1; /* success */ + } + walk = walk->next; + } + return 0; /* fail */ +} + +/******* Search format_list for findme, then delete it. ********/ +static int fmt_delete(const char *findme){ + format_node *walk; + format_node *old; + if(!strcmp(format_list->name, findme)){ + old = format_list; + format_list = format_list->next; + free(old); + return 1; /* success */ + } + walk = format_list; + while(walk->next){ + if(!strcmp(walk->next->name, findme)){ + old = walk->next; + walk->next = walk->next->next; + free(old); + return 1; /* success */ + } + walk = walk->next; + } + return 0; /* fail */ +} + + +/************ Build a SysV format backwards. ***********/ +#define PUSH(foo) (fn=do_one_spec(foo, NULL), fn->next=format_list, format_list=fn) +static const char *generate_sysv_list(void){ + format_node *fn; + if((format_modifiers & FM_y) && !(format_flags & FF_Ul)) + return _("modifier -y without format -l makes no sense"); + if(prefer_bsd_defaults){ + if(format_flags) PUSH("cmd"); + else PUSH("args"); + PUSH("bsdtime"); + if(!(format_flags & FF_Ul)) PUSH("stat"); + }else{ + if(format_flags & FF_Uf) PUSH("cmd"); + else PUSH("ucmd"); + PUSH("time"); + } + PUSH("tname"); /* Unix98 says "TTY" here, yet "tty" produces "TT". */ + if(format_flags & FF_Uf) PUSH("stime"); + /* avoid duplicate columns from -FP and -Fly */ + if(format_modifiers & FM_F){ + /* if -FP take the Sun-style column instead (sorry about "sgi_p") */ + if(!(format_modifiers & FM_P)) PUSH("psr"); /* should be ENG */ + /* if -Fly take the ADDR-replacement RSS instead */ + if(!( (format_flags & FF_Ul) && (format_modifiers & FM_y) )) PUSH("rss"); + } + if(format_flags & FF_Ul){ + PUSH("wchan"); + } + /* since FM_y adds RSS anyway, don't do this hack when that is true */ + if( (format_flags & FF_Ul) && !(format_modifiers & FM_y) ){ + if(personality & PER_IRIX_l){ /* add "rss" then ':' here */ + PUSH("sgi_rss"); + fn = xmalloc(sizeof(format_node)); + fn->width = 1; + fn->name = strdup(":"); + fn->pr = NULL; /* checked for */ + fn->vendor = AIX; /* yes, for SGI weirdness */ + fn->flags = CF_PRINT_EVERY_TIME; + fn->next = format_list; + format_list=fn; + } + } + if((format_modifiers & FM_F) || (format_flags & FF_Ul)){ + PUSH("sz"); + } + if(format_flags & FF_Ul){ + if(format_modifiers & FM_y) PUSH("rss"); + else if(personality & (PER_ZAP_ADDR|PER_IRIX_l)) PUSH("sgi_p"); + else PUSH("addr_1"); + } + if(format_modifiers & FM_c){ + PUSH("pri"); PUSH("class"); + }else if(format_flags & FF_Ul){ + PUSH("ni"); + if(personality & PER_IRIX_l) PUSH("priority"); + else /* is this good? */ PUSH("opri"); + } + + // FIXME TODO XXX -- this is a serious problem + // These somehow got flipped around. + // The bug is in procps-3.1.1, procps-990211, prior too? + if((thread_flags & TF_U_L) && (format_flags & FF_Uf)) PUSH("nlwp"); + if( (format_flags & (FF_Uf|FF_Ul)) && !(format_modifiers & FM_c) ) PUSH("c"); + + if(format_modifiers & FM_P) PUSH("psr"); + if(thread_flags & TF_U_L) PUSH("lwp"); + if(format_modifiers & FM_j){ + PUSH("sid"); + PUSH("pgid"); + } + if(format_flags & (FF_Uf|FF_Ul)) PUSH("ppid"); + if(thread_flags & TF_U_T) PUSH("spid"); + PUSH("pid"); + if(format_flags & FF_Uf){ + if(personality & PER_SANE_USER) PUSH("user"); + else PUSH("uid_hack"); + }else if(format_flags & FF_Ul){ + PUSH("uid"); + } + if(format_flags & FF_Ul){ + PUSH("s"); + if(!(format_modifiers & FM_y)) PUSH("f"); + } + if(format_modifiers & FM_M){ + PUSH("label"); /* Mandatory Access Control */ + } + return NULL; +} + + +/************************************************************************** + * Used to parse option O lists. Option O is shared between + * sorting and formatting. Users may expect one or the other. + * The "broken" flag enables a really bad Unix98 misfeature. + */ +const char *process_sf_options(void){ + sf_node *sf_walk; + + if(sf_list){ + const char *err; + err = parse_O_option(sf_list); + if(err) return err; + } + + if(format_list) catastrophic_failure(__FILE__, __LINE__, _("bug: must reset the list first")); + + /* merge formatting info of sf_list into format_list here */ + sf_walk = sf_list; + while(sf_walk){ + format_node *fmt_walk; + fmt_walk = sf_walk->f_cooked; + sf_walk->f_cooked = NULL; + while(fmt_walk){ /* put any nodes onto format_list in opposite way */ + format_node *travler; + travler = fmt_walk; + fmt_walk = fmt_walk->next; + travler->next = format_list; + format_list = travler; + } + sf_walk = sf_walk->next; + } + + /* merge sorting info of sf_list into sort_list here */ + sf_walk = sf_list; + while(sf_walk){ + sort_node *srt_walk; + srt_walk = sf_walk->s_cooked; + sf_walk->s_cooked = NULL; + if (srt_walk) { + sort_node *travler = srt_walk; + while (travler->next) travler = travler->next; + travler->next = sort_list; + sort_list = srt_walk; + } + sf_walk = sf_walk->next; + } + + // Get somebody to explain how -L/-T is supposed to interact + // with sorting. Do the threads remain grouped, with sorting + // by process, or do the threads get sorted by themselves? + if(sort_list && (thread_flags&TF_no_sort)){ + return _("tell <procps@freelists.org> what you expected"); + } + + // If nothing else, try to use $PS_FORMAT before the default. + if(!format_flags && !format_modifiers && !format_list){ + char *tmp; + tmp = getenv("PS_FORMAT"); /* user override kills default */ + if(tmp && *tmp){ + const char *err; + sf_node sfn; + if(thread_flags&TF_must_use) return _("tell <procps@freelists.org> what you want (-L/-T, -m/m/H, and $PS_FORMAT)"); + sfn.sf = tmp; + sfn.f_cooked = NULL; + err = format_parse(&sfn); + if(!err){ + format_node *fmt_walk; + fmt_walk = sfn.f_cooked; + while(fmt_walk){ /* put any nodes onto format_list in opposite way */ + format_node *travler; + travler = fmt_walk; + fmt_walk = fmt_walk->next; + travler->next = format_list; + format_list = travler; + } + return NULL; + } + // FIXME: prove that this won't be hit on valid bogus-BSD options + fprintf(stderr, _("warning: $PS_FORMAT ignored. (%s)\n"), err); + } + } + + if(format_list){ + if(format_flags) return _("conflicting format options"); + if(format_modifiers) return _("can not use output modifiers with user-defined output"); + if(thread_flags&TF_must_use) return _("-L/-T with H/m/-m and -o/-O/o/O is nonsense"); + return NULL; + } + + do{ + const char *spec; + switch(format_flags){ + + default: return _("conflicting format options"); + + /* These can be NULL, which enables SysV list generation code. */ + case 0: spec=NULL; break; + case FF_Uf | FF_Ul: spec=sysv_fl_format; break; + case FF_Uf: spec=sysv_f_format; break; + case FF_Ul: spec=sysv_l_format; break; + + /* These are NOT REACHED for normal -j processing. */ + case FF_Uj: spec=sysv_j_format; break; /* Debian & Digital */ + case FF_Uj | FF_Ul: spec="RD_lj"; break; /* Debian */ + case FF_Uj | FF_Uf: spec="RD_fj"; break; /* Debian */ + + /* These are true BSD options. */ + case FF_Bj: spec=bsd_j_format; break; + case FF_Bl: spec=bsd_l_format; break; + case FF_Bs: spec=bsd_s_format; break; + case FF_Bu: spec=bsd_u_format; break; + case FF_Bv: spec=bsd_v_format; break; + + /* These are old Linux options. Option m is overloaded. */ + case FF_LX: spec="OL_X"; break; + case FF_Lm: spec="OL_m"; break; + + /* This is the sole FLASK security option. */ + case FF_Fc: spec="FLASK_context"; break; + + } /* end switch(format_flags) */ + + // not just for case 0, since sysv_l_format and such may be NULL + if(!spec) return generate_sysv_list(); + + do{ + format_node *fmt_walk; + fmt_walk = do_one_spec(spec, NULL); /* use override "" for no headers */ + while(fmt_walk){ /* put any nodes onto format_list in opposite way */ + format_node *travler; + travler = fmt_walk; + fmt_walk = fmt_walk->next; + travler->next = format_list; + format_list = travler; + } + }while(0); + }while(0); + + do{ + format_node *fn; + if(format_modifiers & FM_j){ + fn = do_one_spec("pgid", NULL); + if(!fmt_add_after("PPID", fn)) if(!fmt_add_after("PID", fn)) + catastrophic_failure(__FILE__, __LINE__, _("internal error: no PID or PPID for -j option")); + fn = do_one_spec("sid", NULL); + if(!fmt_add_after("PGID", fn)) return _("lost my PGID"); + } + if(format_modifiers & FM_y){ + /* TODO: check for failure to do something, and complain if so */ + fmt_delete("F"); + fn = do_one_spec("rss", NULL); + if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR"); + } + if(format_modifiers & FM_c){ + fmt_delete("%CPU"); fmt_delete("CPU"); fmt_delete("CP"); fmt_delete("C"); + fmt_delete("NI"); + fn = do_one_spec("class", NULL); + if(!fmt_add_after("PRI", fn)) + catastrophic_failure(__FILE__, __LINE__, _("internal error: no PRI for -c option")); + fmt_delete("PRI"); /* we want a different one */ + fn = do_one_spec("pri", NULL); + if(!fmt_add_after("CLS", fn)) return _("lost my CLS"); + } + if(thread_flags & TF_U_T){ + fn = do_one_spec("spid", NULL); + if(!fmt_add_after("PID", fn) && (thread_flags&TF_must_use)) + return _("-T with H/-m/m but no PID for SPID to follow"); + } + if(thread_flags & TF_U_L){ + fn = do_one_spec("lwp", NULL); + if(fmt_add_after("SID", fn)) goto did_lwp; + if(fmt_add_after("SESS", fn)) goto did_lwp; + if(fmt_add_after("PGID", fn)) goto did_lwp; + if(fmt_add_after("PGRP", fn)) goto did_lwp; + if(fmt_add_after("PPID", fn)) goto did_lwp; + if(fmt_add_after("PID", fn)) goto did_lwp; + if(thread_flags&TF_must_use) + return _("-L with H/-m/m but no PID/PGID/SID/SESS for NLWP to follow"); +did_lwp: + fn = do_one_spec("nlwp", NULL); + fmt_add_after("%CPU", fn); + } + if(format_modifiers & FM_M){ // Mandatory Access Control, IRIX style + fn = do_one_spec("label", NULL); + fn->next=format_list; + format_list=fn; + } + /* Do personality-specific translations not covered by format_flags. + * Generally, these only get hit when personality overrides unix output. + * That (mostly?) means the Digital and Debian personalities. + */ + if((personality & PER_ZAP_ADDR) && (format_flags & FF_Ul)){ + fn = do_one_spec("sgi_p", NULL); + if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR"); + } + if((personality & PER_SANE_USER) && (format_flags & FF_Uf)){ + fn = do_one_spec("user", NULL); + if(fmt_add_after("UID", fn)) fmt_delete("UID"); + } + }while(0); + + return NULL; +} + diff --git a/src/ps/stacktrace.c b/src/ps/stacktrace.c new file mode 100644 index 0000000..fdd2aa9 --- /dev/null +++ b/src/ps/stacktrace.c @@ -0,0 +1,191 @@ +/* + * stacktrace.c - ps debugging additions + * + * Gnu debugger stack trace code provided by Peter Mattis + * <petm@CSUA.Berkeley.EDU> on Thu, 2 Nov 1995 + * Modified for easy use by Albert Cahalan. + * + * Copyright © 2004-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2016 Jim Warner <james.warner@comcast.net + * Copyright © 1998-2004 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "common.h" + +#define INTERACTIVE 0 +#define STACK_TRACE 1 + +char *stored_prog_name = "you forgot to set \"program\""; +static int stack_trace_done; + +/***********/ +static void debug_stop(char **args){ + execvp (args[0], args); + perror ("exec failed"); + _exit (0); +} + +/***********/ +static void stack_trace_sigchld(int signum){ + (void)signum; + stack_trace_done = 1; +} + +/************/ +static void stack_trace(char **args){ + pid_t pid; + int in_fd[2]; + int out_fd[2]; + fd_set fdset; + fd_set readset; + struct timeval tv; + int sel, index, state; + char buffer[256]; + char c; + + stack_trace_done = 0; + signal(SIGCHLD, stack_trace_sigchld); + + if((pipe (in_fd) == -1) || (pipe (out_fd) == -1)){ + perror ("could open pipe"); + _exit (0); + } + + pid = fork (); + if (pid == 0){ + close (0); dup (in_fd[0]); /* set the stdin to the in pipe */ + close (1); dup (out_fd[1]); /* set the stdout to the out pipe */ + close (2); dup (out_fd[1]); /* set the stderr to the out pipe */ + execvp (args[0], args); /* exec gdb */ + perror ("exec failed"); + _exit (0); + } else { + if(pid == (pid_t) -1){ + perror ("could not fork"); + _exit (0); + } + } + + FD_ZERO (&fdset); + FD_SET (out_fd[0], &fdset); + + write (in_fd[1], "backtrace\n", 10); + write (in_fd[1], "p x = 0\n", 8); + write (in_fd[1], "quit\n", 5); + + index = 0; + state = 0; + + for(;;){ + readset = fdset; + tv.tv_sec = 1; + tv.tv_usec = 0; + + sel = select (FD_SETSIZE, &readset, NULL, NULL, &tv); + if (sel == -1) break; + + if((sel > 0) && (FD_ISSET (out_fd[0], &readset))){ + if(read (out_fd[0], &c, 1)){ + switch(state){ + case 0: + if(c == '#'){ + state = 1; + index = 0; + buffer[index++] = c; + } + break; + case 1: + buffer[index++] = c; + if((c == '\n') || (c == '\r')){ + buffer[index] = 0; + fprintf (stderr, "%s", buffer); + state = 0; + index = 0; + } + break; + default: + break; + } + } + } + else if(stack_trace_done) break; + } + + close (in_fd[0]); + close (in_fd[1]); + close (out_fd[0]); + close (out_fd[1]); + _exit (0); +} + +/************/ +void debug(int method, char *prog_name){ + pid_t pid; + char buf[16]; + char *args[4] = { "gdb", NULL, NULL, NULL }; + int x; + + snprintf (buf, sizeof(buf), "%d", getpid ()); + + args[1] = prog_name; + args[2] = buf; + + pid = fork (); + if(pid == 0){ + switch (method){ + case INTERACTIVE: + fprintf (stderr, "debug_stop\n"); + debug_stop(args); + break; + case STACK_TRACE: + fprintf (stderr, "stack_trace\n"); + stack_trace(args); + break; + } + _exit(0); + } else if(pid == (pid_t) -1){ + perror ("could not fork"); + return; + } + + x = 1; + while(x); /* wait for debugger? */ +} + +#ifdef DEBUG +/************/ +static void stack_trace_sigsegv(int signum){ + (void)signum; + debug(STACK_TRACE, stored_prog_name); +} + +/************/ +void init_stack_trace(char *prog_name){ + stored_prog_name = prog_name; + signal(SIGSEGV, stack_trace_sigsegv); +} +#endif diff --git a/src/pwdx.c b/src/pwdx.c new file mode 100644 index 0000000..90ead80 --- /dev/null +++ b/src/pwdx.c @@ -0,0 +1,153 @@ +/* + * pwdx.c - print process working directory + * + * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2004-2006 Albert Cahalan + * Copyright © 2004 Nicholas Miell + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include "c.h" +#include "nls.h" +#include "xalloc.h" +#include "fileutils.h" + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] pid...\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("pwdx(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int check_pid_argument(char *input) +{ + int skip = 0; + long pid; + char *end = NULL; + + if (!strncmp("/proc/", input, 6)) + skip = 6; + errno = 0; + pid = strtol(input + skip, &end, 10); + + if (errno || input + skip == end || (end && *end)) + return 1; + if (pid < 1) + return 1; + return 0; +} + +int main(int argc, char *argv[]) +{ + int ch; + int retval = 0, i; + ssize_t alloclen = 128; + char *pathbuf; + + static const struct option longopts[] = { + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {NULL, 0, 0, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((ch = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) + switch (ch) { + case 'V': + printf(PROCPS_NG_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + + argc -= optind; + argv += optind; + + if (argc == 0) + usage(stderr); + + pathbuf = xmalloc(alloclen); + + for (i = 0; i < argc; i++) { + char *s; + ssize_t len, buflen; + /* Constant 10 is the length of strings "/proc/" + "/cwd" */ + char *buf; + buflen = 10 + strlen(argv[i]) + 1; + buf = xmalloc(buflen); + + if (check_pid_argument(argv[i])) + xerrx(EXIT_FAILURE, _("invalid process id: %s"), + argv[i]); + /* + * At this point, all arguments are in the form + * /proc/NNNN or NNNN, so a simple check based on + * the first char is possible + */ + if (argv[i][0] != '/') + snprintf(buf, buflen, "/proc/%s/cwd", argv[i]); + else + snprintf(buf, buflen, "%s/cwd", argv[i]); + + /* + * buf contains /proc/NNNN/cwd symlink name + * on entry, the target of that symlink on return + */ + while ((len = readlink(buf, pathbuf, alloclen)) == alloclen) { + alloclen *= 2; + pathbuf = xrealloc(pathbuf, alloclen); + } + free(buf); + + if (len < 0) { + s = strerror(errno == ENOENT ? ESRCH : errno); + retval = EXIT_FAILURE; + fprintf(stderr, "%s: %s\n", argv[i], s); + continue; + } else { + pathbuf[len] = 0; + s = pathbuf; + } + + printf("%s: %s\n", argv[i], s); + } + free(pathbuf); + return retval; +} diff --git a/src/skill.c b/src/skill.c new file mode 100644 index 0000000..055f7ee --- /dev/null +++ b/src/skill.c @@ -0,0 +1,600 @@ +/* + * skill.c - send a signal to process + * + * Copyright © 2009-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 1998-2002 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include "c.h" +#include "fileutils.h" +#include "signals.h" +#include "strutils.h" +#include "nls.h" +#include "xalloc.h" +#include "rpmatch.h" + +#include "misc.h" +#include "pids.h" + +#define DEFAULT_NICE 4 + +struct run_time_conf_t { + int fast; + int interactive; + int verbose; + int warnings; + int noaction; + int debugging; +}; +static int tty_count, uid_count, cmd_count, pid_count, namespace_count; +static int *ttys; +static uid_t *uids; +static const char **cmds; +static int *pids; +static char **namespaces; +static int ns_pid; +static struct procps_ns match_namespaces; +static int ns_flags = 0x3f; + +#define ENLIST(thing,addme) do{ \ +if(thing##_count < 0 || (size_t)thing##_count >= INT_MAX / sizeof(*thing##s)) \ + xerrx(EXIT_FAILURE, _("integer overflow")); \ +thing##s = xrealloc(thing##s, sizeof(*thing##s)*(thing##_count+1)); \ +thing##s[thing##_count++] = addme; \ +}while(0) + +struct pids_info *Pids_info; + +enum pids_item items[] = { + PIDS_ID_PID, + PIDS_ID_EUID, + PIDS_ID_EUSER, + PIDS_TTY, + PIDS_TTY_NAME, + PIDS_CMD}; +enum rel_items { + EU_PID, EU_EUID, EU_EUSER, EU_TTY, EU_TTYNAME, EU_CMD}; + +static int my_pid; + +static int sig_or_pri; + +enum { + PROG_UNKNOWN, + PROG_SKILL, + PROG_SNICE +}; +static int program = PROG_UNKNOWN; + +static int parse_namespaces(char *optarg) +{ + char *ptr = optarg, *tmp; + int len, id; + + ns_flags = 0; + while (1) { + if (strchr(ptr, ',') == NULL) { + len = -1; + tmp = strdup(ptr); + } else { + len = strchr(ptr, ',') - ptr; + tmp = strndup(ptr, len); + } + + id = procps_ns_get_id(tmp); + if (id == -1) { + fprintf(stderr, "%s is not a valid namespace\n", tmp); + free(tmp); + return 1; + } + ns_flags |= (1 << id); + ENLIST(namespace, tmp); + + if (len == -1) + break; + + ptr+= len + 1; + } + return 0; +} + +static int match_intlist(const int value, const int len, int *list) +{ + int i; + + for(i=0; i<len; i++) + if (list[i] == value) + return 1; + return 0; +} + +static int match_strlist(const char *value, const int len, const char **list) +{ + int i; + + for(i=0; i<len; i++) + if (strcmp(list[i], value) == 0) + return 1; + return 0; +} + +static int match_ns(const int pid) +{ + struct procps_ns proc_ns; + int found = 1; + int i; + + if (procps_ns_read_pid(pid, &proc_ns) < 0) + xerrx(EXIT_FAILURE, + _("Unable to read process namespace information")); + for (i = 0; i < PROCPS_NS_COUNT; i++) { + if (ns_flags & (1 << i)) { + if (proc_ns.ns[i] != match_namespaces.ns[i]) + found = 0; + } + } + + return found; +} + +#define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, stack, Pids_info) +#define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, stack, Pids_info) + +static int ask_user(struct pids_stack *stack) +{ + char *buf=NULL; + size_t len=0; + + fprintf(stderr, "%-8s %-8s %5d %-16.16s ? ", + PIDS_GETSTR(TTYNAME), + PIDS_GETSTR(EUSER), + PIDS_GETINT(PID), + PIDS_GETSTR(CMD)); + fflush(stdout); + if (getline(&buf, &len, stdin) == -1) { + free(buf); + return 0; + } + if (rpmatch(buf) < 1) { + free(buf); + return 0; + } + free(buf); + return 1; +} + +static void nice_or_kill(struct pids_stack *stack, + struct run_time_conf_t *run_time) +{ + int failed; + + if (run_time->interactive && !ask_user(stack)) + return; + + /* do the actual work */ + errno = 0; + if (program == PROG_SKILL) + failed = kill(PIDS_GETINT(PID), sig_or_pri); + else + failed = setpriority(PRIO_PROCESS, PIDS_GETINT(PID), sig_or_pri); + if ((run_time->warnings && failed) || run_time->debugging || run_time->verbose) { + fprintf(stderr, "%-8s %-8s %5d %-16.16s ", + PIDS_GETSTR(TTYNAME), + PIDS_GETSTR(EUSER), + PIDS_GETINT(PID), + PIDS_GETSTR(CMD)); + perror(""); + return; + } + if (run_time->interactive) + return; + if (run_time->noaction) { + printf("%d\n", PIDS_GETINT(PID)); + return; + } +} + +#undef PIDS_GETINT +#undef PIDS_GETSTR + +/* debug function */ +static void show_lists(void) +{ + int i; + + fprintf(stderr, "signal: %d\n", sig_or_pri); + + fprintf(stderr, "%d TTY: ", tty_count); + if (ttys) { + i = tty_count; + while (i--) { + fprintf(stderr, "%d,%d%c", (ttys[i] >> 8) & 0xff, + ttys[i] & 0xff, i ? ' ' : '\n'); + } + } else + fprintf(stderr, "\n"); + + fprintf(stderr, "%d UID: ", uid_count); + if (uids) { + i = uid_count; + while (i--) + fprintf(stderr, "%d%c", uids[i], i ? ' ' : '\n'); + } else + fprintf(stderr, "\n"); + + fprintf(stderr, "%d PID: ", pid_count); + if (pids) { + i = pid_count; + while (i--) + fprintf(stderr, "%d%c", pids[i], i ? ' ' : '\n'); + } else + fprintf(stderr, "\n"); + + fprintf(stderr, "%d CMD: ", cmd_count); + if (cmds) { + i = cmd_count; + while (i--) + fprintf(stderr, "%s%c", cmds[i], i ? ' ' : '\n'); + } else + fprintf(stderr, "\n"); +} + +static void scan_procs(struct run_time_conf_t *run_time) +{ + #define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, reap->stacks[i], Pids_info) + #define PIDS_GETUNT(e) PIDS_VAL(EU_ ## e, u_int, reap->stacks[i], Pids_info) + #define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, reap->stacks[i], Pids_info) + struct pids_fetch *reap; + int i, total_procs; + + if (procps_pids_new(&Pids_info, items, 6) < 0) + xerrx(EXIT_FAILURE, + _("Unable to create pid Pids_info structure")); + if ((reap = procps_pids_reap(Pids_info, PIDS_FETCH_TASKS_ONLY)) == NULL) + xerrx(EXIT_FAILURE, + _("Unable to load process information")); + + total_procs = reap->counts->total; + for (i=0; i < total_procs; i++) { + if (PIDS_GETINT(PID) == my_pid || PIDS_GETINT(PID) == 0) + continue; + if (pids && !match_intlist(PIDS_GETINT(PID), pid_count, pids)) + continue; + if (uids && !match_intlist(PIDS_GETUNT(EUID), uid_count, (int *)uids)) + continue; + if (ttys && !match_intlist(PIDS_GETINT(TTY), tty_count, ttys)) + continue; + if (cmds && !match_strlist(PIDS_GETSTR(CMD), cmd_count, cmds)) + continue; + if (namespaces && !match_ns(PIDS_GETINT(PID))) + continue; + nice_or_kill(reap->stacks[i], run_time); + } + + #undef PIDS_GETINT + #undef PIDS_GETUNT + #undef PIDS_GETSTR +} + +/* skill and snice help */ +static void __attribute__ ((__noreturn__)) skillsnice_usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + + if (program == PROG_SKILL) { + fprintf(out, + _(" %s [signal] [options] <expression>\n"), + program_invocation_short_name); + } else { + fprintf(out, + _(" %s [new priority] [options] <expression>\n"), + program_invocation_short_name); + } + fputs(USAGE_OPTIONS, out); + fputs(_(" -f, --fast fast mode (not implemented)\n"), out); + fputs(_(" -i, --interactive interactive\n"), out); + fputs(_(" -l, --list list all signal names\n"), out); + fputs(_(" -L, --table list all signal names in a nice table\n"), out); + fputs(_(" -n, --no-action do not actually kill processes; just print what would happen\n"), out); + fputs(_(" -v, --verbose explain what is being done\n"), out); + fputs(_(" -w, --warnings enable warnings (not implemented)\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(_("Expression can be: terminal, user, pid, command.\n" + "The options below may be used to ensure correct interpretation.\n"), out); + fputs(_(" -c, --command <command> expression is a command name\n"), out); + fputs(_(" -p, --pid <pid> expression is a process id number\n"), out); + fputs(_(" -t, --tty <tty> expression is a terminal\n"), out); + fputs(_(" -u, --user <username> expression is a username\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(_("Alternatively, expression can be:\n"), out); + fputs(_(" --ns <pid> match the processes that belong to the same\n" + " namespace as <pid>\n"), out); + fputs(_(" --nslist <ns,...> list which namespaces will be considered for\n" + " the --ns option; available namespaces are\n:" + " ipc, mnt, net, pid, user, uts\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + if (program == PROG_SKILL) { + fprintf(out, + _("\n" + "The default signal is TERM. Use -l or -L to list available signals.\n" + "Particularly useful signals include HUP, INT, KILL, STOP, CONT, and 0.\n" + "Alternate signals may be specified in three ways: -SIGKILL -KILL -9\n")); + fprintf(out, USAGE_MAN_TAIL("skill(1)")); + } else { + fprintf(out, + _("\n" + "The default priority is +4. (snice +4 ...)\n" + "Priority numbers range from +20 (slowest) to -20 (fastest).\n" + "Negative priority numbers are restricted to administrative users.\n")); + fprintf(out, USAGE_MAN_TAIL("snice(1)")); + } + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + + +static int snice_prio_option(int *argc, char **argv) +{ + int i = 1, nargs = *argc; + long prio = DEFAULT_NICE; + + while (i < nargs) { + if ((argv[i][0] == '-' || argv[i][0] == '+') + && isdigit(argv[i][1])) { + prio = strtol_or_err(argv[i], + _("failed to parse argument")); + if (prio < INT_MIN || INT_MAX < prio) + xerrx(EXIT_FAILURE, + _("priority %lu out of range"), prio); + memmove(argv + i, argv + i + 1, + sizeof(char *) * (nargs - i)); + nargs--; + } else + i++; + } + *argc = nargs; + return (int)prio; +} + +static void parse_options(int argc, + char **argv, struct run_time_conf_t *run_time) +{ + int signo = -1; + int prino = DEFAULT_NICE; + int ch, i; + + enum { + NS_OPTION = CHAR_MAX + 1, + NSLIST_OPTION, + }; + + static const struct option longopts[] = { + {"command", required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'd'}, + {"fast", no_argument, NULL, 'f'}, + {"interactive", no_argument, NULL, 'i'}, + {"list", no_argument, NULL, 'l'}, + {"no-action", no_argument, NULL, 'n'}, + {"pid", required_argument, NULL, 'p'}, + {"table", no_argument, NULL, 'L'}, + {"tty", required_argument, NULL, 't'}, + {"user", required_argument, NULL, 'u'}, + {"ns", required_argument, NULL, NS_OPTION}, + {"nslist", required_argument, NULL, NSLIST_OPTION}, + {"verbose", no_argument, NULL, 'v'}, + {"warnings", no_argument, NULL, 'w'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + + if (argc < 2) + skillsnice_usage(stderr); + + sig_or_pri = -1; + + if (program == PROG_SNICE) + prino = snice_prio_option(&argc, argv); + else if (program == PROG_SKILL) { + signo = skill_sig_option(&argc, argv); + if (-1 < signo) + sig_or_pri = signo; + } + + while ((ch = + getopt_long(argc, argv, "c:dfilnp:Lt:u:vwhV", longopts, + NULL)) != -1) + switch (ch) { + case 'c': + ENLIST(cmd, optarg); + break; + case 'd': + run_time->debugging = 1; + break; + case 'f': + run_time->fast = 1; + break; + case 'i': + run_time->interactive = 1; + break; + case 'l': + unix_print_signals(); + exit(EXIT_SUCCESS); + case 'n': + run_time->noaction = 1; + break; + case 'p': + ENLIST(pid, + strtol_or_err(optarg, + _("failed to parse argument"))); + break; + case 'L': + pretty_print_signals(); + exit(EXIT_SUCCESS); + case 't': + { + struct stat sbuf; + char path[32]; + snprintf(path, 32, "/dev/%s", optarg); + if (stat(path, &sbuf) >= 0 + && S_ISCHR(sbuf.st_mode)) { + ENLIST(tty, sbuf.st_rdev); + } + } + break; + case 'u': + { + struct passwd *passwd_data; + passwd_data = getpwnam(optarg); + if (passwd_data) { + ENLIST(uid, passwd_data->pw_uid); + } + } + break; + case NS_OPTION: + ns_pid = atoi(optarg); + if (ns_pid == 0) { + xwarnx(_("invalid pid number %s"), optarg); + skillsnice_usage(stderr); + } + if (procps_ns_read_pid(ns_pid, &match_namespaces) < 0) { + xwarnx(_("error reading reference namespace " + "information")); + skillsnice_usage(stderr); + } + + break; + case NSLIST_OPTION: + if (parse_namespaces(optarg)) { + xwarnx(_("invalid namespace list")); + skillsnice_usage(stderr); + } + break; + case 'v': + run_time->verbose = 1; + break; + case 'w': + run_time->warnings = 1; + break; + case 'h': + skillsnice_usage(stdout); + case 'V': + fprintf(stdout, PROCPS_NG_VERSION); + exit(EXIT_SUCCESS); + default: + skillsnice_usage(stderr); + } + + argc -= optind; + argv += optind; + + for (i = 0; i < argc; i++) { + long num; + char *end = NULL; + errno = 0; + num = strtol(argv[0], &end, 10); + if (errno == 0 && argv[0] != end && end != NULL && *end == '\0') { + ENLIST(pid, num); + } else { + ENLIST(cmd, argv[0]); + } + argv++; + } + + /* No more arguments to process. Must sanity check. */ + if (!tty_count && !uid_count && !cmd_count && !pid_count && !ns_pid) + xerrx(EXIT_FAILURE, _("no process selection criteria")); + if ((run_time->fast | run_time->interactive | run_time-> + verbose | run_time->warnings | run_time->noaction) & ~1) + xerrx(EXIT_FAILURE, _("general flags may not be repeated")); + if (run_time->interactive + && (run_time->verbose | run_time->fast | run_time->noaction)) + xerrx(EXIT_FAILURE, _("-i makes no sense with -v, -f, and -n")); + if (run_time->verbose && (run_time->interactive | run_time->fast)) + xerrx(EXIT_FAILURE, _("-v makes no sense with -i and -f")); + if (run_time->noaction) { + program = PROG_SKILL; + /* harmless */ + sig_or_pri = 0; + } + if (program == PROG_SNICE) + sig_or_pri = prino; + else if (sig_or_pri < 0) + sig_or_pri = SIGTERM; +} + +/* main body */ +int main(int argc, char ** argv) +{ +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + struct run_time_conf_t run_time; + memset(&run_time, 0, sizeof(struct run_time_conf_t)); + my_pid = getpid(); + + if (strcmp(program_invocation_short_name, "skill") == 0 || + strcmp(program_invocation_short_name, "lt-skill") == 0) + program = PROG_SKILL; + else if (strcmp(program_invocation_short_name, "snice") == 0 || + strcmp(program_invocation_short_name, "lt-snice") == 0) + program = PROG_SNICE; +#ifdef __CYGWIN__ + else if (strcmp(program_invocation_short_name, "prockill") == 0 || + strcmp(program_invocation_short_name, "lt-prockill") == 0) + program = PROG_KILL; +#endif + + switch (program) { + case PROG_SNICE: + case PROG_SKILL: + setpriority(PRIO_PROCESS, my_pid, -20); + parse_options(argc, argv, &run_time); + if (run_time.debugging) + show_lists(); + scan_procs(&run_time); + break; + default: + fprintf(stderr, _("skill: \"%s\" is not supported\n"), + program_invocation_short_name); + fprintf(stderr, USAGE_MAN_TAIL("skill(1)")); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/slabtop.c b/src/slabtop.c new file mode 100644 index 0000000..40ce340 --- /dev/null +++ b/src/slabtop.c @@ -0,0 +1,388 @@ +/* + * slabtop.c - utility to display kernel slab information. + * + * Copyright © 2009-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2002-2004 Albert Cahalan + * Copyright © 2003 Chris Rivera <cmrivera@ufl.edu> + * Copyright © 2003 Robert Love <rml@tech9.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <locale.h> +#include <ncurses.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> + +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include "c.h" +#include "fileutils.h" +#include "nls.h" +#include "strutils.h" + +#include "slabinfo.h" + +#define DEFAULT_SORT SLAB_NUM_OBJS +#define CHAINS_ALLOC 150 +#define MAXTBL(t) (int)( sizeof(t) / sizeof(t[0]) ) +#define DEFAULT_DELAY 3 + +static unsigned short Cols, Rows; +static struct termios Saved_tty; +static long Delay = 0; +static int Run_once = 0; + +static struct slabinfo_info *Slab_info; + +enum slabinfo_item Sort_item = DEFAULT_SORT; +enum slabinfo_sort_order Sort_Order = SLABINFO_SORT_DESCEND; + +enum slabinfo_item Node_items[] = { + SLAB_NUM_OBJS, SLAB_ACTIVE_OBJS, SLAB_PERCENT_USED, + SLAB_OBJ_SIZE, SLAB_NUMS_SLABS, SLAB_OBJ_PER_SLAB, + SLAB_SIZE_TOTAL, SLAB_NAME, + /* next 2 are sortable but are not displayable, + thus they need not be represented in the Relative_enums */ + SLAB_PAGES_PER_SLAB, SLAB_ACTIVE_SLABS }; + +enum Relative_node { + nod_OBJS, nod_AOBJS, nod_USE, nod_OSIZE, + nod_SLABS, nod_OPS, nod_SIZE, nod_NAME }; + +#define MAX_ITEMS (int)(sizeof(Node_items) / sizeof(Node_items[0])) + +#define PRINT_line(fmt, ...) if (Run_once) printf(fmt, __VA_ARGS__); else printw(fmt, __VA_ARGS__) + + +/* + * term_resize - set the globals 'Cols' and 'Rows' to the current terminal size + */ +static void term_resize (int unusused __attribute__ ((__unused__))) +{ + struct winsize ws; + + if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) && ws.ws_row > 10) { + Cols = ws.ws_col; + Rows = ws.ws_row; + } else { + Cols = 80; + Rows = 24; + } +} + +static void sigint_handler (int unused __attribute__ ((__unused__))) +{ + Delay = 0; +} + +static void __attribute__((__noreturn__)) usage (FILE *out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -d, --delay <secs> delay updates\n"), out); + fputs(_(" -o, --once only display once, then exit\n"), out); + fputs(_(" -s, --sort <char> specify sort criteria by character (see below)\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + + fputs(_("\nThe following are valid sort criteria:\n"), out); + fputs(_(" a: sort by number of active objects\n"), out); + fputs(_(" b: sort by objects per slab\n"), out); + fputs(_(" c: sort by cache size\n"), out); + fputs(_(" l: sort by number of slabs\n"), out); + fputs(_(" v: sort by (non display) number of active slabs\n"), out); + fputs(_(" n: sort by name\n"), out); + fputs(_(" o: sort by number of objects (the default)\n"), out); + fputs(_(" p: sort by (non display) pages per slab\n"), out); + fputs(_(" s: sort by object size\n"), out); + fputs(_(" u: sort by cache utilization\n"), out); + fprintf(out, USAGE_MAN_TAIL("slabtop(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static void set_sort_stuff (const char key) +{ + Sort_item = DEFAULT_SORT; + Sort_Order = SLABINFO_SORT_DESCEND; + + switch (tolower(key)) { + case 'n': + Sort_item = SLAB_NAME; + Sort_Order = SLABINFO_SORT_ASCEND; + break; + case 'o': + Sort_item = SLAB_NUM_OBJS; + break; + case 'a': + Sort_item = SLAB_ACTIVE_OBJS; + break; + case 's': + Sort_item = SLAB_OBJ_SIZE; + break; + case 'b': + Sort_item = SLAB_OBJ_PER_SLAB; + break; + case 'p': + Sort_item = SLAB_PAGES_PER_SLAB; + break; + case 'l': + Sort_item = SLAB_NUMS_SLABS; + break; + case 'v': + Sort_item = SLAB_ACTIVE_SLABS; + break; + case 'c': + Sort_item = SLAB_SIZE_TOTAL; + break; + case 'u': + Sort_item = SLAB_PERCENT_USED; + break; + default: + break; + } +} + +static void parse_opts (int argc, char **argv) +{ + static const struct option longopts[] = { + { "delay", required_argument, NULL, 'd' }, + { "sort", required_argument, NULL, 's' }, + { "once", no_argument, NULL, 'o' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }}; + int o; + + while ((o = getopt_long(argc, argv, "d:s:ohV", longopts, NULL)) != -1) { + switch (o) { + case 'd': + if (Run_once) + xerrx(EXIT_FAILURE, _("Cannot combine -d and -o options")); + errno = 0; + Delay = strtol_or_err(optarg, _("illegal delay")); + if (Delay < 1) + xerrx(EXIT_FAILURE, _("delay must be positive integer")); + break; + case 's': + set_sort_stuff(optarg[0]); + break; + case 'o': + if (Delay != 0) + xerrx(EXIT_FAILURE, _("Cannot combine -d and -o options")); + Run_once=1; + break; + case 'V': + printf(PROCPS_NG_VERSION); + exit(EXIT_SUCCESS); + case 'h': + usage(stdout); + default: + usage(stderr); + } + } + if (optind != argc) + usage(stderr); + if (!Run_once && Delay == 0) + Delay = DEFAULT_DELAY; +} + +static void print_summary (void) +{ + #define totalVAL(e,t) SLABINFO_VAL(e, t, p, Slab_info) + enum slabinfo_item items[] = { + SLABS_ACTIVE_OBJS, SLABS_NUM_OBJS, + SLABS_ACTIVE_SLABS, SLABS_NUMS_SLABS, + SLABS_CACHES_ACTIVE, SLABS_CACHES_TOTAL, + SLABS_SIZE_ACTIVE, SLABS_SIZE_TOTAL, + SLABS_OBJ_SIZE_MIN, SLABS_OBJ_SIZE_AVG, + SLABS_OBJ_SIZE_MAX + }; + enum rel_items { + tot_AOBJS, tot_OBJS, tot_ASLABS, tot_SLABS, + tot_ACACHES, tot_CACHES, tot_ACTIVE, tot_TOTAL, + tot_MIN, tot_AVG, tot_MAX + }; + struct slabinfo_stack *p; + + if (!(p = procps_slabinfo_select(Slab_info, items, MAXTBL(items)))) + xerrx(EXIT_FAILURE, _("Error getting slab summary results")); + + PRINT_line(" %-35s: %u / %u (%.1f%%)\n" + , /* Translation Hint: Next five strings must not + * exceed a length of 35 characters. */ + /* xgettext:no-c-format */ + _("Active / Total Objects (% used)") + , totalVAL(tot_AOBJS, u_int) + , totalVAL(tot_OBJS, u_int) + , 100.0 * totalVAL(tot_AOBJS, u_int) / totalVAL(tot_OBJS, u_int)); + PRINT_line(" %-35s: %u / %u (%.1f%%)\n" + , /* xgettext:no-c-format */ + _("Active / Total Slabs (% used)") + , totalVAL(tot_ASLABS, u_int) + , totalVAL(tot_SLABS, u_int) + , 100.0 * totalVAL(tot_ASLABS, u_int) / totalVAL(tot_SLABS, u_int)); + PRINT_line(" %-35s: %u / %u (%.1f%%)\n" + , /* xgettext:no-c-format */ + _("Active / Total Caches (% used)") + , totalVAL(tot_ACACHES, u_int) + , totalVAL(tot_CACHES, u_int) + , 100.0 * totalVAL(tot_ACACHES, u_int) / totalVAL(tot_CACHES, u_int)); + PRINT_line(" %-35s: %.2fK / %.2fK (%.1f%%)\n" + , /* xgettext:no-c-format */ + _("Active / Total Size (% used)") + , totalVAL(tot_ACTIVE, ul_int) / 1024.0 + , totalVAL(tot_TOTAL, ul_int) / 1024.0 + , 100.0 * totalVAL(tot_ACTIVE, ul_int) / totalVAL(tot_TOTAL, ul_int)); + PRINT_line(" %-35s: %.2fK / %.2fK / %.2fK\n\n" + , _("Minimum / Average / Maximum Object") + , totalVAL(tot_MIN, u_int) / 1024.0 + , totalVAL(tot_AVG, u_int) / 1024.0 + , totalVAL(tot_MAX, u_int) / 1024.0); + #undef totalVAL +} + +static void print_headings (void) +{ + /* Translation Hint: Please keep alignment of the + * following intact. */ + PRINT_line("%-78s\n", _(" OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME")); +} + +static void print_details (struct slabinfo_stack *stack) +{ + #define nodeVAL(e,t) SLABINFO_VAL(e, t, stack, Slab_info) + PRINT_line("%6u %6u %3u%% %7.2fK %6u %8u %9luK %-23s\n" + , nodeVAL(nod_OBJS, u_int) + , nodeVAL(nod_AOBJS, u_int) + , nodeVAL(nod_USE, u_int) + , nodeVAL(nod_OSIZE, u_int) / 1024.0 + , nodeVAL(nod_SLABS, u_int) + , nodeVAL(nod_OPS, u_int) + , nodeVAL(nod_SIZE, ul_int) / 1024 + , nodeVAL(nod_NAME, str)); + + return; + #undef nodeVAL +} + + +int main(int argc, char *argv[]) +{ + int is_tty = 0, rc = EXIT_SUCCESS; + unsigned short old_rows = 0; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + parse_opts(argc, argv); + + if (procps_slabinfo_new(&Slab_info) < 0) + xerr(EXIT_FAILURE, _("Unable to create slabinfo structure")); + + if (!Run_once) { + is_tty = isatty(STDIN_FILENO); + if (is_tty && tcgetattr(STDIN_FILENO, &Saved_tty) == -1) + xwarn(_("terminal setting retrieval")); + old_rows = Rows; + term_resize(0); + initscr(); + resizeterm(Rows, Cols); + signal(SIGWINCH, term_resize); + signal(SIGINT, sigint_handler); + } + + do { + struct slabinfo_reaped *reaped; + struct timeval tv; + fd_set readfds; + int i; + + if (!(reaped = procps_slabinfo_reap(Slab_info, Node_items, MAXTBL(Node_items)))) { + xwarn(_("Unable to get slabinfo node data")); + rc = EXIT_FAILURE; + break; + } + + if (!(procps_slabinfo_sort(Slab_info, reaped->stacks, reaped->total, Sort_item, Sort_Order))) { + xwarn(_("Unable to sort slab nodes")); + rc = EXIT_FAILURE; + break; + } + + if (Run_once) { + print_summary(); + print_headings(); + for (i = 0; i < reaped->total; i++) + print_details(reaped->stacks[i]); + break; + } + + if (old_rows != Rows) { + resizeterm(Rows, Cols); + old_rows = Rows; + } + move(0, 0); + print_summary(); + attron(A_REVERSE); + print_headings(); + attroff(A_REVERSE); + + for (i = 0; i < Rows - 8 && i < reaped->total; i++) + print_details(reaped->stacks[i]); + + refresh(); + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + tv.tv_sec = Delay; + tv.tv_usec = 0; + if (select(STDOUT_FILENO, &readfds, NULL, NULL, &tv) > 0) { + char c; + if (read(STDIN_FILENO, &c, 1) != 1 + || (c == 'Q' || c == 'q')) + break; + set_sort_stuff(c); + } + // made zero by sigint_handler() + } while (Delay); + + if (!Run_once) { + if (is_tty) + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Saved_tty); + endwin(); + } + procps_slabinfo_unref(&Slab_info); + return rc; +} diff --git a/src/sysctl.c b/src/sysctl.c new file mode 100644 index 0000000..1cb548c --- /dev/null +++ b/src/sysctl.c @@ -0,0 +1,1070 @@ +/* + * Sysctl - A utility to read and manipulate the sysctl parameters + * + * Copyright © 2009-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2012-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2017-2018 Werner Fink <werner@suse.de> + * Copyright © 2014 Jaromir Capik <jcapik@redhat.com> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2002-2007 Albert Cahalan + * Copyright © 1999 George Staikos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Part of this code comes from systemd, especially sysctl.c + * Changelog: + * v1.01: + * - added -p <preload> to preload values from a file + * Horms: + * - added -q to be quiet when modifying values + * + */ + +#include <dirent.h> +#include <errno.h> +#include <getopt.h> +#include <glob.h> +#include <libgen.h> +#include <limits.h> +#include <regex.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <ctype.h> + +#include "c.h" +#include "fileutils.h" +#include "nls.h" +#include "xalloc.h" +#include "procio.h" + +/* + * Globals... + */ +static const char PROC_PATH[] = "/proc/sys/"; +static const char DEFAULT_PRELOAD[] = "/etc/sysctl.conf"; +static const char *DEPRECATED[] = { + "base_reachable_time", + "retrans_time", + "" +}; +static bool IgnoreDeprecated; +static bool NameOnly; +static bool PrintName; +static bool PrintNewline; +static bool IgnoreError; +static bool Quiet; +static bool DryRun; +static char *pattern; + +#define LINELEN 4096 +static char *iobuf; +static size_t iolen = LINELEN; + +typedef struct SysctlSetting { + char *key; + char *path; + char *value; + bool ignore_failure; + bool glob_exclude; + struct SysctlSetting *next; +} SysctlSetting; + +typedef struct SettingList { + struct SysctlSetting *head; + struct SysctlSetting *tail; +} SettingList; + +#define GLOB_CHARS "*?[" +static inline bool string_is_glob(const char *p) +{ + return !!strpbrk(p, GLOB_CHARS); +} + + +/* Function prototypes. */ +static int pattern_match(const char *string, const char *pat); +static int DisplayAll(const char *restrict const path); + +static inline bool is_proc_path( + const char *path) +{ + char *resolved_path; + + if ( (resolved_path = realpath(path, NULL)) == NULL) + return false; + + if (strncmp(PROC_PATH, resolved_path, strlen(PROC_PATH)) == 0) { + free(resolved_path); + return true; + } + + xwarnx(_("Path is not under %s: %s"), PROC_PATH, path); + free(resolved_path); + return false; +} + +static void slashdot(char *restrict p, char old, char new) +{ + int warned = 1; + p = strpbrk(p, "/."); + if (!p) + /* nothing -- can't be, but oh well */ + return; + if (*p == new) + /* already in desired format */ + return; + while (p) { + char c = *p; + if ((*(p + 1) == '/' || *(p + 1) == '.') && warned) { + xwarnx(_("separators should not be repeated: %s"), p); + warned = 0; + } + if (c == old) + *p = new; + if (c == new) + *p = old; + p = strpbrk(p + 1, "/."); + } +} + +#if 0 // avoid '-Wunused-function' warning +static void setting_free(SysctlSetting *s) { + if (!s) + return; + + free(s->key); + free(s->path); + free(s->value); + free(s); +} +#endif + +static SysctlSetting *setting_new( + const char *key, + const char *value, + bool ignore_failure, + bool glob_exclude) { + + SysctlSetting *s = NULL; + char *path = NULL; + int proc_len; + + proc_len = strlen(PROC_PATH); + /* used to open the file */ + path = xmalloc(strlen(key) + proc_len + 2); + strcpy(path, PROC_PATH); + if (key[0] == '-') + strcat(path + proc_len, key+1); + else + strcat(path + proc_len, key); + /* change . to / for path */ + slashdot(path + proc_len, '.', '/'); + + s = xmalloc(sizeof(SysctlSetting)); + + *s = (SysctlSetting) { + .key = strdup(key), + .path = path, + .value = value? strdup(value): NULL, + .ignore_failure = ignore_failure, + .glob_exclude = glob_exclude, + .next = NULL, + }; + + return s; +} + +static void settinglist_add(SettingList *l, SysctlSetting *s) { + SysctlSetting *old_tail; + + if (!l) + return; + + if (l->head == NULL) + l->head = s; + + if (l->tail != NULL) { + old_tail = l->tail; + old_tail->next = s; + } + l->tail = s; +} + +static SysctlSetting *settinglist_findpath(const SettingList *l, const char *path) { + SysctlSetting *node; + + for (node=l->head; node != NULL; node = node->next) { + if (strcmp(node->path, path) == 0) + return node; + } + return NULL; +} + +/* Function prototypes. */ +static int pattern_match(const char *string, const char *pat); +static int DisplayAll(const char *restrict const path); + +/* + * Display the usage format + */ +static void __attribute__ ((__noreturn__)) + Usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] [variable[=value] ...]\n"), + program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --all display all variables\n"), out); + fputs(_(" -A alias of -a\n"), out); + fputs(_(" -X alias of -a\n"), out); + fputs(_(" --deprecated include deprecated parameters to listing\n"), out); + fputs(_(" --dry-run Print the key and values but do not write\n"), out); + fputs(_(" -b, --binary print value without new line\n"), out); + fputs(_(" -e, --ignore ignore unknown variables errors\n"), out); + fputs(_(" -N, --names print variable names without values\n"), out); + fputs(_(" -n, --values print only values of the given variable(s)\n"), out); + fputs(_(" -p, --load[=<file>] read values from file\n"), out); + fputs(_(" -f alias of -p\n"), out); + fputs(_(" --system read values from all system directories\n"), out); + fputs(_(" -r, --pattern <expression>\n" + " select setting that match expression\n"), out); + fputs(_(" -q, --quiet do not echo variable set\n"), out); + fputs(_(" -w, --write enable writing a value to variable\n"), out); + fputs(_(" -o does nothing\n"), out); + fputs(_(" -x does nothing\n"), out); + fputs(_(" -d alias of -h\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("sysctl(8)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +/* + * Strip left/leading side of a string + */ +static char *lstrip(char *line) +{ + char *start; + + if (!line || !*line) + return line; + + start = line; + while(isspace(*start)) start++; + + return start; +} + +/* + * Strip right/trailing side of a string + * by placing a \0 + */ +static void rstrip(char *line) +{ + char *end; + + if (!line || !*line) + return; + + end = line + strlen(line) - 1; + while(end > line && isspace(*end)) end--; + + end[1] = '\0'; +} + +#if 0 // avoid '-Wunused-function' warning +/* + * Strip the leading and trailing spaces from a string + */ +static char *StripLeadingAndTrailingSpaces(char *oneline) +{ + char *t; + + if (!oneline || !*oneline) + return oneline; + + t = oneline; + t += strlen(oneline) - 1; + + while ((*t == ' ' || *t == '\t' || *t == '\n' || *t == '\r') && t != oneline) + *t-- = 0; + + t = oneline; + + while ((*t == ' ' || *t == '\t') && *t != 0) + t++; + + return t; +} +#endif + +/* + * Read a sysctl setting + */ +static int ReadSetting(const char *restrict const name) +{ + int rc = EXIT_SUCCESS; + char *restrict tmpname; + char *restrict outname; + ssize_t rlen; + FILE *restrict fp; + struct stat ts; + + if (!name || !*name) { + xwarnx(_("\"%s\" is an unknown key"), name); + return -1; + } + + /* used to open the file */ + tmpname = xmalloc(strlen(name) + strlen(PROC_PATH) + 2); + strcpy(tmpname, PROC_PATH); + strcat(tmpname, name); + /* change . to / */ + slashdot(tmpname + strlen(PROC_PATH), '.', '/'); + + /* used to display the output */ + outname = xstrdup(name); + /* change / to . */ + slashdot(outname, '/', '.'); + + if (stat(tmpname, &ts) < 0) { + if (!IgnoreError) { + xwarn(_("cannot stat %s"), tmpname); + rc = EXIT_FAILURE; + } + goto out; + } + if ((ts.st_mode & S_IRUSR) == 0) + goto out; + + if (!is_proc_path(tmpname)) { + rc = -1; + goto out; + } + + if (S_ISDIR(ts.st_mode)) { + size_t len; + len = strlen(tmpname); + tmpname[len] = '/'; + tmpname[len + 1] = '\0'; + rc = DisplayAll(tmpname); + goto out; + } + + if (pattern && !pattern_match(outname, pattern)) { + rc = EXIT_SUCCESS; + goto out; + } + + if (NameOnly) { + fprintf(stdout, "%s\n", outname); + goto out; + } + + fp = fprocopen(tmpname, "r"); + + if (!fp) { + switch (errno) { + case ENOENT: + if (!IgnoreError) { + xwarnx(_("\"%s\" is an unknown key"), outname); + rc = EXIT_FAILURE; + } + break; + case EACCES: + xwarnx(_("permission denied on key '%s'"), outname); + rc = EXIT_FAILURE; + break; + case EIO: /* Ignore stable_secret below /proc/sys/net/ipv6/conf */ + rc = EXIT_FAILURE; + break; + default: + xwarn(_("reading key \"%s\""), outname); + rc = EXIT_FAILURE; + break; + } + } else { + errno = 0; + if ((rlen = getline(&iobuf, &iolen, fp)) > 0) { + /* this loop is required, see + * /sbin/sysctl -a | egrep -6 dev.cdrom.info + */ + do { + char *nlptr; + if (PrintName) { + fprintf(stdout, "%s = ", outname); + do { + fprintf(stdout, "%s", iobuf); + nlptr = &iobuf[strlen(iobuf) - 1]; + /* already has the \n in it */ + if (*nlptr == '\n') + break; + } while ((rlen = getline(&iobuf, &iolen, fp)) > 0); + if (*nlptr != '\n') + putchar('\n'); + } else { + if (!PrintNewline) { + nlptr = strchr(iobuf, '\n'); + if (nlptr) + *nlptr = '\0'; + } + fprintf(stdout, "%s", iobuf); + } + } while ((rlen = getline(&iobuf, &iolen, fp)) > 0); + } else { + switch (errno) { + case EACCES: + xwarnx(_("permission denied on key '%s'"), + outname); + rc = EXIT_FAILURE; + break; + case EISDIR: { + size_t len; + len = strlen(tmpname); + tmpname[len] = '/'; + tmpname[len + 1] = '\0'; + fclose(fp); + rc = DisplayAll(tmpname); + goto out; + } + case EIO: /* Ignore stable_secret below /proc/sys/net/ipv6/conf */ + rc = EXIT_FAILURE; + break; + default: + xwarnx(_("reading key \"%s\""), outname); + rc = EXIT_FAILURE; + case 0: + break; + } + } + fclose(fp); + } + out: + free(tmpname); + free(outname); + return rc; +} + +static int is_deprecated(char *filename) +{ + int i; + for (i = 0; strlen(DEPRECATED[i]); i++) { + if (strcmp(DEPRECATED[i], filename) == 0) + return 1; + } + return 0; +} + +/* + * Display all the sysctl settings + */ +static int DisplayAll(const char *restrict const path) +{ + int rc = EXIT_SUCCESS; + int rc2; + DIR *restrict dp; + struct dirent *restrict de; + struct stat ts; + + dp = opendir(path); + + if (!dp) { + xwarnx(_("unable to open directory \"%s\""), path); + rc = EXIT_FAILURE; + } else { + readdir(dp); /* skip . */ + readdir(dp); /* skip .. */ + while ((de = readdir(dp))) { + char *restrict tmpdir; + if (IgnoreDeprecated && is_deprecated(de->d_name)) + continue; + tmpdir = + (char *restrict) xmalloc(strlen(path) + + strlen(de->d_name) + + 2); + sprintf(tmpdir, "%s%s", path, de->d_name); + rc2 = stat(tmpdir, &ts); + if (rc2 != 0) { + xwarn(_("cannot stat %s"), tmpdir); + } else { + if (S_ISDIR(ts.st_mode)) { + strcat(tmpdir, "/"); + DisplayAll(tmpdir); + } else { + rc |= + ReadSetting(tmpdir + + strlen(PROC_PATH)); + } + } + free(tmpdir); + } + closedir(dp); + } + return rc; +} + +/* + * Write a sysctl setting + */ +static int WriteSetting( + const char *key, + const char *path, + const char *value, + const bool ignore_failure) +{ + int rc = EXIT_SUCCESS; + FILE *fp; + struct stat ts; + char *dotted_key; + + if (!key || !path) + return rc; + + if (stat(path, &ts) < 0) { + if (!IgnoreError) { + xwarn(_("cannot stat %s"), path); + rc = EXIT_FAILURE; + } + return rc; + } + + if (!is_proc_path(path)) { + return EXIT_FAILURE; + } + + /* Convert the globbed path into a dotted key */ + if ( (dotted_key = strdup(path + strlen(PROC_PATH))) == NULL) { + xerrx(EXIT_FAILURE, _("strdup key")); + return EXIT_FAILURE; + } + slashdot(dotted_key, '/', '.'); + + if ((ts.st_mode & S_IWUSR) == 0) { + errno = EPERM; + xwarn(_("setting key \"%s\""), dotted_key); + free(dotted_key); + return rc; + } + + if (S_ISDIR(ts.st_mode)) { + errno = EISDIR; + xwarn(_("setting key \"%s\""), dotted_key); + free(dotted_key); + return rc; + } + + if (!DryRun) { + if ((fp = fprocopen(path, "w")) == NULL) { + switch (errno) { + case ENOENT: + if (!IgnoreError) { + xwarnx(_("\"%s\" is an unknown key%s"), + dotted_key, (ignore_failure?_(", ignoring"):"")); + if (!ignore_failure) + rc = EXIT_FAILURE; + } + break; + case EPERM: + case EROFS: + case EACCES: + xwarnx(_("permission denied on key \"%s\"%s"), + dotted_key, (ignore_failure?_(", ignoring"):"")); + break; + default: + xwarn(_("setting key \"%s\"%s"), + dotted_key, (ignore_failure?_(", ignoring"):"")); + break; + } + if (!ignore_failure && errno != ENOENT) + rc = EXIT_FAILURE; + } else { + if (0 < fprintf(fp, "%s\n", value)) + rc = EXIT_SUCCESS; + if (close_stream(fp) != 0) { + xwarn(_("setting key \"%s\""), dotted_key); + free(dotted_key); + return EXIT_FAILURE; + } + } + } + if ((rc == EXIT_SUCCESS && !Quiet) || DryRun) { + if (NameOnly) { + printf("%s\n", dotted_key); + } else { + if (PrintName) { + printf("%s = %s\n", dotted_key, value); + } else { + if (PrintNewline) + printf("%s\n", value); + else + printf("%s", value); + } + } + } + free(dotted_key); + return rc; +} + +/* + * parse each configuration line, there are multiple ways of specifying + * a key/value here: + * + * key = value simple setting + * -key = value ignore errors + * key.pattern.*.with.glob = value set keys that match glob + * -key.pattern.exclude.with.glob dont set this value + * key.pattern.override.with.glob = value set this glob match to value + * + */ + +static SysctlSetting *parse_setting_line( + const char *path, + const int linenum, + char *line) +{ + char *key; + char *value; + bool glob_exclude = FALSE; + bool ignore_failure = FALSE; + + key = lstrip(line); + if (strlen(key) < 2) + return NULL; + + /* skip over comments */ + if (key[0] == '#' || key[0] == ';') + return NULL; + + if (pattern && !pattern_match(key, pattern)) + return NULL; + + value = strchr(key, '='); + if (value == NULL) { + if (key[0] == '-') { + glob_exclude = TRUE; + key++; + value = NULL; + rstrip(key); + } else { + xwarnx(_("%s(%d): invalid syntax, continuing..."), + path, linenum); + return NULL; + } + } else { + value[0]='\0'; + if (key[0] == '-') { + ignore_failure = TRUE; + key++; + } + value++; // skip over = + value=lstrip(value); + rstrip(value); + rstrip(key); + } + return setting_new(key, value, ignore_failure, glob_exclude); +} + +/* Go through the setting list, expand and sort out + * setting globs and actually write the settings out + */ +static int write_setting_list(const SettingList *sl) +{ + SysctlSetting *node; + int rc = EXIT_SUCCESS; + + for (node=sl->head; node != NULL; node=node->next) { + if (node->glob_exclude) + continue; + + if (string_is_glob(node->path)) { + glob_t globbuf; + int i; + + if (glob(node->path, 0, NULL, &globbuf) != 0) + continue; + + for(i=0; i < globbuf.gl_pathc; i++) { + if (settinglist_findpath(sl, globbuf.gl_pathv[i])) + continue; // override or exclude + + rc |= WriteSetting(node->key, globbuf.gl_pathv[i], node->value, + node->ignore_failure); + } + } else { + rc |= WriteSetting(node->key, node->path, node->value, + node->ignore_failure); + } + + + } + + return rc; +} + +static int pattern_match(const char *string, const char *pat) +{ + int status; + regex_t re; + + if (regcomp(&re, pat, REG_EXTENDED | REG_NOSUB) != 0) + return (0); + status = regexec(&re, string, (size_t) 0, NULL, 0); + regfree(&re); + if (status != 0) + return (0); + return (1); +} + +/* + * Preload the sysctl's from the conf file. We parse the file and then + * reform it (strip out whitespace). + */ +static int Preload(SettingList *setlist, const char *restrict const filename) +{ + FILE *fp; + int n = 0; + int rc = EXIT_SUCCESS; + ssize_t rlen; + glob_t globbuf; + int globerr; + int globflg; + int j; + + globflg = GLOB_NOCHECK; +#ifdef GLOB_BRACE + globflg |= GLOB_BRACE; +#endif +#ifdef GLOB_TILDE + globflg |= GLOB_TILDE; +#else + if (filename[0] == '~') + xwarnx(_("GLOB_TILDE is not supported on your platform, " + "the tilde in \"%s\" won't be expanded."), filename); +#endif + globerr = glob(filename, globflg, NULL, &globbuf); + + if (globerr != 0 && globerr != GLOB_NOMATCH) + xerr(EXIT_FAILURE, _("glob failed")); + + for (j = 0; j < globbuf.gl_pathc; j++) { + fp = (globbuf.gl_pathv[j][0] == '-' && !globbuf.gl_pathv[j][1]) + ? stdin : fopen(globbuf.gl_pathv[j], "r"); + if (!fp) { + xwarn(_("cannot open \"%s\""), globbuf.gl_pathv[j]); + return EXIT_FAILURE; + } + + while ((rlen = getline(&iobuf, &iolen, fp)) > 0) { + SysctlSetting *setting; + + n++; + + if (rlen < 2) + continue; + + if ( (setting = parse_setting_line(globbuf.gl_pathv[j], n, iobuf)) + == NULL) + continue; + settinglist_add(setlist, setting); + } + + fclose(fp); + } + return rc; +} + +struct pair { + char *name; + char *value; +}; + +static int sortpairs(const void *A, const void *B) +{ + const struct pair *a = *(struct pair * const *) A; + const struct pair *b = *(struct pair * const *) B; + return strcmp(a->name, b->name); +} + +static int PreloadSystem(SettingList *setlist) +{ + unsigned di, i; + const char *dirs[] = { + "/etc/sysctl.d", + "/run/sysctl.d", + "/usr/local/lib/sysctl.d", + "/usr/lib/sysctl.d", + "/lib/sysctl.d", + }; + struct pair **cfgs = NULL; + unsigned ncfgs = 0; + int rc = EXIT_SUCCESS; + struct stat ts; + enum { nprealloc = 16 }; + + for (di = 0; di < sizeof(dirs) / sizeof(dirs[0]); ++di) { + struct dirent *de; + DIR *dp = opendir(dirs[di]); + if (!dp) + continue; + + while ((de = readdir(dp))) { + if (!strcmp(de->d_name, ".") + || !strcmp(de->d_name, "..")) + continue; + if (strlen(de->d_name) < 5 + || strcmp(de->d_name + strlen(de->d_name) - 5, ".conf")) + continue; + /* check if config already known */ + for (i = 0; i < ncfgs; ++i) { + if (cfgs && !strcmp(cfgs[i]->name, de->d_name)) + break; + } + if (i < ncfgs) + /* already in */ + continue; + + if (ncfgs % nprealloc == 0) + cfgs = + xrealloc(cfgs, + sizeof(struct pair *) * (ncfgs + + nprealloc)); + + if (cfgs) { + cfgs[ncfgs] = + xmalloc(sizeof(struct pair) + + strlen(de->d_name) * 2 + 2 + + strlen(dirs[di]) + 1); + cfgs[ncfgs]->name = + (char *)cfgs[ncfgs] + sizeof(struct pair); + strcpy(cfgs[ncfgs]->name, de->d_name); + cfgs[ncfgs]->value = + (char *)cfgs[ncfgs] + sizeof(struct pair) + + strlen(cfgs[ncfgs]->name) + 1; + sprintf(cfgs[ncfgs]->value, "%s/%s", dirs[di], + de->d_name); + ncfgs++; + } else { + xerrx(EXIT_FAILURE, _("internal error")); + } + + } + closedir(dp); + } + qsort(cfgs, ncfgs, sizeof(struct cfg *), sortpairs); + + for (i = 0; i < ncfgs; ++i) { + if (!Quiet) + printf(_("* Applying %s ...\n"), cfgs[i]->value); + rc |= Preload(setlist, cfgs[i]->value); + } + + + if (stat(DEFAULT_PRELOAD, &ts) == 0 && S_ISREG(ts.st_mode)) { + if (!Quiet) + printf(_("* Applying %s ...\n"), DEFAULT_PRELOAD); + rc |= Preload(setlist, DEFAULT_PRELOAD); + } + + /* cleaning */ + for (i = 0; i < ncfgs; ++i) { + free(cfgs[i]); + } + if (cfgs) free(cfgs); + + return rc; +} + +/* + * Main... + */ +int main(int argc, char *argv[]) +{ + bool WriteMode = false; + bool DisplayAllOpt = false; + bool preloadfileOpt = false; + int ReturnCode = 0; + int c; + int rc = 0; + const char *preloadfile = NULL; + SettingList *setlist; + + enum { + DEPRECATED_OPTION = CHAR_MAX + 1, + SYSTEM_OPTION, + DRYRUN_OPTION + }; + static const struct option longopts[] = { + {"all", no_argument, NULL, 'a'}, + {"deprecated", no_argument, NULL, DEPRECATED_OPTION}, + {"dry-run", no_argument, NULL, DRYRUN_OPTION}, + {"binary", no_argument, NULL, 'b'}, + {"ignore", no_argument, NULL, 'e'}, + {"names", no_argument, NULL, 'N'}, + {"values", no_argument, NULL, 'n'}, + {"load", optional_argument, NULL, 'p'}, + {"quiet", no_argument, NULL, 'q'}, + {"write", no_argument, NULL, 'w'}, + {"system", no_argument, NULL, SYSTEM_OPTION}, + {"pattern", required_argument, NULL, 'r'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + PrintName = true; + PrintNewline = true; + IgnoreError = false; + Quiet = false; + IgnoreDeprecated = true; + DryRun = false; + setlist = xmalloc(sizeof(SettingList)); + setlist->head = NULL; + setlist->tail = NULL; + + if (argc < 2) + Usage(stderr); + + while ((c = + getopt_long(argc, argv, "bneNwfp::qoxaAXr:Vdh", longopts, + NULL)) != -1) { + switch (c) { + case 'b': + /* This is "binary" format, which means more for BSD. */ + PrintNewline = false; + /* FALL THROUGH */ + case 'n': + PrintName = false; + break; + case 'e': + /* + * For FreeBSD, -e means a "%s=%s\n" format. + * ("%s: %s\n" default). We (and NetBSD) use + * "%s = %s\n" always, and -e to ignore errors. + */ + IgnoreError = true; + break; + case 'N': + NameOnly = true; + break; + case 'w': + WriteMode = true; + break; + case 'f': /* the NetBSD way */ + case 'p': + preloadfileOpt = true; + if (optarg) + preloadfile = optarg; + break; + case 'q': + Quiet = true; + break; + case 'o': /* BSD: binary values too, 1st 16 bytes in hex */ + case 'x': /* BSD: binary values too, whole thing in hex */ + /* does nothing */ ; + break; + case 'a': /* string and integer values (for Linux, all of them) */ + case 'A': /* same as -a -o */ + case 'X': /* same as -a -x */ + DisplayAllOpt = true; + break; + case DEPRECATED_OPTION: + IgnoreDeprecated = false; + break; + case SYSTEM_OPTION: + IgnoreError = true; + rc |= PreloadSystem(setlist); + rc |= write_setting_list(setlist); + return rc; + case DRYRUN_OPTION: + DryRun = true; + break; + case 'r': + pattern = xstrdup(optarg); + break; + case 'V': + printf(PROCPS_NG_VERSION); + return EXIT_SUCCESS; + case 'd': /* BSD: print description ("vm.kvm_size: Size of KVM") */ + case 'h': /* BSD: human-readable (did FreeBSD 5 make -e default?) */ + case '?': + Usage(stdout); + default: + Usage(stderr); + } + } + + argc -= optind; + argv += optind; + + iobuf = xmalloc(iolen); + + if (DisplayAllOpt) + return DisplayAll(PROC_PATH); + + if (preloadfileOpt) { + int ret = EXIT_SUCCESS, i; + if (!preloadfile) { + if (!argc) { + ret |= Preload(setlist, DEFAULT_PRELOAD); + } + } else { + /* This happens when -pfile option is + * used without space. */ + ret |= Preload(setlist, preloadfile); + } + for (i = 0; i < argc; i++) + ret |= Preload(setlist, argv[i]); + ret |= write_setting_list(setlist); + return ret; + } + + if (argc < 1) + xerrx(EXIT_FAILURE, _("no variables specified\n" + "Try `%s --help' for more information."), + program_invocation_short_name); + if (NameOnly && Quiet) + xerrx(EXIT_FAILURE, _("options -N and -q cannot coexist\n" + "Try `%s --help' for more information."), + program_invocation_short_name); + + for ( ; *argv; argv++) { + if (WriteMode || strchr(*argv, '=')) { + SysctlSetting *s; + if ( (s = parse_setting_line("command line", 0, *argv)) != NULL) + ReturnCode |= WriteSetting(s->key, s->path, s->value, + s->ignore_failure); + else + ReturnCode |= EXIT_FAILURE; + } else + ReturnCode += ReadSetting(*argv); + } + return ReturnCode; +} diff --git a/src/tests/test_fileutils.c b/src/tests/test_fileutils.c new file mode 100644 index 0000000..ebfc5e2 --- /dev/null +++ b/src/tests/test_fileutils.c @@ -0,0 +1,10 @@ +#include <stdio.h> +#include <stdlib.h> +#include "fileutils.h" + +int main(int argc, char *argv[]) +{ + atexit(close_stdout); + printf("Hello, World!\n"); + return EXIT_SUCCESS; +} diff --git a/src/tests/test_process.c b/src/tests/test_process.c new file mode 100644 index 0000000..ef2582e --- /dev/null +++ b/src/tests/test_process.c @@ -0,0 +1,115 @@ +/* + * test_procps -- program to create a process to test procps + * + * Copyright 2015 Craig Small <csmall@dropbear.xyz> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#ifdef __linux__ +#include <sys/prctl.h> +#endif +#include "c.h" + +#define DEFAULT_SLEEPTIME 300 +#define MY_NAME "spcorp" + +static void usage(void) +{ + fprintf(stderr, " %s [options]\n", program_invocation_short_name); + fprintf(stderr, " -s <seconds>\n"); + exit(EXIT_FAILURE); +} + + +void +signal_handler(int signum, siginfo_t *siginfo, void *ucontext) +{ + char *signame = NULL; + + switch(signum) { + case SIGUSR1: + signame = strdup("SIGUSR1"); + break; + case SIGUSR2: + signame = strdup("SIGUSR2"); + break; + default: + printf("SIG unknown\n"); + exit(EXIT_FAILURE); + } + if (signame == NULL) { + printf("SIG malloc error\n"); + exit(EXIT_FAILURE); + } + switch (siginfo->si_code) { + case SI_USER: + printf("SIG %s\n", signame); + break; + case SI_QUEUE: +#ifdef HAVE_SIGINFO_T_SI_INT + printf("SIG %s value=%d\n", signame, siginfo->si_int); +#else + printf("case SI_QUEUE: SIG %s siginfo->si_int undefined\n", signame); +#endif + break; + default: + printf("Unknown si_code %d\n", siginfo->si_code); + exit(EXIT_FAILURE); + } + + free(signame); +} + +int main(int argc, char *argv[]) +{ + int sleep_time, opt; + struct sigaction signal_action; + + sleep_time = DEFAULT_SLEEPTIME; + while ((opt = getopt(argc, argv, "s:")) != -1) { + switch(opt) { + case 's': + sleep_time = atoi(optarg); + if (sleep_time < 1) { + fprintf(stderr, "sleep time must be 1 second or more\n"); + usage(); + } + break; + default: + usage(); + } + } + + /* Setup signal handling */ + signal_action.sa_sigaction = signal_handler; + sigemptyset (&signal_action.sa_mask); + signal_action.sa_flags = SA_SIGINFO; + sigaction(SIGUSR1, &signal_action, NULL); + sigaction(SIGUSR2, &signal_action, NULL); + +#ifdef __linux__ + /* set process name */ + prctl(PR_SET_NAME, MY_NAME, NULL, NULL, NULL); +#endif + + while (sleep_time > 0) { + sleep_time = sleep(sleep_time); + } + return EXIT_SUCCESS; +} diff --git a/src/tests/test_shm.c b/src/tests/test_shm.c new file mode 100644 index 0000000..3917379 --- /dev/null +++ b/src/tests/test_shm.c @@ -0,0 +1,69 @@ +/* + * test_shm -- program to create a shared memory segment for testing + * + * Copyright 2022 Craig Small <csmall@dropbear.xyz> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/shm.h> +#include "c.h" + +#define DEFAULT_SLEEPTIME 300 +#define SHM_SIZE 50 + +static void usage(void) +{ + fprintf(stderr, " %s [options]\n", program_invocation_short_name); + fprintf(stderr, " -s <seconds>\n"); + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) +{ + int sleep_time, opt; + int shm_id; + void *shm_addr; + + sleep_time = DEFAULT_SLEEPTIME; + while ((opt = getopt(argc, argv, "s:")) != -1) + { + switch(opt) + { + case 's': + sleep_time = atoi(optarg); + if (sleep_time < 1) + { + fprintf(stderr, "sleep time must be 1 second or more\n"); + usage(); + } + break; + default: + usage(); + } + } + + /* get some shared memory */ + if ( (shm_id = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666)) < 0) + xerr(EXIT_FAILURE, "Unable to shmget()"); + if ( (shm_addr = shmat(shm_id, NULL, SHM_RDONLY)) < 0) + xerr(EXIT_FAILURE, "Unable to shmat()"); + printf("SHMID: %x\n", shm_id); + sleep(sleep_time); + return EXIT_SUCCESS; +} + diff --git a/src/tests/test_strtod_nol.c b/src/tests/test_strtod_nol.c new file mode 100644 index 0000000..408cf46 --- /dev/null +++ b/src/tests/test_strtod_nol.c @@ -0,0 +1,51 @@ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "strutils.h" + +struct strtod_tests { + char *string; + double result; +}; + +struct strtod_tests tests[] = { + {"123", 123.0}, + {"-123", -123.0}, + {"12.34", 12.34}, + {"-12.34", -12.34}, + {".34", 0.34}, + {"-.34", -0.34}, + {"12,34", 12.34}, + {"-12,34", -12.34}, + {",34", 0.34}, + {"-,34", -0.34}, + {"0", 0.0}, + {".0", 0.0}, + {"0.0", 0.0}, + {NULL, 0.0} +}; + +#define EPSILON 1.0 // Really not trying for precision here +int dequal(const double d1, const double d2) +{ + return fabs(d1-d2) < EPSILON; +} + + +int main(int argc, char *argv[]) +{ + int i; + double val; + + for(i=0; tests[i].string != NULL; i++) { + if(!dequal (strtod_nol_or_err(tests[i].string, "Cannot parse number"), + tests[i].result)) { + fprintf(stderr, "FAIL: strtod_nol_or_err(\"%s\") != %f\n", + tests[i].string, tests[i].result); + return EXIT_FAILURE; + } + //fprintf(stderr, "PASS: strtod_nol for %s\n", tests[i].string); + } + return EXIT_SUCCESS; +} diff --git a/src/tests/test_strutils.c b/src/tests/test_strutils.c new file mode 100644 index 0000000..4a7c7aa --- /dev/null +++ b/src/tests/test_strutils.c @@ -0,0 +1,38 @@ +/* + * test_strutils.c - tests for strutils.c routines + * This file was copied from util-linux at fall 2011. + * + * Copyright (C) 2010 Karel Zak <kzak@redhat.com> + * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <stdlib.h> + +#include "c.h" +#include "strutils.h" + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + error(EXIT_FAILURE, 0, "no arguments"); + } else if (argc < 3) { + printf("%ld\n", strtol_or_err(argv[1], "strtol_or_err")); + } else { + printf("%lf\n", strtod_or_err(argv[2], "strtod_or_err")); + } + return EXIT_SUCCESS; +} diff --git a/src/tload.c b/src/tload.c new file mode 100644 index 0000000..9c19d9c --- /dev/null +++ b/src/tload.c @@ -0,0 +1,232 @@ +/* + * tload.c - terminal version of xload + * + * Copyright © 2009-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 1992 Branko Lankester + * + * /proc changes by David Engel (david@ods.com) + * Made a little more efficient by Michael K. Johnson (johnsonm@sunsite.unc.edu) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <unistd.h> +#include <limits.h> + +#include "c.h" +#include "fileutils.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "misc.h" + +static char *screen; + +static int nrows = 25; +static int ncols = 80; +static int scr_size; +static int fd = STDOUT_FILENO; +static unsigned int dly = 5; +static jmp_buf jb; + +static void alrm(int signo __attribute__ ((__unused__))) +{ + signal(SIGALRM, alrm); + alarm(dly); +} + +static void setsize(int i) +{ + struct winsize win; + + signal(SIGWINCH, setsize); + if (ioctl(fd, TIOCGWINSZ, &win) != -1) { + if (win.ws_col > 0) + ncols = win.ws_col; + if (win.ws_row > 0) + nrows = win.ws_row; + } + if (ncols < 2 || ncols >= INT_MAX) + xerrx(EXIT_FAILURE, _("screen too small or too large")); + if (nrows < 2 || nrows >= INT_MAX / ncols) + xerrx(EXIT_FAILURE, _("screen too small or too large")); + scr_size = nrows * ncols; + if (scr_size < 2) + xerrx(EXIT_FAILURE, _("screen too small")); + if (screen == NULL) + screen = (char *)xmalloc(scr_size); + else + screen = (char *)xrealloc(screen, scr_size); + + memset(screen, ' ', scr_size - 1); + *(screen + scr_size - 2) = '\0'; + if (i) + longjmp(jb, 1); +} + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] [tty]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -d, --delay <secs> update delay in seconds\n"), out); + fputs(_(" -s, --scale <num> vertical scale\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("tload(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int lines, row, col = 0; + int i, opt; + double av[3]; + static double max_scale = 0, scale_fact; + long tmpdly; + + static const struct option longopts[] = { + {"scale", required_argument, NULL, 's'}, + {"delay", required_argument, NULL, 'd'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((opt = + getopt_long(argc, argv, "s:d:Vh", longopts, NULL)) != -1) + switch (opt) { + case 's': + max_scale = strtod_or_err(optarg, _("failed to parse argument")); + if (max_scale < 0) + xerrx(EXIT_FAILURE, _("scale cannot be negative")); + break; + case 'd': + tmpdly = strtol_or_err(optarg, _("failed to parse argument")); + if (tmpdly < 1) + xerrx(EXIT_FAILURE, _("delay must be positive integer")); + else if (UINT_MAX < tmpdly) + xerrx(EXIT_FAILURE, _("too large delay value")); + dly = tmpdly; + break; + case 'V': + printf(PROCPS_NG_VERSION); + return EXIT_SUCCESS; + break; + case 'h': + usage(stdout); + default: + usage(stderr); + } + + if (argc > optind) + if ((fd = open(argv[optind], O_WRONLY)) == -1) + xerr(EXIT_FAILURE, _("can not open tty")); + + setsize(0); + + if (max_scale == 0) + max_scale = nrows; + + scale_fact = max_scale; + + setjmp(jb); + col = 0; + alrm(0); + + while (1) { + int rc; + + if (scale_fact < max_scale) + scale_fact *= 2.0; /* help it drift back up. */ + + if ((rc = procps_loadavg(&av[0], &av[1], &av[2])) < 0) + { + if (rc == -ENOENT) + xerrx(EXIT_FAILURE, + _("Load average file /proc/loadavg does not exist")); + else + xerrx(EXIT_FAILURE, + _("Unable to get load average")); + } + + do { + lines = av[0] * scale_fact; + row = nrows - 1; + + while (0 <= --lines) { + *(screen + row * ncols + col) = '*'; + if (--row < 0) { + scale_fact /= 2.0; + break; + } + } + } while (0 <= lines); + + while (row >= 0) + *(screen + row-- * ncols + col) = ' '; + + for (i = 1;; ++i) { + char *p; + row = nrows - (i * scale_fact); + if (row < 0 || row >= nrows) + break; + if (*(p = screen + row * ncols + col) == ' ') + *p = '-'; + else + *p = '='; + } + + if (++col == ncols) { + --col; + memmove(screen, screen + 1, scr_size - 1); + + for (row = nrows - 2; row >= 0; --row) + *(screen + row * ncols + col) = ' '; + } + i = snprintf(screen, scr_size, " %.2f, %.2f, %.2f", av[0], av[1], av[2]); + if (i > 0 && i < scr_size) + screen[i] = ' '; + + if (write(fd, "\033[H", 3) < 0) + xerr(EXIT_FAILURE, _("writing to tty failed")); + if (write(fd, screen, scr_size - 1) < 0) + xerr(EXIT_FAILURE, _("writing to tty failed")); + pause(); + } +} diff --git a/src/top/top.c b/src/top/top.c new file mode 100644 index 0000000..969c553 --- /dev/null +++ b/src/top/top.c @@ -0,0 +1,7404 @@ +/* top.c - Source file: show Linux processes */ +/* + * Copyright © 2002-2023 Jim Warner <james.warner@comcast.net + * + * This file may be used subject to the terms and conditions of the + * GNU Library General Public License Version 2, or any later version + * at your option, as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + */ +/* For contributions to this program, the author wishes to thank: + * Craig Small, <csmall@dropbear.xyz> + * Albert D. Cahalan, <albert@users.sf.net> + * Sami Kerola, <kerolasa@iki.fi> + */ + +#include <ctype.h> +#include <curses.h> +#include <errno.h> +#include <fcntl.h> +#include <float.h> +#include <getopt.h> +#include <limits.h> +#include <pwd.h> +#include <pthread.h> +#include <semaphore.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <term.h> // foul sob, defines all sorts of stuff... +#undef raw +#undef tab +#undef TTY +#include <termios.h> +#include <time.h> +#include <unistd.h> +#include <wchar.h> + +#include <sys/ioctl.h> +#include <sys/resource.h> +#include <sys/select.h> // also available via <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> // also available via <stdlib.h> + +#include "fileutils.h" +#include "signals.h" +#include "nls.h" + +#include "meminfo.h" +#include "misc.h" +#include "pids.h" +#include "stat.h" + +#include "top.h" +#include "top_nls.h" + +/*###### Miscellaneous global stuff ####################################*/ + + /* The original and new terminal definitions + (only set when not in 'Batch' mode) */ +static struct termios Tty_original, // our inherited terminal definition +#ifdef TERMIOS_ONLY + Tty_tweaked, // for interactive 'line' input +#endif + Tty_raw; // for unsolicited input +static int Ttychanged = 0; + + /* Last established cursor state/shape */ +static const char *Cursor_state = ""; + + /* Program name used in error messages and local 'rc' file name */ +static char *Myname; + + /* Our constant sigset, so we need initialize it but once */ +static sigset_t Sigwinch_set; + + /* The 'local' config file support */ +static char Rc_name [OURPATHSZ]; +static RCF_t Rc = DEF_RCFILE; +static int Rc_questions; +static int Rc_compatibilty; + + /* SMP, Irix/Solaris mode, Linux 2.5.xx support (and beyond) */ +static long Hertz; +static int Cpu_cnt; +static float Cpu_pmax; +static const char *Cpu_States_fmts; + + /* Specific process id monitoring support */ +static int Monpids [MONPIDMAX+1] = { 0 }; +static int Monpidsidx = 0; + + /* Current screen dimensions. + note: the number of processes displayed is tracked on a per window + basis (see the WIN_t). Max_lines is the total number of + screen rows after deducting summary information overhead. */ + /* Current terminal screen size. */ +static int Screen_cols, Screen_rows, Max_lines; + + /* These 'SCREEN_ROWS', 'BOT_ and 'Bot_' guys are used + in managing the special separate bottom 'window' ... */ +#define SCREEN_ROWS ( Screen_rows - Bot_rsvd ) +#define BOT_PRESENT ( Bot_what != 0 ) +#define BOT_ITEMMAX 10 // Bot_item array's max size +#define BOT_MSGSMAX 10 // total entries for Msg_tab +#define BOT_UNFOCUS -1 // tab focus not established + // negative 'item' values won't be seen by build_headers() ... +#define BOT_DELIMIT -1 // fencepost with item array +#define BOT_ITEM_NS -2 // data for namespaces req'd +#define BOT_MSG_LOG -3 // show the most recent msgs + // next 4 are used when toggling window contents +#define BOT_SEP_CMA ',' +#define BOT_SEP_SLS '/' +#define BOT_SEP_SMI ';' +#define BOT_SEP_SPC ' ' + // 1 for horizontal separator +#define BOT_RSVD 1 +#define BOT_KEEP Bot_show_func = NULL +#define BOT_TOSS do { Bot_show_func = NULL; Bot_item[0] = BOT_DELIMIT; \ + Bot_task = Bot_rsvd = Bot_what = 0; \ + Bot_indx = BOT_UNFOCUS; \ + } while(0) +static int Bot_task, + Bot_what, + Bot_rsvd, + Bot_indx = BOT_UNFOCUS, + Bot_item[BOT_ITEMMAX] = { BOT_DELIMIT }; +static char Bot_sep, + *Bot_head, + Bot_buf[BOTBUFSIZ]; // the 'environ' can be huge +typedef int(*BOT_f)(const void *, const void *); +static BOT_f Bot_focus_func; +static void(*Bot_show_func)(void); + + /* This is really the number of lines needed to display the summary + information (0 - nn), but is used as the relative row where we + stick the cursor between frames. */ +static int Msg_row; + + /* Global/Non-windows mode stuff that is NOT persistent */ +static int Batch = 0, // batch mode, collect no input, dumb output + Loops = -1, // number of iterations, -1 loops forever + Secure_mode = 0, // set if some functionality restricted + Width_mode = 0, // set w/ 'w' - potential output override + Thread_mode = 0; // set w/ 'H' - show threads vs. tasks + + /* Unchangeable cap's stuff built just once (if at all) and + thus NOT saved in a WIN_t's RCW_t. To accommodate 'Batch' + mode, they begin life as empty strings so the overlying + logic need not change ! */ +static char Cap_clr_eol [CAPBUFSIZ] = "", // global and/or static vars + Cap_nl_clreos [CAPBUFSIZ] = "", // are initialized to zeros! + Cap_clr_scr [CAPBUFSIZ] = "", // the assignments used here + Cap_curs_norm [CAPBUFSIZ] = "", // cost nothing but DO serve + Cap_curs_huge [CAPBUFSIZ] = "", // to remind people of those + Cap_curs_hide [CAPBUFSIZ] = "", // batch requirements! + Cap_clr_eos [CAPBUFSIZ] = "", + Cap_home [CAPBUFSIZ] = "", + Cap_norm [CAPBUFSIZ] = "", + Cap_reverse [CAPBUFSIZ] = "", + Caps_off [CAPBUFSIZ] = "", + Caps_endline [SMLBUFSIZ] = ""; +#ifndef RMAN_IGNORED +static char Cap_rmam [CAPBUFSIZ] = "", + Cap_smam [CAPBUFSIZ] = ""; + /* set to 1 if writing to the last column would be troublesome + (we don't distinguish the lowermost row from the other rows) */ +static int Cap_avoid_eol = 0; +#endif +static int Cap_can_goto = 0; + + /* Some optimization stuff, to reduce output demands... + The Pseudo_ guys are managed by adj_geometry and frame_make. They + are exploited in a macro and represent 90% of our optimization. + The Stdout_buf is transparent to our code and regardless of whose + buffer is used, stdout is flushed at frame end or if interactive. */ +static char *Pseudo_screen; +static int Pseudo_row = PROC_XTRA; +static size_t Pseudo_size; +#ifndef OFF_STDIOLBF + // less than stdout's normal buffer but with luck mostly '\n' anyway +static char Stdout_buf[2048]; +#endif + + /* Our four WIN_t's, and which of those is considered the 'current' + window (ie. which window is associated with any summ info displayed + and to which window commands are directed) */ +static WIN_t Winstk [GROUPSMAX]; +static WIN_t *Curwin; + + /* Frame oriented stuff that can't remain local to any 1 function + and/or that would be too cumbersome managed as parms, + and/or that are simply more efficiently handled as globals + [ 'Frames_...' (plural) stuff persists beyond 1 frame ] + [ or are used in response to async signals received ! ] */ +static volatile int Frames_signal; // time to rebuild all column headers +static float Frame_etscale; // so we can '*' vs. '/' WHEN 'pcpu' + + /* Support for automatically sized fixed-width column expansions. + * (hopefully, the macros help clarify/document our new 'feature') */ +static int Autox_array [EU_MAXPFLGS], + Autox_found; +#define AUTOX_NO EU_MAXPFLGS +#define AUTOX_COL(f) if (EU_MAXPFLGS > f && f >= 0) Autox_array[f] = Autox_found = 1 +#define AUTOX_MODE (0 > Rc.fixed_widest) + + /* Support for scale_mem and scale_num (to avoid duplication. */ +#ifdef CASEUP_SUFIX // nls_maybe + static char Scaled_sfxtab[] = { 'K', 'M', 'G', 'T', 'P', 'E', 0 }; +#else // nls_maybe + static char Scaled_sfxtab[] = { 'k', 'm', 'g', 't', 'p', 'e', 0 }; +#endif + + /* Support for NUMA Node display plus node expansion and targeting */ +#ifndef OFF_STDERROR +static int Stderr_save = -1; +#endif +static int Numa_node_tot; +static int Numa_node_sel = -1; + + /* Support for Graphing of the View_STATES ('t') and View_MEMORY ('m') + commands -- which are now both 4-way toggles */ +#define GRAPH_length_max 100 // the actual bars or blocks +#define GRAPH_length_min 10 // the actual bars or blocks +#define GRAPH_prefix_std 25 // '.......: 100.0/100.0 100[' +#define GRAPH_prefix_abv 12 // '.......:100[' +#define GRAPH_suffix 2 // '] ' (bracket + trailing space) + // first 3 more static (adj_geometry), last 3 volatile (sum_tics/do_memory) +struct graph_parms { + float adjust; // bars/blocks scaling factor + int length; // scaled length (<= GRAPH_length_max) + int style; // rc.graph_cpus or rc.graph_mems + long total, part1, part2; // elements to be graphed +}; +static struct graph_parms *Graph_cpus, *Graph_mems; +static const char Graph_blks[] = " "; +static const char Graph_bars[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"; + + /* Support for 'Other Filters' in the configuration file */ +static const char Osel_delim_1_txt[] = "begin: saved other filter data -------------------\n"; +static const char Osel_delim_2_txt[] = "end : saved other filter data -------------------\n"; +static const char Osel_window_fmts[] = "window #%d, osel_tot=%d\n"; +#define OSEL_FILTER "filter=" +static const char Osel_filterO_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%s\n"; +static const char Osel_filterI_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%*s\n"; + + /* Support for adjoining display (if terminal is wide enough) */ +#ifdef TOG4_SEP_OFF +static char Adjoin_sp[] = " "; +#define ADJOIN_space (sizeof(Adjoin_sp) - 1) // 1 for null +#else +#ifdef TOG4_SEP_STD +static char Adjoin_sp[] = "~1 ~6 "; +#else +static char Adjoin_sp[] = " ~6 ~1"; +#endif +#define ADJOIN_space (sizeof(Adjoin_sp) - 5) // 1 for null + 4 unprintable +#endif +#define ADJOIN_limit 8 + + /* Support for the new library API -- acquired (if necessary) + at program startup and referenced throughout our lifetime. */ + /* + * --- <proc/pids.h> -------------------------------------------------- */ +static struct pids_info *Pids_ctx; +static int Pids_itms_tot; // same as MAXTBL(Fieldstab) +static enum pids_item *Pids_itms; // allocated as MAXTBL(Fieldstab) +static struct pids_fetch *Pids_reap; // for reap or select +#define PIDSmaxt Pids_reap->counts->total // just a little less wordy + // pid stack results extractor macro, where e=our EU enum, t=type, s=stack + // ( we'll exploit that <proc/pids.h> provided macro as much as possible ) + // ( but many functions use their own unique tailored version for access ) +#define PID_VAL(e,t,s) PIDS_VAL(e, t, s, Pids_ctx) + /* + * --- <proc/stat.h> -------------------------------------------------- */ +static struct stat_info *Stat_ctx; +static struct stat_reaped *Stat_reap; +static enum stat_item Stat_items[] = { + STAT_TIC_ID, STAT_TIC_NUMA_NODE, + STAT_TIC_DELTA_USER, STAT_TIC_DELTA_SYSTEM, + STAT_TIC_DELTA_NICE, STAT_TIC_DELTA_IDLE, + STAT_TIC_DELTA_IOWAIT, STAT_TIC_DELTA_IRQ, + STAT_TIC_DELTA_SOFTIRQ, STAT_TIC_DELTA_STOLEN, + STAT_TIC_DELTA_GUEST, STAT_TIC_DELTA_GUEST_NICE, + STAT_TIC_SUM_DELTA_USER, STAT_TIC_SUM_DELTA_SYSTEM, +#ifdef CORE_TYPE_NO + STAT_TIC_SUM_DELTA_TOTAL }; +#else + STAT_TIC_SUM_DELTA_TOTAL, STAT_TIC_TYPE_CORE }; +#endif +enum Rel_statitems { + stat_ID, stat_NU, + stat_US, stat_SY, + stat_NI, stat_IL, + stat_IO, stat_IR, + stat_SI, stat_ST, + stat_GU, stat_GN, + stat_SUM_USR, stat_SUM_SYS, +#ifdef CORE_TYPE_NO + stat_SUM_TOT }; +#else + stat_SUM_TOT, stat_COR_TYP }; +#endif + // cpu/node stack results extractor macros, where e=rel enum, x=index +#define CPU_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->cpus->stacks[x], Stat_ctx) +#define NOD_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->numa->stacks[x], Stat_ctx) +#define TIC_VAL(e,s) STAT_VAL(e, sl_int, s, Stat_ctx) +#define E_CORE 1 // values for stat_COR_TYP itself +#define P_CORE 2 // ( 0 = unsure/unknown ) +#define P_CORES_ONLY 2 // values of rc.core_types toggle, for filtering +#define E_CORES_ONLY 3 // ( 0 = Cpu shown, 1 = both CpP & CpE shown ) + /* + * --- <proc/meminfo.h> ----------------------------------------------- */ +static struct meminfo_info *Mem_ctx; +static struct meminfo_stack *Mem_stack; +static enum meminfo_item Mem_items[] = { + MEMINFO_MEM_FREE, MEMINFO_MEM_USED, MEMINFO_MEM_TOTAL, + MEMINFO_MEM_CACHED_ALL, MEMINFO_MEM_BUFFERS, MEMINFO_MEM_AVAILABLE, + MEMINFO_SWAP_TOTAL, MEMINFO_SWAP_FREE, MEMINFO_SWAP_USED }; +enum Rel_memitems { + mem_FRE, mem_USE, mem_TOT, + mem_QUE, mem_BUF, mem_AVL, + swp_TOT, swp_FRE, swp_USE }; + // mem stack results extractor macro, where e=rel enum +#define MEM_VAL(e) MEMINFO_VAL(e, ul_int, Mem_stack, Mem_ctx) + + /* Support for concurrent library updates via + multithreaded background processes */ +#ifdef THREADED_CPU +static pthread_t Thread_id_cpus; +static sem_t Semaphore_cpus_beg, Semaphore_cpus_end; +#endif +#ifdef THREADED_MEM +static pthread_t Thread_id_memory; +static sem_t Semaphore_memory_beg, Semaphore_memory_end; +#endif +#ifdef THREADED_TSK +static pthread_t Thread_id_tasks; +static sem_t Semaphore_tasks_beg, Semaphore_tasks_end; +#endif +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK +static pthread_t Thread_id_main; +#endif + + /* Support for a namespace with proc mounted subset=pid, + ( we'll limit our display to task information only ). */ +static int Restrict_some = 0; + +/*###### Tiny useful routine(s) ########################################*/ + + /* + * This routine simply formats whatever the caller wants and + * returns a pointer to the resulting 'const char' string... */ +static const char *fmtmk (const char *fmts, ...) __attribute__((format(printf,1,2))); +static const char *fmtmk (const char *fmts, ...) { + static char buf[BIGBUFSIZ]; // with help stuff, our buffer + va_list va; // requirements now exceed 1k + + va_start(va, fmts); + vsnprintf(buf, sizeof(buf), fmts, va); + va_end(va); + return (const char *)buf; +} // end: fmtmk + + + /* + * Integer based fieldscur version of 'strlen' */ +static inline int mlen (const int *mem) { + int i; + + for (i = 0; mem[i]; i++) + ; + return i; +} // end: mlen + + + /* + * Integer based fieldscur version of 'strchr' */ +static inline int *msch (const int *mem, int obj, int max) { + int i; + + for (i = 0; i < max; i++) + if (*(mem + i) == obj) return (int *)mem + i; + return NULL; +} // end: msch + + + /* + * This guy is just our way of avoiding the overhead of the standard + * strcat function (should the caller choose to participate) */ +static inline char *scat (char *dst, const char *src) { + while (*dst) dst++; + while ((*(dst++) = *(src++))); + return --dst; +} // end: scat + + + /* + * This guy just facilitates Batch and protects against dumb ttys + * -- we'd 'inline' him but he's only called twice per frame, + * yet used in many other locations. */ +static const char *tg2 (int x, int y) { + // it's entirely possible we're trying for an invalid row... + return Cap_can_goto ? tgoto(cursor_address, x, y) : ""; +} // end: tg2 + +/*###### Exit/Interrupt routines #######################################*/ + + /* + * Reset the tty, if necessary */ +static void at_eoj (void) { + if (Ttychanged) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original); + if (keypad_local) putp(keypad_local); + putp(tg2(0, Screen_rows)); + putp("\n"); +#ifdef OFF_SCROLLBK + if (exit_ca_mode) { + // this next will also replace top's most recent screen with the + // original display contents that were visible at our invocation + putp(exit_ca_mode); + } +#endif + putp(Cap_curs_norm); + putp(Cap_clr_eol); +#ifndef RMAN_IGNORED + putp(Cap_smam); +#endif + } + fflush(stdout); +#ifndef OFF_STDERROR + /* we gotta reverse the stderr redirect which was employed during start up + and needed because the two libnuma 'weak' functions were useless to us! */ + if (-1 < Stderr_save) { + dup2(Stderr_save, fileno(stderr)); + close(Stderr_save); + Stderr_save = -1; // we'll be ending soon anyway but what the heck + } +#endif +} // end: at_eoj + + + /* + * The real program end */ +static void bye_bye (const char *str) __attribute__((__noreturn__)); +static void bye_bye (const char *str) { + sigset_t ss; + +// POSIX.1 async-signal-safe: sigfillset, sigprocmask, pthread_sigmask + sigfillset(&ss); +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + pthread_sigmask(SIG_BLOCK, &ss, NULL); +#else + sigprocmask(SIG_BLOCK, &ss, NULL); +#endif + at_eoj(); // restore tty in preparation for exit +#ifdef ATEOJ_RPTSTD +{ + if (!str && !Frames_signal && Ttychanged) { fprintf(stderr, + "\n%s's Summary report:" + "\n\tProgram" + "\n\t %s" + "\n\t Hertz = %u (%u bytes, %u-bit time)" + "\n\t Stat_reap->cpus->total = %d, Stat_reap->numa->total = %d" + "\n\t Pids_itms_tot = %d, sizeof(struct pids_result) = %d, pids stack size = %d" + "\n\t SCREENMAX = %d, ROWMINSIZ = %d, ROWMAXSIZ = %d" + "\n\t PACKAGE = '%s', LOCALEDIR = '%s'" + "\n\tTerminal: %s" + "\n\t device = %s, ncurses = v%s" + "\n\t max_colors = %d, max_pairs = %d" + "\n\t Cap_can_goto = %s" + "\n\t Screen_cols = %d, Screen_rows = %d" + "\n\t Max_lines = %d, most recent Pseudo_size = %u" +#ifndef OFF_STDIOLBF + "\n\t Stdout_buf = %u, BUFSIZ = %u" +#endif + "\n\tWindows and Curwin->" + "\n\t sizeof(WIN_t) = %u, GROUPSMAX = %d" + "\n\t winname = %s, grpname = %s" +#ifdef CASEUP_HEXES + "\n\t winflags = %08X, maxpflgs = %d" +#else + "\n\t winflags = x%08x, maxpflgs = %d" +#endif + "\n\t sortindx = %d, maxtasks = %d" + "\n\t varcolsz = %d, winlines = %d" + "\n\t strlen(columnhdr) = %d" + "\n\t current fieldscur = %d, maximum fieldscur = %d" + "\n" + , __func__ + , PACKAGE_STRING + , (unsigned)Hertz, (unsigned)sizeof(Hertz), (unsigned)sizeof(Hertz) * 8 + , Stat_reap->cpus->total, Stat_reap->numa->total + , Pids_itms_tot, (int)sizeof(struct pids_result), (int)(sizeof(struct pids_result) * Pids_itms_tot) + , (int)SCREENMAX, (int)ROWMINSIZ, (int)ROWMAXSIZ + , PACKAGE, LOCALEDIR +#ifdef PRETENDNOCAP + , "dumb" +#else + , termname() +#endif + , ttyname(STDOUT_FILENO), NCURSES_VERSION + , max_colors, max_pairs + , Cap_can_goto ? "yes" : "No!" + , Screen_cols, Screen_rows + , Max_lines, (unsigned)Pseudo_size +#ifndef OFF_STDIOLBF + , (unsigned)sizeof(Stdout_buf), (unsigned)BUFSIZ +#endif + , (unsigned)sizeof(WIN_t), GROUPSMAX + , Curwin->rc.winname, Curwin->grpname + , Curwin->rc.winflags, Curwin->maxpflgs + , Curwin->rc.sortindx, Curwin->rc.maxtasks + , Curwin->varcolsz, Curwin->winlines + , (int)strlen(Curwin->columnhdr) + , EU_MAXPFLGS, mlen(Curwin->rc.fieldscur) + ); + } +} +#endif // end: ATEOJ_RPTSTD + + // there's lots of signal-unsafe stuff in the following ... + if (Frames_signal != BREAK_sig) { +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + /* can not execute any cleanup from a sibling thread and + we will violate proper indentation to minimize impact */ + if (pthread_equal(Thread_id_main, pthread_self())) { +#endif +#ifdef THREADED_CPU + pthread_cancel(Thread_id_cpus); + pthread_join(Thread_id_cpus, NULL); + sem_destroy(&Semaphore_cpus_end); + sem_destroy(&Semaphore_cpus_beg); +#endif +#ifdef THREADED_MEM + pthread_cancel(Thread_id_memory); + pthread_join(Thread_id_memory, NULL); + sem_destroy(&Semaphore_memory_end); + sem_destroy(&Semaphore_memory_beg); +#endif +#ifdef THREADED_TSK + pthread_cancel(Thread_id_tasks); + pthread_join(Thread_id_tasks, NULL); + sem_destroy(&Semaphore_tasks_end); + sem_destroy(&Semaphore_tasks_beg); +#endif + procps_pids_unref(&Pids_ctx); + procps_stat_unref(&Stat_ctx); + procps_meminfo_unref(&Mem_ctx); +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + } +#endif + } + + /* we'll only have a 'str' if called by error_exit() | + and parse_args(), never from a signal handler ... | */ + if (str) { + fputs(str, stderr); + exit(EXIT_FAILURE); + } + /* this could happen when called from several places | + including that sig_endpgm(). thus we must use an | + async-signal-safe write function just in case ... | + (thanks: Shaohua Zhan shaohua.zhan@windriver.com) | */ + if (Batch) + write(fileno(stdout), "\n", sizeof("\n") - 1); + + exit(EXIT_SUCCESS); +} // end: bye_bye + + + /* + * Standard error handler to normalize the look of all err output */ +static void error_exit (const char *str) __attribute__((__noreturn__)); +static void error_exit (const char *str) { + static char buf[MEDBUFSIZ]; + + Frames_signal = BREAK_off; + /* we'll use our own buffer so callers can still use fmtmk() and, after + twelve long years, 2013 was the year we finally eliminated the leading + tab character -- now our message can get lost in screen clutter too! */ + snprintf(buf, sizeof(buf), "%s: %s\n", Myname, str); + bye_bye(buf); +} // end: error_exit + + + /* + * Catches all remaining signals not otherwise handled */ +static void sig_abexit (int sig) __attribute__((__noreturn__)); +static void sig_abexit (int sig) { + sigset_t ss; + +// POSIX.1 async-signal-safe: sigfillset, signal, sigemptyset, sigaddset, sigprocmask, pthread_sigmask, raise + sigfillset(&ss); +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + pthread_sigmask(SIG_BLOCK, &ss, NULL); +#else + sigprocmask(SIG_BLOCK, &ss, NULL); +#endif + at_eoj(); // restore tty in preparation for exit + fprintf(stderr, N_fmt(EXIT_signals_fmt) + , sig, signal_number_to_name(sig), Myname); + signal(sig, SIG_DFL); // allow core dumps, if applicable + sigemptyset(&ss); + sigaddset(&ss, sig); +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + pthread_sigmask(SIG_UNBLOCK, &ss, NULL); +#else + sigprocmask(SIG_UNBLOCK, &ss, NULL); +#endif + raise(sig); // ( plus set proper return code ) + _exit(EXIT_FAILURE); // if default sig action is ignore +} // end: sig_abexit + + + /* + * Catches: + * SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, + * SIGUSR1 and SIGUSR2 */ +static void sig_endpgm (int dont_care_sig) __attribute__((__noreturn__)); +static void sig_endpgm (int dont_care_sig) { + Frames_signal = BREAK_sig; + bye_bye(NULL); + (void)dont_care_sig; +} // end: sig_endpgm + + + /* + * Catches: + * SIGTSTP, SIGTTIN and SIGTTOU */ +static void sig_paused (int dont_care_sig) { +// POSIX.1 async-signal-safe: tcsetattr, tcdrain, raise + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original)) + error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno))); + if (keypad_local) putp(keypad_local); + putp(tg2(0, Screen_rows)); + putp(Cap_curs_norm); +#ifndef RMAN_IGNORED + putp(Cap_smam); +#endif + // tcdrain(STDOUT_FILENO) was not reliable prior to ncurses-5.9.20121017, + // so we'll risk POSIX's wrath with good ol' fflush, lest 'Stopped' gets + // co-mingled with our most recent output... + fflush(stdout); + raise(SIGSTOP); + // later, after SIGCONT... + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw)) + error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno))); +#ifndef RMAN_IGNORED + putp(Cap_rmam); +#endif + if (keypad_xmit) putp(keypad_xmit); + putp(Cursor_state); + Frames_signal = BREAK_sig; + (void)dont_care_sig; +} // end: sig_paused + + + /* + * Catches: + * SIGCONT and SIGWINCH */ +static void sig_resize (int dont_care_sig) { +// POSIX.1 async-signal-safe: tcdrain + tcdrain(STDOUT_FILENO); + Frames_signal = BREAK_sig; + (void)dont_care_sig; +} // end: sig_resize + +/*###### Special UTF-8 Multi-Byte support ##############################*/ + + /* Support for NLS translated multi-byte strings */ +static char UTF8_tab[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 0x0F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 0x1F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 0x3F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x80 - 0x8F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x90 - 0x9F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xA0 - 0xAF + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xB0 - 0xBF + -1,-1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xC0 - 0xCF, 0xC2 = begins 2 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0 - 0xDF + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0 - 0xEF, 0xE0 = begins 3 + 4, 4, 4, 4, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xF0 - 0xFF, 0xF0 = begins 4 +}; // ( 0xF5 & beyond invalid ) + + + /* + * Accommodate any potential differences between some multibyte + * character sequence and the screen columns needed to print it */ +static inline int utf8_cols (const unsigned char *p, int n) { +#ifndef OFF_XTRAWIDE + wchar_t wc; + + if (n > 1) { + (void)mbtowc(&wc, (const char *)p, n); + // allow a zero as valid, as with a 'combining acute accent' + if ((n = wcwidth(wc)) < 0) n = 1; + } + return n; +#else + (void)p; (void)n; + return 1; +#endif +} // end: utf8_cols + + + /* + * Determine difference between total bytes versus printable + * characters in that passed, potentially multi-byte, string */ +static int utf8_delta (const char *str) { + const unsigned char *p = (const unsigned char *)str; + int clen, cnum = 0; + + while (*p) { + // -1 represents a decoding error, pretend it's untranslated ... + if (0 > (clen = UTF8_tab[*p])) return 0; + cnum += utf8_cols(p, clen); + p += clen; + } + return (int)((const char *)p - str) - cnum; +} // end: utf8_delta + + + /* + * Determine a physical end within a potential multi-byte string + * where maximum printable chars could be accommodated in width */ +static int utf8_embody (const char *str, int width) { + const unsigned char *p = (const unsigned char *)str; + int clen, cnum = 0; + + if (width > 0) { + while (*p) { + // -1 represents a decoding error, pretend it's untranslated ... + if (0 > (clen = UTF8_tab[*p])) return width; + if (width < (cnum += utf8_cols(p, clen))) break; + p += clen; + } + } + return (int)((const char *)p - str); +} // end: utf8_embody + + + /* + * Like the regular justify_pad routine but this guy + * can accommodate the multi-byte translated strings */ +static const char *utf8_justify (const char *str, int width, int justr) { + static char l_fmt[] = "%-*.*s%s", r_fmt[] = "%*.*s%s"; + static char buf[SCREENMAX]; + char tmp[SCREENMAX]; + + snprintf(tmp, sizeof(tmp), "%.*s", utf8_embody(str, width), str); + width += utf8_delta(tmp); + snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, tmp, COLPADSTR); + return buf; +} // end: utf8_justify + + + /* + * Returns a physical or logical column number given a + * multi-byte string and a target column value */ +static int utf8_proper_col (const char *str, int col, int tophysical) { + const unsigned char *p = (const unsigned char *)str; + int clen, tlen = 0, cnum = 0; + + while (*p) { + // -1 represents a decoding error, don't encourage repositioning ... + if (0 > (clen = UTF8_tab[*p])) return col; + if (cnum + 1 > col && tophysical) break; + p += clen; + tlen += clen; + if (tlen > col && !tophysical) break; + ++cnum; + } + return tophysical ? tlen : cnum; +} // end: utf8_proper_col + +/*###### Misc Color/Display support ####################################*/ + + /* + * Make the appropriate caps/color strings for a window/field group. + * note: we avoid the use of background color so as to maximize + * compatibility with the user's xterm settings */ +static void capsmk (WIN_t *q) { + /* macro to test if a basic (non-color) capability is valid + thanks: Floyd Davidson <floyd@ptialaska.net> */ + #define tIF(s) s ? s : "" + /* macro to make compatible with netbsd-curses too + thanks: rofl0r <retnyg@gmx.net> */ + #define tPM(a,b) tparm(a, b, 0, 0, 0, 0, 0, 0, 0, 0) + static int capsdone = 0; + + // we must NOT disturb our 'empty' terminfo strings! + if (Batch) return; + + // these are the unchangeable puppies, so we only do 'em once + if (!capsdone) { + STRLCPY(Cap_clr_eol, tIF(clr_eol)) + STRLCPY(Cap_clr_eos, tIF(clr_eos)) + STRLCPY(Cap_clr_scr, tIF(clear_screen)) + // due to the leading newline, the following must be used with care + snprintf(Cap_nl_clreos, sizeof(Cap_nl_clreos), "\n%s", tIF(clr_eos)); + STRLCPY(Cap_curs_huge, tIF(cursor_visible)) + STRLCPY(Cap_curs_norm, tIF(cursor_normal)) + STRLCPY(Cap_curs_hide, tIF(cursor_invisible)) + STRLCPY(Cap_home, tIF(cursor_home)) + STRLCPY(Cap_norm, tIF(exit_attribute_mode)) + STRLCPY(Cap_reverse, tIF(enter_reverse_mode)) +#ifndef RMAN_IGNORED + if (!eat_newline_glitch) { + STRLCPY(Cap_rmam, tIF(exit_am_mode)) + STRLCPY(Cap_smam, tIF(enter_am_mode)) + if (!*Cap_rmam || !*Cap_smam) { + *Cap_rmam = '\0'; + *Cap_smam = '\0'; + if (auto_right_margin) + Cap_avoid_eol = 1; + } + putp(Cap_rmam); + } +#endif + snprintf(Caps_off, sizeof(Caps_off), "%s%s", Cap_norm, tIF(orig_pair)); + snprintf(Caps_endline, sizeof(Caps_endline), "%s%s", Caps_off, Cap_clr_eol); + if (tgoto(cursor_address, 1, 1)) Cap_can_goto = 1; + capsdone = 1; + } + + /* the key to NO run-time costs for configurable colors -- we spend a + little time with the user now setting up our terminfo strings, and + the job's done until he/she/it has a change-of-heart */ + STRLCPY(q->cap_bold, CHKw(q, View_NOBOLD) ? Cap_norm : tIF(enter_bold_mode)) + if (CHKw(q, Show_COLORS) && max_colors > 0) { + STRLCPY(q->capclr_sum, tPM(set_a_foreground, q->rc.summclr)) + snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s" + , tPM(set_a_foreground, q->rc.msgsclr), Cap_reverse); + snprintf(q->capclr_pmt, sizeof(q->capclr_pmt), "%s%s" + , tPM(set_a_foreground, q->rc.msgsclr), q->cap_bold); + snprintf(q->capclr_hdr, sizeof(q->capclr_hdr), "%s%s" + , tPM(set_a_foreground, q->rc.headclr), Cap_reverse); + snprintf(q->capclr_rownorm, sizeof(q->capclr_rownorm), "%s%s" + , Caps_off, tPM(set_a_foreground, q->rc.taskclr)); + } else { + q->capclr_sum[0] = '\0'; +#ifdef USE_X_COLHDR + snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s" + , Cap_reverse, q->cap_bold); +#else + STRLCPY(q->capclr_msg, Cap_reverse) +#endif + STRLCPY(q->capclr_pmt, q->cap_bold) + STRLCPY(q->capclr_hdr, Cap_reverse) + STRLCPY(q->capclr_rownorm, Cap_norm) + } + + // composite(s), so we do 'em outside and after the if + snprintf(q->capclr_rowhigh, sizeof(q->capclr_rowhigh), "%s%s" + , q->capclr_rownorm, CHKw(q, Show_HIBOLD) ? q->cap_bold : Cap_reverse); + #undef tIF + #undef tPM +} // end: capsmk + + +static struct msg_node { + char msg[SMLBUFSIZ]; + struct msg_node *prev; +} Msg_tab[BOT_MSGSMAX]; + +static struct msg_node *Msg_this = Msg_tab; + + /* + * Show an error message (caller may include '\a' for sound) */ +static void show_msg (const char *str) { + STRLCPY(Msg_this->msg, str); + if (++Msg_this > &Msg_tab[BOT_MSGSMAX - 1]) Msg_this = Msg_tab; + + PUTT("%s%s %.*s %s%s%s" + , tg2(0, Msg_row) + , Curwin->capclr_msg + , utf8_embody(str, Screen_cols - 2) + , str + , Cap_curs_hide + , Caps_off + , Cap_clr_eol); + fflush(stdout); + usleep(MSG_USLEEP); +} // end: show_msg + + + /* + * Show an input prompt + larger cursor (if possible) */ +static int show_pmt (const char *str) { + char buf[MEDBUFSIZ]; + int len; + + snprintf(buf, sizeof(buf), "%.*s", utf8_embody(str, Screen_cols - 2), str); + len = utf8_delta(buf); +#ifdef PRETENDNOCAP + PUTT("\n%s%s%.*s %s%s%s" +#else + PUTT("%s%s%.*s %s%s%s" +#endif + , tg2(0, Msg_row) + , Curwin->capclr_pmt + , (Screen_cols - 2) + len + , buf + , Cap_curs_huge + , Caps_off + , Cap_clr_eol); + fflush(stdout); + len = strlen(buf) - len; + // +1 for the space we added or -1 for the cursor... + return (len + 1 < Screen_cols) ? len + 1 : Screen_cols - 1; +} // end: show_pmt + + + /* + * Create and print the optional scroll coordinates message */ +static void show_scroll (void) { + char tmp1[SMLBUFSIZ]; +#ifndef SCROLLVAR_NO + char tmp2[SMLBUFSIZ]; +#endif + int totpflgs = Curwin->totpflgs; + int begpflgs = Curwin->begpflg + 1; + +#ifndef USE_X_COLHDR + if (CHKw(Curwin, Show_HICOLS)) { + totpflgs -= 2; + if (ENUpos(Curwin, Curwin->rc.sortindx) < Curwin->begpflg) begpflgs -= 2; + } +#endif + if (1 > totpflgs) totpflgs = 1; + if (1 > begpflgs) begpflgs = 1; + snprintf(tmp1, sizeof(tmp1), N_fmt(SCROLL_coord_fmt), Curwin->begtask + 1, PIDSmaxt, begpflgs, totpflgs); +#ifndef SCROLLVAR_NO + if (Curwin->varcolbeg) { + snprintf(tmp2, sizeof(tmp2), " + %d", Curwin->varcolbeg); + scat(tmp1, tmp2); + } +#endif + PUTT("%s%s %.*s%s", tg2(0, Msg_row), Caps_off, Screen_cols - 3, tmp1, Cap_clr_eol); +} // end: show_scroll + + + /* + * Show lines with specially formatted elements, but only output + * what will fit within the current screen width. + * Our special formatting consists of: + * "some text <_delimiter_> some more text <_delimiter_>...\n" + * Where <_delimiter_> is a two byte combination consisting of a + * tilde followed by an ascii digit in the range of 1 - 8. + * examples: ~1, ~5, ~8, etc. + * The tilde is effectively stripped and the next digit + * converted to an index which is then used to select an + * 'attribute' from a capabilities table. That attribute + * is then applied to the *preceding* substring. + * Once recognized, the delimiter is replaced with a null character + * and viola, we've got a substring ready to output! Strings or + * substrings without delimiters will receive the Cap_norm attribute. + * + * Caution: + * This routine treats all non-delimiter bytes as displayable + * data subject to our screen width marching orders. If callers + * embed non-display data like tabs or terminfo strings in our + * glob, a line will truncate incorrectly at best. Worse case + * would be truncation of an embedded tty escape sequence. + * + * Tabs must always be avoided or our efforts are wasted and + * lines will wrap. To lessen but not eliminate the risk of + * terminfo string truncation, such non-display stuff should + * be placed at the beginning of a "short" line. */ +static void show_special (int interact, const char *glob) { + /* note: the following is for documentation only, + the real captab is now found in a group's WIN_t ! + +------------------------------------------------------+ + | char *captab[] = { : Cap's = Index | + | Cap_norm, Cap_norm, = \000, \001, | + | cap_bold, capclr_sum, = \002, \003, | + | capclr_msg, capclr_pmt, = \004, \005, | + | capclr_hdr, = \006, | + | capclr_rowhigh, = \007, | + | capclr_rownorm }; = \010 [octal!] | + +------------------------------------------------------+ */ + /* ( Pssst, after adding the termcap transitions, row may ) + ( exceed 300+ bytes, even in an 80x24 terminal window! ) + ( Shown here are the former buffer size specifications ) + ( char tmp[SMLBUFSIZ], lin[MEDBUFSIZ], row[LRGBUFSIZ]. ) + ( So now we use larger buffers and a little protection ) + ( against overrunning them with this 'lin_end - glob'. ) + + ( That was uncovered during 'Inspect' development when ) + ( this guy was being considered for a supporting role. ) + ( However, such an approach was abandoned. As a result ) + ( this function is called only with a glob under top's ) + ( control and never containing any 'raw/binary' chars! ) */ + char tmp[LRGBUFSIZ], lin[LRGBUFSIZ], row[ROWMINSIZ]; + char *rp, *lin_end, *sub_beg, *sub_end; + int room; + + // handle multiple lines passed in a bunch + while ((lin_end = strchr(glob, '\n'))) { + #define myMIN(a,b) (((a) < (b)) ? (a) : (b)) + size_t lessor = myMIN((size_t)(lin_end - glob), sizeof(lin) -3); + + // create a local copy we can extend and otherwise abuse + memcpy(lin, glob, lessor); + // zero terminate this part and prepare to parse substrings + lin[lessor] = '\0'; + room = Screen_cols; + sub_beg = sub_end = lin; + *(rp = row) = '\0'; + + while (*sub_beg) { + int ch = *sub_end; + if ('~' == ch) ch = *(sub_end + 1) - '0'; + switch (ch) { + case 0: // no end delim, captab makes normal + // only possible when '\n' was NOT preceded with a '~#' sequence + // ( '~1' thru '~8' is valid range, '~0' is never actually used ) + *(sub_end + 1) = '\0'; // extend str end, then fall through + *(sub_end + 2) = '\0'; // ( +1 optimization for usual path ) + // fall through + case 1: case 2: case 3: case 4: + case 5: case 6: case 7: case 8: + *sub_end = '\0'; + snprintf(tmp, sizeof(tmp), "%s%.*s%s" + , Curwin->captab[ch], utf8_embody(sub_beg, room), sub_beg, Caps_off); + rp = scat(rp, tmp); + room -= (sub_end - sub_beg); + room += utf8_delta(sub_beg); + sub_beg = (sub_end += 2); + break; + default: // nothin' special, just text + ++sub_end; + } + if (0 >= room) break; // skip substrings that won't fit + } + + if (interact) PUTT("%s%s\n", row, Cap_clr_eol); + else PUFF("%s%s\n", row, Caps_endline); + glob = ++lin_end; // point to next line (maybe) + + #undef myMIN + } // end: while 'lines' + + /* If there's anything left in the glob (by virtue of no trailing '\n'), + it probably means caller wants to retain cursor position on this final + line. That, in turn, means we're interactive and so we'll just do our + 'fit-to-screen' thingy while also leaving room for the cursor... */ + if (*glob) PUTT("%.*s", utf8_embody(glob, Screen_cols - 1), glob); +} // end: show_special + +/*###### Low Level Memory/Keyboard/File I/O support ####################*/ + + /* + * Handle our own memory stuff without the risk of leaving the + * user's terminal in an ugly state should things go sour. */ + +static void *alloc_c (size_t num) { + void *pv; + + if (!num) ++num; + if (!(pv = calloc(1, num))) + error_exit(N_txt(FAIL_alloc_c_txt)); + return pv; +} // end: alloc_c + + +static void *alloc_r (void *ptr, size_t num) { + void *pv; + + if (!num) ++num; + if (!(pv = realloc(ptr, num))) + error_exit(N_txt(FAIL_alloc_r_txt)); + return pv; +} // end: alloc_r + + +static char *alloc_s (const char *str) { + return strcpy(alloc_c(strlen(str) +1), str); +} // end: alloc_s + + + /* + * An 'I/O available' routine which will detect raw single byte | + * unsolicited keyboard input which was susceptible to SIGWINCH | + * interrupt (or any other signal). He'll also support timeout | + * in the absence of any user keystrokes or a signal interrupt. | */ +static inline int ioa (struct timespec *ts) { + fd_set fs; + int rc; + + FD_ZERO(&fs); + FD_SET(STDIN_FILENO, &fs); + +#ifdef SIGNALS_LESS // conditional comments are silly, but help in documenting + // hold here until we've got keyboard input, any signal except SIGWINCH + // or (optionally) we timeout with nanosecond granularity +#else + // hold here until we've got keyboard input, any signal (including SIGWINCH) + // or (optionally) we timeout with nanosecond granularity +#endif + rc = pselect(STDIN_FILENO + 1, &fs, NULL, NULL, ts, &Sigwinch_set); + + if (rc < 0) rc = 0; + return rc; +} // end: ioa + + + /* + * This routine isolates ALL user INPUT and ensures that we + * won't be mixing I/O from stdio and low-level read() requests */ +static int ioch (int ech, char *buf, unsigned cnt) { + int rc = -1; + +#ifdef TERMIOS_ONLY + if (ech) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_tweaked); + rc = read(STDIN_FILENO, buf, cnt); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw); + } else { + if (ioa(NULL)) + rc = read(STDIN_FILENO, buf, cnt); + } +#else + (void)ech; + if (ioa(NULL)) + rc = read(STDIN_FILENO, buf, cnt); +#endif + + // zero means EOF, might happen if we erroneously get detached from terminal + if (0 == rc) bye_bye(NULL); + + // it may have been the beginning of a lengthy escape sequence + tcflush(STDIN_FILENO, TCIFLUSH); + + // note: we do NOT produce a valid 'string' + return rc; +} // end: ioch + + +#define IOKEY_INIT 0 +#define IOKEY_ONCE 1 +#define IOKEY_NEXT 2 + + /* + * Support for single or multiple keystroke input AND + * escaped cursor motion keys. + * note: we support more keys than we currently need, in case + * we attract new consumers in the future */ +static int iokey (int action) { + static struct { + const char *str; + int key; + } tinfo_tab[] = { + { NULL, kbd_BKSP }, { NULL, kbd_INS }, { NULL, kbd_DEL }, { NULL, kbd_LEFT }, + { NULL, kbd_DOWN }, { NULL, kbd_UP }, { NULL, kbd_RIGHT }, { NULL, kbd_HOME }, + { NULL, kbd_PGDN }, { NULL, kbd_PGUP }, { NULL, kbd_END }, { NULL, kbd_BTAB }, + // remainder are alternatives for above, just in case... + // ( the h,j,k,l entries are the vim cursor motion keys ) + { "\b", kbd_BKSP }, { "\177", kbd_BKSP }, /* backspace */ + { "\033h", kbd_LEFT }, { "\033j", kbd_DOWN }, /* meta+ h,j */ + { "\033k", kbd_UP }, { "\033l", kbd_RIGHT }, /* meta+ k,l */ + { "\033\010", kbd_HOME }, { "\033\012", kbd_PGDN }, /* ctrl+meta+ h,j */ + { "\033\013", kbd_PGUP }, { "\033\014", kbd_END }, /* ctrl+meta+ k,l */ + { "\xC3\xA8", kbd_LEFT }, { "\xC3\xAA", kbd_DOWN }, /* meta+ h,j (some xterms) */ + { "\xC3\xAB", kbd_UP }, { "\xC3\xAC", kbd_RIGHT }, /* meta+ k,l (some xterms) */ + { "\xC2\x88", kbd_HOME }, { "\xC2\x8A", kbd_PGDN }, /* ctrl+meta+ h,j (some xterms) */ + { "\xC2\x8B", kbd_PGUP }, { "\xC2\x8C", kbd_END }, /* ctrl+meta+ k,l (some xterms) */ + { "\033\011", kbd_BTAB } + }; + static char erase[2]; +#ifdef TERMIOS_ONLY + char buf[SMLBUFSIZ], *pb; +#else + static char buf[MEDBUFSIZ]; + static int pos, len; + char *pb; +#endif + int i; + + if (action == IOKEY_INIT) { +#ifndef _POSIX_VDISABLE +#define _POSIX_VDISABLE fpathconf(STDIN_FILENO, _PC_VDISABLE) +#endif + if (Tty_original.c_cc[VERASE] == _POSIX_VDISABLE) + tinfo_tab[0].str = key_backspace; + else { + *erase = Tty_original.c_cc[VERASE]; + tinfo_tab[0].str = erase; + } + tinfo_tab[1].str = key_ic; + tinfo_tab[2].str = key_dc; + tinfo_tab[3].str = key_left; + tinfo_tab[4].str = key_down; + tinfo_tab[5].str = key_up; + tinfo_tab[6].str = key_right; + tinfo_tab[7].str = key_home; + tinfo_tab[8].str = key_npage; + tinfo_tab[9].str = key_ppage; + tinfo_tab[10].str = key_end; + tinfo_tab[11].str = back_tab; + // next is critical so returned results match bound terminfo keys + if (keypad_xmit) + putp(keypad_xmit); + // ( converse keypad_local issued at pause/pgm end, just in case ) + return 0; + } + + if (action == IOKEY_ONCE) { + memset(buf, '\0', sizeof(buf)); + if (1 > ioch(0, buf, sizeof(buf)-1)) return 0; + } + +#ifndef TERMIOS_ONLY + if (action == IOKEY_NEXT) { + if (pos < len) + return buf[pos++]; // exhaust prior keystrokes + pos = len = 0; + memset(buf, '\0', sizeof(buf)); + if (1 > ioch(0, buf, sizeof(buf)-1)) return 0; + if (!iscntrl(buf[0])) { // no need for translation + len = strlen(buf); + pos = 1; + return buf[0]; + } + } +#endif + + /* some emulators implement 'key repeat' too well and we get duplicate + key sequences -- so we'll focus on the last escaped sequence, while + also allowing use of the meta key... */ + if (!(pb = strrchr(buf, '\033'))) pb = buf; + else if (pb > buf && '\033' == *(pb - 1)) --pb; + + for (i = 0; i < MAXTBL(tinfo_tab); i++) + if (tinfo_tab[i].str && !strcmp(tinfo_tab[i].str, pb)) + return tinfo_tab[i].key; + + // no match, so we'll return single non-escaped keystrokes only + if (buf[0] == '\033' && buf[1]) return -1; + return buf[0]; +} // end: iokey + + +#ifdef TERMIOS_ONLY + /* + * Get line oriented interactive input from the user, + * using native tty support */ +static char *ioline (const char *prompt) { + static const char ws[] = "\b\f\n\r\t\v\x1b\x9b"; // 0x1b + 0x9b are escape + static char buf[MEDBUFSIZ]; + char *p; + + show_pmt(prompt); + memset(buf, '\0', sizeof(buf)); + ioch(1, buf, sizeof(buf)-1); + + if ((p = strpbrk(buf, ws))) *p = '\0'; + // note: we DO produce a valid 'string' + return buf; +} // end: ioline + +#else + /* + * Get some line oriented interactive input from the ol' user, + * going way, way beyond that native tty support by providing: + * . true input line editing, not just a destructive backspace + * . an input limit sensitive to the current screen dimensions + * . an ability to recall prior strings for editing & re-input */ +static char *ioline (const char *prompt) { + #define setLEN ( len = strlen(buf) - utf8_delta(buf) ) + #define setPOS(X) ( pos = utf8_embody(buf, X) ) + #define utfCHR(X) ( (unsigned char *)&buf[X] ) + #define utfTOT(X) ( UTF8_tab[(unsigned char)buf[X]] ) + #define utfCOL(X) ( utf8_cols(utfCHR(X), utfTOT(X)) ) + #define movBKW { setPOS(cur - 1); while (utfTOT(pos) < 0) --pos; } + #define chkCUR { if (cur < 0) cur = 0; if (cur > len) cur = len; } + // thank goodness ol' memmove will safely allow strings to overlap + #define sqzSTR { i = utfTOT(pos); while (i < 0) i = utfTOT(--pos); \ + if (!utfCOL(pos + i)) i += utfTOT(pos + i); \ + memmove(&buf[pos], &buf[pos + i], bufMAX-(pos + i)); \ + memset(&buf[bufMAX - i], '\0', i); } + #define expSTR(X) if (bufNXT < bufMAX && scrNXT < Screen_cols) { \ + memmove(&buf[pos + X], &buf[pos], bufMAX - pos); } + #define savMAX 50 + #define bufNXT ( pos + 4 ) // four equals longest utf8 str + #define scrNXT ( beg + len + 2 ) // two due to multi-column char + #define bufMAX ((int)sizeof(buf)-2) // -1 for '\0' string delimiter + static char buf[MEDBUFSIZ+1]; // +1 for '\0' string delimiter + static int ovt; + int beg, // the physical column where input began, buf[0] + cur, // the logical current column/insertion position + len, // the logical input length, thus the end column + pos, // the physical position in the buffer currently + key, i; + struct lin_s { + struct lin_s *bkw; // pointer for older saved strs + struct lin_s *fwd; // pointer for newer saved strs + char *str; // an actual saved input string + }; + static struct lin_s *anchor, *plin; + + if (!anchor) { + anchor = alloc_c(sizeof(struct lin_s)); + anchor->str = alloc_s(""); // the top-of-stack (empty str) + } + plin = anchor; + cur = len = pos = 0; + beg = show_pmt(prompt); + memset(buf, '\0', sizeof(buf)); + // this may not work under a gui emulator (but linux console is ok) + putp(ovt ? Cap_curs_huge : Cap_curs_norm); + + do { + fflush(stdout); + key = iokey(IOKEY_NEXT); + switch (key) { + case 0: + buf[0] = '\0'; + return buf; + case kbd_ESC: + buf[0] = kbd_ESC; + return buf; + case kbd_ENTER: + case kbd_BTAB: case kbd_PGUP: case kbd_PGDN: + continue; + case kbd_INS: + ovt = !ovt; + putp(ovt ? Cap_curs_huge : Cap_curs_norm); + break; + case kbd_DEL: + sqzSTR + break; + case kbd_BKSP: + if (0 < cur) { movBKW; cur -= utfCOL(pos); setPOS(cur); sqzSTR; } + break; + case kbd_LEFT: + if (0 < cur) { movBKW; cur -= utfCOL(pos); } + break; + case kbd_RIGHT: + if (cur < len) cur += utfCOL(pos); + break; + case kbd_HOME: + cur = pos = 0; + break; + case kbd_END: + cur = len; + pos = strlen(buf); + break; + case kbd_UP: + if (plin->bkw) { + plin = plin->bkw; + memset(buf, '\0', sizeof(buf)); + memccpy(buf, plin->str, '\0', bufMAX); + cur = setLEN; + pos = strlen(buf); + } + break; + case kbd_DOWN: + if (plin->fwd) plin = plin->fwd; + memset(buf, '\0', sizeof(buf)); + memccpy(buf, plin->str, '\0', bufMAX); + cur = setLEN; + pos = strlen(buf); + break; + default: // what we REALLY wanted (maybe) + if (bufNXT < bufMAX && scrNXT < Screen_cols && strlen(buf) < bufMAX) { + int tot = UTF8_tab[(unsigned char)key], + sav = pos; + if (tot < 1) tot = 1; + if (!ovt) { expSTR(tot); } + else { pos = utf8_embody(buf, cur); sqzSTR; expSTR(tot); } + buf[pos++] = key; + while (tot > 1) { + key = iokey(IOKEY_NEXT); + buf[pos++] = key; + --tot; + } + cur += utfCOL(sav); + } + break; + } + setLEN; + chkCUR; + setPOS(cur); + putp(fmtmk("%s%s%s", tg2(beg, Msg_row), Cap_clr_eol, buf)); +#ifdef OVERTYPE_SEE + putp(fmtmk("%s%c", tg2(beg - 1, Msg_row), ovt ? '^' : ' ')); +#endif + putp(tg2(beg + cur, Msg_row)); + } while (key != kbd_ENTER); + + // weed out duplicates, including empty strings (top-of-stack)... + for (i = 0, plin = anchor; ; i++) { +#ifdef RECALL_FIXED + if (!STRCMP(plin->str, buf)) // if matched, retain original order + return buf; +#else + if (!STRCMP(plin->str, buf)) { // if matched, rearrange stack order + if (i > 1) { // but not null str or if already #2 + if (plin->bkw) // splice around this matched string + plin->bkw->fwd = plin->fwd; // if older exists link to newer + plin->fwd->bkw = plin->bkw; // newer linked to older or NULL + anchor->bkw->fwd = plin; // stick matched on top of former #2 + plin->bkw = anchor->bkw; // keep empty string at top-of-stack + plin->fwd = anchor; // then prepare to be the 2nd banana + anchor->bkw = plin; // by sliding us in below the anchor + } + return buf; + } +#endif + if (!plin->bkw) break; // let i equal total stacked strings + plin = plin->bkw; // ( with plin representing bottom ) + } + if (i < savMAX) + plin = alloc_c(sizeof(struct lin_s)); + else { // when a new string causes overflow + plin->fwd->bkw = NULL; // make next-to-last string new last + free(plin->str); // and toss copy but keep the struct + } + plin->str = alloc_s(buf); // copy user's new unique input line + plin->bkw = anchor->bkw; // keep empty string as top-of-stack + if (plin->bkw) // did we have some already stacked? + plin->bkw->fwd = plin; // yep, so point prior to new string + plin->fwd = anchor; // and prepare to be a second banana + anchor->bkw = plin; // by sliding it in as new number 2! + + return buf; // protect our copy, return original + #undef setLEN + #undef setPOS + #undef utfCHR + #undef utfTOT + #undef utfCOL + #undef movBKW + #undef chkCUR + #undef sqzSTR + #undef expSTR + #undef savMAX + #undef bufNXT + #undef scrNXT + #undef bufMAX +} // end: ioline +#endif + + + /* + * Make locale unaware float (but maybe restrict to whole numbers). */ +static int mkfloat (const char *str, float *num, int whole) { + char tmp[SMLBUFSIZ], *ep; + + if (whole) { + *num = (float)strtol(str, &ep, 0); + if (ep != str && *ep == '\0' && *num < INT_MAX) + return 1; + return 0; + } + snprintf(tmp, sizeof(tmp), "%s", str); + *num = strtof(tmp, &ep); + if (*ep != '\0') { + // fallback - try to swap the floating point separator + if (*ep == '.') *ep = ','; + else if (*ep == ',') *ep = '.'; + *num = strtof(tmp, &ep); + } + if (ep != tmp && *ep == '\0' && *num < INT_MAX) + return 1; + return 0; +} // end: mkfloat + + + /* + * This routine provides the i/o in support of files whose size + * cannot be determined in advance. Given a stream pointer, he'll + * try to slurp in the whole thing and return a dynamically acquired + * buffer supporting that single string glob. + * + * He always creates a buffer at least READMINSZ big, possibly + * all zeros (an empty string), even if the file wasn't read. */ +static int readfile (FILE *fp, char **baddr, size_t *bsize, size_t *bread) { + char chunk[4096*16]; + size_t num; + + *bread = 0; + *bsize = READMINSZ; + *baddr = alloc_c(READMINSZ); + if (fp) { + while (0 < (num = fread(chunk, 1, sizeof(chunk), fp))) { + *baddr = alloc_r(*baddr, num + *bsize); + memcpy(*baddr + *bread, chunk, num); + *bread += num; + *bsize += num; + }; + *(*baddr + *bread) = '\0'; + return ferror(fp); + } + return ENOENT; +} // end: readfile + +/*###### Small Utility routines ########################################*/ + +#define GET_NUM_BAD INT_MIN +#define GET_NUM_ESC (INT_MIN + 1) +#define GET_NUM_NOT (INT_MIN + 2) + + /* + * Get a float from the user */ +static float get_float (const char *prompt) { + char *line; + float f; + + line = ioline(prompt); + if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC; + if (!line[0]) return GET_NUM_NOT; + // note: we're not allowing negative floats + if (!mkfloat(line, &f, 0) || f < 0) { + show_msg(N_txt(BAD_numfloat_txt)); + return GET_NUM_BAD; + } + return f; +} // end: get_float + + + /* + * Get an integer from the user, returning INT_MIN for error */ +static int get_int (const char *prompt) { + char *line; + float f; + + line = ioline(prompt); + if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC; + if (!line[0]) return GET_NUM_NOT; + // note: we've got to allow negative ints (renice) + if (!mkfloat(line, &f, 1)) { + show_msg(N_txt(BAD_integers_txt)); + return GET_NUM_BAD; + } + return (int)f; +} // end: get_int + + + /* + * Make a hex value, and maybe suppress zeroes. */ +static inline const char *hex_make (long num, int noz) { + static char buf[SMLBUFSIZ]; + int i; + +#ifdef CASEUP_HEXES + snprintf(buf, sizeof(buf), "%08lX", num); +#else + snprintf(buf, sizeof(buf), "%08lx", num); +#endif + if (noz) + for (i = 0; buf[i]; i++) + if ('0' == buf[i]) + buf[i] = '.'; + return buf; +} // end: hex_make + + + /* + * Validate the passed string as a user name or number, + * and/or update the window's 'u/U' selection stuff. */ +static const char *user_certify (WIN_t *q, const char *str, char typ) { + struct passwd *pwd; + char *endp; + uid_t num; + + Monpidsidx = 0; + q->usrseltyp = 0; + q->usrselflg = 1; + if (*str) { + if ('!' == *str) { ++str; q->usrselflg = 0; } + num = (uid_t)strtoul(str, &endp, 0); + if ('\0' == *endp) { + pwd = getpwuid(num); + if (!pwd) { + /* allow foreign users, from e.g within chroot + ( thanks Dr. Werner Fink <werner@suse.de> ) */ + q->usrseluid = num; + q->usrseltyp = typ; + return NULL; + } + } else + pwd = getpwnam(str); + if (!pwd) return N_txt(BAD_username_txt); + q->usrseluid = pwd->pw_uid; + q->usrseltyp = typ; + } + return NULL; +} // end: user_certify + +/*###### Basic Formatting support ######################################*/ + + /* + * Just do some justify stuff, then add post column padding. */ +static inline const char *justify_pad (const char *str, int width, int justr) { + static char l_fmt[] = "%-*.*s%s", r_fmt[] = "%*.*s%s"; + static char buf[SCREENMAX]; + + snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, str, COLPADSTR); + return buf; +} // end: justify_pad + + + /* + * Make and then justify a single character. */ +static inline const char *make_chr (const char ch, int width, int justr) { + static char buf[SMLBUFSIZ]; + + snprintf(buf, sizeof(buf), "%c", ch); + return justify_pad(buf, width, justr); +} // end: make_chr + + + /* + * Make and then justify an integer NOT subject to scaling, + * and include a visual clue should tuncation be necessary. */ +static inline const char *make_num (long num, int width, int justr, int col, int noz) { + static char buf[SMLBUFSIZ]; + + buf[0] = '\0'; + if (noz && Rc.zero_suppress && 0 == num) + goto end_justifies; + + if (width < snprintf(buf, sizeof(buf), "%ld", num)) { + if (width <= 0 || (size_t)width >= sizeof(buf)) + width = sizeof(buf)-1; + buf[width-1] = COLPLUSCH; + buf[width] = '\0'; + AUTOX_COL(col); + } +end_justifies: + return justify_pad(buf, width, justr); +} // end: make_num + + + /* + * Make and then justify a character string, + * and include a visual clue should tuncation be necessary. */ +static inline const char *make_str (const char *str, int width, int justr, int col) { + static char buf[SCREENMAX]; + + if (width < snprintf(buf, sizeof(buf), "%s", str)) { + if (width <= 0 || (size_t)width >= sizeof(buf)) + width = sizeof(buf)-1; + buf[width-1] = COLPLUSCH; + buf[width] = '\0'; + AUTOX_COL(col); + } + return justify_pad(buf, width, justr); +} // end: make_str + + + /* + * Make and then justify a potentially multi-byte character string, + * and include a visual clue should tuncation be necessary. */ +static inline const char *make_str_utf8 (const char *str, int width, int justr, int col) { + static char buf[SCREENMAX]; + int delta = utf8_delta(str); + + if (width + delta < snprintf(buf, sizeof(buf), "%s", str)) { + snprintf(buf, sizeof(buf), "%.*s%c", utf8_embody(str, width-1), str, COLPLUSCH); + delta = utf8_delta(buf); + AUTOX_COL(col); + } + return justify_pad(buf, width + delta, justr); +} // end: make_str_utf8 + + + /* + * Do some scaling then justify stuff. + * We'll interpret 'num' as a kibibytes quantity and try to + * format it to reach 'target' while also fitting 'width'. */ +static const char *scale_mem (int target, float num, int width, int justr) { + // SK_Kb SK_Mb SK_Gb SK_Tb SK_Pb SK_Eb +#ifdef BOOST_MEMORY + static const char *fmttab[] = { "%.0f", "%#.1f%c", "%#.3f%c", "%#.3f%c", "%#.3f%c", NULL }; +#else + static const char *fmttab[] = { "%.0f", "%.1f%c", "%.1f%c", "%.1f%c", "%.1f%c", NULL }; +#endif + static char buf[SMLBUFSIZ]; + char *psfx; + int i; + + buf[0] = '\0'; + if (Rc.zero_suppress && 0 >= num) + goto end_justifies; + + for (i = SK_Kb, psfx = Scaled_sfxtab; i < SK_Eb; psfx++, i++) { + if (i >= target + && (width >= snprintf(buf, sizeof(buf), fmttab[i], num, *psfx))) + goto end_justifies; + num /= 1024.0; + } + + // well shoot, this outta' fit... + snprintf(buf, sizeof(buf), "?"); +end_justifies: + return justify_pad(buf, width, justr); +} // end: scale_mem + + + /* + * Do some scaling then justify stuff. */ +static const char *scale_num (float num, int width, int justr) { + static char buf[SMLBUFSIZ]; + char *psfx; + + buf[0] = '\0'; + if (Rc.zero_suppress && 0 >= num) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%.0f", num)) + goto end_justifies; + + for (psfx = Scaled_sfxtab; 0 < *psfx; psfx++) { + num /= 1024.0; + if (width >= snprintf(buf, sizeof(buf), "%.1f%c", num, *psfx)) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%.0f%c", num, *psfx)) + goto end_justifies; + } + + // well shoot, this outta' fit... + snprintf(buf, sizeof(buf), "?"); +end_justifies: + return justify_pad(buf, width, justr); +} // end: scale_num + + + /* + * Make and then justify a percentage, with decreasing precision. */ +static const char *scale_pcnt (float num, int width, int justr, int xtra) { + static char buf[SMLBUFSIZ]; + + buf[0] = '\0'; + if (Rc.zero_suppress && 0 >= num) + goto end_justifies; + if (xtra) { + if (width >= snprintf(buf, sizeof(buf), "%#.3f", num)) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%#.2f", num)) + goto end_justifies; + goto carry_on; + } +#ifdef BOOST_PERCNT + if (width >= snprintf(buf, sizeof(buf), "%#.3f", num)) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%#.2f", num)) + goto end_justifies; + (void)xtra; +#endif +carry_on: + if (width >= snprintf(buf, sizeof(buf), "%#.1f", num)) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%*.0f", width, num)) + goto end_justifies; + + // well shoot, this outta' fit... + snprintf(buf, sizeof(buf), "?"); +end_justifies: + return justify_pad(buf, width, justr); +} // end: scale_pcnt + + +#define TICS_AS_SECS 0 +#define TICS_AS_MINS 1 +#define TICS_AS_HOUR 2 +#define TICS_AS_DAY1 3 +#define TICS_AS_DAY2 4 +#define TICS_AS_WEEK 5 +#define TICS_AS_LAST 6 + + /* + * Do some scaling stuff. + * Try to format 'tics' to reach 'target' while also + * fitting in 'width', then justify it. */ +static const char *scale_tics (TIC_t tics, int width, int justr, int target) { +#ifdef CASEUP_SUFIX + #define HH "%luH" // nls_maybe + #define DD "%luD" + #define WW "%luW" +#else + #define HH "%luh" // nls_maybe + #define DD "%lud" + #define WW "%luw" +#endif + static char buf[SMLBUFSIZ]; + TIC_t nt; // for speed on 64-bit +#ifdef SCALE_FORMER + unsigned long cc; // centiseconds + unsigned long nn; // multi-purpose whatever +#else + unsigned long cent, secs, mins, hour, days, week; +#endif + + buf[0] = '\0'; + nt = (tics * 100ull) / Hertz; // lots of room for any time + if (Rc.zero_suppress && 0 >= nt) + goto end_justifies; + +#ifdef SCALE_FORMER + cc = nt % 100; // centiseconds past second + nt /= 100; // total seconds + nn = nt % 60; // seconds past the minute + nt /= 60; // total minutes + if (target < TICS_AS_MINS + && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu.%02lu", nt, nn, cc))) + goto end_justifies; + if (target < TICS_AS_HOUR + && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu", nt, nn))) + goto end_justifies; + nn = nt % 60; // minutes past the hour + nt /= 60; // total hours + if (width >= snprintf(buf, sizeof(buf), "%llu,%02lu", nt, nn)) + goto end_justifies; + nn = nt; // now also hours + if (width >= snprintf(buf, sizeof(buf), HH, nn)) + goto end_justifies; + nn /= 24; // now days + if (width >= snprintf(buf, sizeof(buf), DD, nn)) + goto end_justifies; + nn /= 7; // now weeks + if (width >= snprintf(buf, sizeof(buf), WW, nn)) + goto end_justifies; +#else + #define mmLIMIT 360 // arbitrary 6 hours + #define hhLIMIT 96 // arbitrary 4 days + #define ddLIMIT 14 // arbitrary 2 weeks + + cent = (nt % 100); // cent past secs + secs = (nt /= 100); // total secs + mins = (nt /= 60); // total mins + hour = (nt /= 60); // total hour + days = (nt /= 24); // total days + week = (nt / 7); // total week + + if (Rc.tics_scaled > target) + target += Rc.tics_scaled - target; + + switch (target) { + case TICS_AS_SECS: + if (mins < mmLIMIT + 1) { + if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu.%02lu", mins, secs % 60, cent)) + goto end_justifies; + } + case TICS_AS_MINS: // fall through + if (mins < mmLIMIT + 1) { + if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu", mins, secs % 60)) + goto end_justifies; + } + case TICS_AS_HOUR: // fall through + if (hour < hhLIMIT + 1) { + if (width >= snprintf(buf, sizeof(buf), "%lu,%02lu", hour, mins % 60)) + goto end_justifies; + } + case TICS_AS_DAY1: // fall through + if (days < ddLIMIT + 1) { + if (width >= snprintf(buf, sizeof(buf), DD "+" HH, days, hour % 24)) + goto end_justifies; +#ifdef SCALE_POSTFX + if (width >= snprintf(buf, sizeof(buf), DD "+%lu", days, hour % 24)) + goto end_justifies; +#endif + case TICS_AS_DAY2: // fall through + if (width >= snprintf(buf, sizeof(buf), DD, days)) + goto end_justifies; + } + case TICS_AS_WEEK: // fall through + if (width >= snprintf(buf, sizeof(buf), WW "+" DD, week, days % 7)) + goto end_justifies; +#ifdef SCALE_POSTFX + if (width >= snprintf(buf, sizeof(buf), WW "+%lu", week, days % 7)) + goto end_justifies; +#endif + case TICS_AS_LAST: // fall through + if (width >= snprintf(buf, sizeof(buf), WW, week)) + goto end_justifies; + default: // fall through + break; + } + #undef mmLIMIT + #undef hhLIMIT + #undef ddLIMIT +#endif + + // well shoot, this outta' fit... + snprintf(buf, sizeof(buf), "?"); + +end_justifies: + return justify_pad(buf, width, justr); + #undef HH + #undef DD + #undef WW +} // end: scale_tics + +/*###### Fields Management support #####################################*/ + + /* These are our gosh darn 'Fields' ! + They MUST be kept in sync with pflags !! */ +static struct { + int width; // field width, if applicable + int scale; // scaled target, if applicable + const int align; // the default column alignment flag + enum pids_item item; // the new libproc item enum identifier +} Fieldstab[] = { + // these identifiers reflect the default column alignment but they really + // contain the WIN_t flag used to check/change justification at run-time! + #define A_left Show_JRSTRS /* toggled with lower case 'j' */ + #define A_right Show_JRNUMS /* toggled with upper case 'J' */ + +/* .width anomalies: + a -1 width represents variable width columns + a 0 width represents columns set once at startup (see zap_fieldstab) + + .width .scale .align .item + ------ ------ -------- ------------------- */ + { 0, -1, A_right, PIDS_ID_PID }, // s_int EU_PID + { 0, -1, A_right, PIDS_ID_PPID }, // s_int EU_PPD + { 5, -1, A_right, PIDS_ID_EUID }, // u_int EU_UED + { 8, -1, A_left, PIDS_ID_EUSER }, // str EU_UEN + { 5, -1, A_right, PIDS_ID_RUID }, // u_int EU_URD + { 8, -1, A_left, PIDS_ID_RUSER }, // str EU_URN + { 5, -1, A_right, PIDS_ID_SUID }, // u_int EU_USD + { 8, -1, A_left, PIDS_ID_SUSER }, // str EU_USN + { 5, -1, A_right, PIDS_ID_EGID }, // u_int EU_GID + { 8, -1, A_left, PIDS_ID_EGROUP }, // str EU_GRP + { 0, -1, A_right, PIDS_ID_PGRP }, // s_int EU_PGD + { 8, -1, A_left, PIDS_TTY_NAME }, // str EU_TTY + { 0, -1, A_right, PIDS_ID_TPGID }, // s_int EU_TPG + { 0, -1, A_right, PIDS_ID_SESSION }, // s_int EU_SID + { 3, -1, A_right, PIDS_PRIORITY }, // s_int EU_PRI + { 3, -1, A_right, PIDS_NICE }, // s_int EU_NCE + { 3, -1, A_right, PIDS_NLWP }, // s_int EU_THD + { 0, -1, A_right, PIDS_PROCESSOR }, // s_int EU_CPN + { 5, -1, A_right, PIDS_TICS_ALL_DELTA }, // u_int EU_CPU + { 6, -1, A_right, PIDS_TICS_ALL }, // ull_int EU_TME + { 9, -1, A_right, PIDS_TICS_ALL }, // ull_int EU_TM2 + { 5, -1, A_right, PIDS_MEM_RES }, // ul_int EU_MEM + { 7, SK_Kb, A_right, PIDS_MEM_VIRT }, // ul_int EU_VRT + { 6, SK_Kb, A_right, PIDS_VM_SWAP }, // ul_int EU_SWP + { 6, SK_Kb, A_right, PIDS_MEM_RES }, // ul_int EU_RES + { 6, SK_Kb, A_right, PIDS_MEM_CODE }, // ul_int EU_COD + { 7, SK_Kb, A_right, PIDS_MEM_DATA }, // ul_int EU_DAT + { 6, SK_Kb, A_right, PIDS_MEM_SHR }, // ul_int EU_SHR + { 4, -1, A_right, PIDS_FLT_MAJ }, // ul_int EU_FL1 + { 4, -1, A_right, PIDS_FLT_MIN }, // ul_int EU_FL2 + { 4, -1, A_right, PIDS_noop }, // ul_int EU_DRT ( always 0 w/ since 2.6 ) + { 1, -1, A_right, PIDS_STATE }, // s_ch EU_STA + { -1, -1, A_left, PIDS_CMD }, // str EU_CMD + { 10, -1, A_left, PIDS_WCHAN_NAME }, // str EU_WCH + { 8, -1, A_left, PIDS_FLAGS }, // ul_int EU_FLG + { -1, -1, A_left, PIDS_CGROUP }, // str EU_CGR + { -1, -1, A_left, PIDS_SUPGIDS }, // str EU_SGD + { -1, -1, A_left, PIDS_SUPGROUPS }, // str EU_SGN + { 0, -1, A_right, PIDS_ID_TGID }, // s_int EU_TGD + { 5, -1, A_right, PIDS_OOM_ADJ }, // s_int EU_OOA + { 4, -1, A_right, PIDS_OOM_SCORE }, // s_int EU_OOM + { -1, -1, A_left, PIDS_ENVIRON }, // str EU_ENV + { 3, -1, A_right, PIDS_FLT_MAJ_DELTA }, // s_int EU_FV1 + { 3, -1, A_right, PIDS_FLT_MIN_DELTA }, // s_int EU_FV2 + { 6, SK_Kb, A_right, PIDS_VM_USED }, // ul_int EU_USE + { 10, -1, A_right, PIDS_NS_IPC }, // ul_int EU_NS1 + { 10, -1, A_right, PIDS_NS_MNT }, // ul_int EU_NS2 + { 10, -1, A_right, PIDS_NS_NET }, // ul_int EU_NS3 + { 10, -1, A_right, PIDS_NS_PID }, // ul_int EU_NS4 + { 10, -1, A_right, PIDS_NS_USER }, // ul_int EU_NS5 + { 10, -1, A_right, PIDS_NS_UTS }, // ul_int EU_NS6 + { 8, -1, A_left, PIDS_LXCNAME }, // str EU_LXC + { 6, SK_Kb, A_right, PIDS_VM_RSS_ANON }, // ul_int EU_RZA + { 6, SK_Kb, A_right, PIDS_VM_RSS_FILE }, // ul_int EU_RZF + { 6, SK_Kb, A_right, PIDS_VM_RSS_LOCKED }, // ul_int EU_RZL + { 6, SK_Kb, A_right, PIDS_VM_RSS_SHARED }, // ul_int EU_RZS + { -1, -1, A_left, PIDS_CGNAME }, // str EU_CGN + { 0, -1, A_right, PIDS_PROCESSOR_NODE }, // s_int EU_NMA + { 5, -1, A_right, PIDS_ID_LOGIN }, // s_int EU_LID + { -1, -1, A_left, PIDS_EXE }, // str EU_EXE + { 6, SK_Kb, A_right, PIDS_SMAP_RSS }, // ul_int EU_RSS + { 6, SK_Kb, A_right, PIDS_SMAP_PSS }, // ul_int EU_PSS + { 6, SK_Kb, A_right, PIDS_SMAP_PSS_ANON }, // ul_int EU_PZA + { 6, SK_Kb, A_right, PIDS_SMAP_PSS_FILE }, // ul_int EU_PZF + { 6, SK_Kb, A_right, PIDS_SMAP_PSS_SHMEM }, // ul_int EU_PZS + { 6, SK_Kb, A_right, PIDS_SMAP_PRV_TOTAL }, // ul_int EU_USS + { 6, -1, A_right, PIDS_IO_READ_BYTES }, // ul_int EU_IRB + { 5, -1, A_right, PIDS_IO_READ_OPS }, // ul_int EU_IRO + { 6, -1, A_right, PIDS_IO_WRITE_BYTES }, // ul_int EU_IWB + { 5, -1, A_right, PIDS_IO_WRITE_OPS }, // ul_int EU_IWO + { 5, -1, A_right, PIDS_AUTOGRP_ID }, // s_int EU_AGI + { 4, -1, A_right, PIDS_AUTOGRP_NICE }, // s_int EU_AGN + { 7, -1, A_right, PIDS_TICS_BEGAN }, // ull_int EU_TM3 + { 7, -1, A_right, PIDS_TIME_ELAPSED }, // real EU_TM4 + { 6, -1, A_right, PIDS_UTILIZATION }, // real EU_CUU + { 7, -1, A_right, PIDS_UTILIZATION_C }, // real EU_CUC + { 10, -1, A_right, PIDS_NS_CGROUP }, // ul_int EU_NS7 + { 10, -1, A_right, PIDS_NS_TIME } // ul_int EU_NS8 +#define eu_LAST EU_NS8 +// xtra Fieldstab 'pseudo pflag' entries for the newlib interface . . . . . . . +#define eu_CMDLINE eu_LAST +1 +#define eu_TICS_ALL_C eu_LAST +2 +#define eu_ID_FUID eu_LAST +3 +#define eu_CMDLINE_V eu_LAST +4 +#define eu_ENVIRON_V eu_LAST +5 +#define eu_TREE_HID eu_LAST +6 +#define eu_TREE_LVL eu_LAST +7 +#define eu_TREE_ADD eu_LAST +8 +#define eu_RESET eu_TREE_HID // demarcation for reset to zero (PIDS_extra) + , { -1, -1, -1, PIDS_CMDLINE } // str ( if Show_CMDLIN, eu_CMDLINE ) + , { -1, -1, -1, PIDS_TICS_ALL_C } // ull_int ( if Show_CTIMES, eu_TICS_ALL_C ) + , { -1, -1, -1, PIDS_ID_FUID } // u_int ( if a usrseltyp, eu_ID_FUID ) + , { -1, -1, -1, PIDS_CMDLINE_V } // strv ( if Ctrlk, eu_CMDLINE_V ) + , { -1, -1, -1, PIDS_ENVIRON_V } // strv ( if CtrlN, eu_ENVIRON_V ) + , { -1, -1, -1, PIDS_extra } // s_ch ( if Show_FOREST, eu_TREE_HID ) + , { -1, -1, -1, PIDS_extra } // s_int ( if Show_FOREST, eu_TREE_LVL ) + , { -1, -1, -1, PIDS_extra } // s_int ( if Show_FOREST, eu_TREE_ADD ) + #undef A_left + #undef A_right +}; + + + /* + * A calibrate_fields() *Helper* function which refreshes + * all that cached screen geometry plus related variables */ +static void adj_geometry (void) { + static size_t pseudo_max = 0; + static int w_set = 0, w_cols = 0, w_rows = 0; + struct winsize wz; + + Screen_cols = columns; // <term.h> + Screen_rows = lines; // <term.h> + + if (-1 != ioctl(STDOUT_FILENO, TIOCGWINSZ, &wz) + && 0 < wz.ws_col && 0 < wz.ws_row) { + Screen_cols = wz.ws_col; + Screen_rows = wz.ws_row; + } + +#ifndef RMAN_IGNORED + // be crudely tolerant of crude tty emulators + if (Cap_avoid_eol) Screen_cols--; +#endif + + // we might disappoint some folks (but they'll deserve it) + if (Screen_cols > SCREENMAX) Screen_cols = SCREENMAX; + if (Screen_cols < W_MIN_COL) Screen_cols = W_MIN_COL; + + if (!w_set) { + if (Width_mode > 0) // -w with arg, we'll try to honor + w_cols = Width_mode; + else + if (Width_mode < 0) { // -w without arg, try environment + char *env_columns = getenv("COLUMNS"), + *env_lines = getenv("LINES"), + *ep; + if (env_columns && *env_columns) { + long t, tc = 0; + t = strtol(env_columns, &ep, 0); + if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tc = t; + if (0 < tc) w_cols = (int)tc; + } + if (env_lines && *env_lines) { + long t, tr = 0; + t = strtol(env_lines, &ep, 0); + if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tr = t; + if (0 < tr) w_rows = (int)tr; + } + if (!w_cols) w_cols = SCREENMAX; + if (w_cols && w_cols < W_MIN_COL) w_cols = W_MIN_COL; + if (w_rows && w_rows < W_MIN_ROW) w_rows = W_MIN_ROW; + } + if (w_cols > SCREENMAX) w_cols = SCREENMAX; + w_set = 1; + } + + /* keep our support for output optimization in sync with current reality + note: when we're in Batch mode, we don't really need a Pseudo_screen + and when not Batch, our buffer will contain 1 extra 'line' since + Msg_row is never represented -- but it's nice to have some space + between us and the great-beyond... */ + if (Batch) { + if (w_cols) Screen_cols = w_cols; + Screen_rows = w_rows ? w_rows : INT_MAX; + Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ); + } else { + const int max_rows = INT_MAX / (sizeof(*Pseudo_screen) * ROWMAXSIZ); + if (w_cols && w_cols < Screen_cols) Screen_cols = w_cols; + if (w_rows && w_rows < Screen_rows) Screen_rows = w_rows; + if (Screen_rows < 0 || Screen_rows > max_rows) Screen_rows = max_rows; + Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ) * Screen_rows; + } + // we'll only grow our Pseudo_screen, never shrink it + if (pseudo_max < Pseudo_size) { + pseudo_max = Pseudo_size; + Pseudo_screen = alloc_r(Pseudo_screen, pseudo_max); + } + // ensure each row is repainted (just in case) + PSU_CLREOS(0); + + // prepare to customize potential cpu/memory graphs + if (Curwin->rc.double_up) { + int num = (Curwin->rc.double_up + 1); + int pfx = (Curwin->rc.double_up < 2) ? GRAPH_prefix_std : GRAPH_prefix_abv; + + Graph_cpus->length = (Screen_cols - (ADJOIN_space * Curwin->rc.double_up) - (num * (pfx + GRAPH_suffix))) / num; + if (Graph_cpus->length > GRAPH_length_max) Graph_cpus->length = GRAPH_length_max; + if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min; + +#ifdef TOG4_MEM_1UP + Graph_mems->length = (Screen_cols - (GRAPH_prefix_std + GRAPH_suffix)); +#else + Graph_mems->length = (Screen_cols - ADJOIN_space - (2 * (pfx + GRAPH_suffix))) / 2; +#endif + if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max; + if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min; + +#if !defined(TOG4_MEM_FIX) && !defined(TOG4_MEM_1UP) + if (num > 2) { + #define cpuGRAPH ( GRAPH_prefix_abv + Graph_cpus->length + GRAPH_suffix ) + #define nxtGRAPH ( cpuGRAPH + ADJOIN_space ) + int len = cpuGRAPH; + for (;;) { + if (len + nxtGRAPH > GRAPH_length_max) break; + len += nxtGRAPH; + } + len -= (GRAPH_prefix_abv + ADJOIN_space); + Graph_mems->length = len; + #undef cpuGRAPH + #undef nxtGRAPH + } +#endif + } else { + Graph_cpus->length = Screen_cols - (GRAPH_prefix_std + GRAPH_length_max + GRAPH_suffix); + if (Graph_cpus->length >= 0) Graph_cpus->length = GRAPH_length_max; + else Graph_cpus->length = Screen_cols - GRAPH_prefix_std - GRAPH_suffix; + if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min; +#ifdef TOG4_MEM_1UP + Graph_mems->length = (Screen_cols - (GRAPH_prefix_std + GRAPH_suffix)); + if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max; + if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min; +#else + Graph_mems->length = Graph_cpus->length; +#endif + } + Graph_cpus->adjust = (float)Graph_cpus->length / 100.0; + Graph_cpus->style = Curwin->rc.graph_cpus; + + Graph_mems->adjust = (float)Graph_mems->length / 100.0; + Graph_mems->style = Curwin->rc.graph_mems; + + fflush(stdout); + Frames_signal = BREAK_off; +} // end: adj_geometry + + + /* + * A calibrate_fields() *Helper* function to build the actual + * column headers & ensure necessary item enumerators support */ +static void build_headers (void) { + #define ckITEM(f) do { Pids_itms[f] = Fieldstab[f].item; } while (0) + #define ckCMDS(w) do { if (CHKw(w, Show_CMDLIN)) ckITEM(eu_CMDLINE); } while (0) + FLG_t f; + char *s; + WIN_t *w = Curwin; +#ifdef EQUCOLHDRYES + int x, hdrmax = 0; +#endif + int i; + + // ensure fields not visible incur no significant library costs + for (i = 0; i < eu_RESET; i++) + Pids_itms[i] = PIDS_noop; + for ( ; i < MAXTBL(Fieldstab); i++) + Pids_itms[i] = PIDS_extra; + + ckITEM(EU_PID); // these 2 fields may not display, + ckITEM(EU_STA); // yet we'll always need them both + ckITEM(EU_CMD); // this is used with 'Y' (inspect) + + do { + if (VIZISw(w)) { + memset((s = w->columnhdr), 0, sizeof(w->columnhdr)); + if (Rc.mode_altscr) s = scat(s, fmtmk("%d", w->winnum)); + + for (i = 0; i < w->maxpflgs; i++) { + f = w->procflgs[i]; +#ifdef USE_X_COLHDR + if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) { + s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_msg)); + w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_msg); + } +#else + if (EU_MAXPFLGS <= f) continue; +#endif + ckITEM(f); + switch (f) { + case EU_CMD: + ckCMDS(w); + break; + case EU_CPU: + // cpu calculations depend on number of threads + ckITEM(EU_THD); + break; + case EU_TME: + case EU_TM2: + // for 'cumulative' times, we'll need equivalent of cutime & cstime + if (CHKw(w, Show_CTIMES)) ckITEM(eu_TICS_ALL_C); + break; + default: + break; + } + s = scat(s, utf8_justify(N_col(f) + , VARcol(f) ? w->varcolsz : Fieldstab[f].width + , CHKw(w, Fieldstab[f].align))); +#ifdef USE_X_COLHDR + if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) { + s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_hdr)); + w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_hdr); + } +#endif + } +#ifdef EQUCOLHDRYES + // prepare to even out column header lengths... + if (hdrmax + w->hdrcaplen < (x = strlen(w->columnhdr))) hdrmax = x - w->hdrcaplen; +#endif + // for 'busy' only processes, we'll need elapsed tics + if (!CHKw(w, Show_IDLEPS)) ckITEM(EU_CPU); + // with forest view mode, we'll need pid, tgid, ppid & start_time... +#ifndef TREE_VCPUOFF + if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); ckITEM(eu_TREE_ADD); } +#else + if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); } +#endif + // for 'u/U' filtering we need these too (old top forgot that, oops) + if (w->usrseltyp) { ckITEM(EU_UED); ckITEM(EU_URD); ckITEM(EU_USD); ckITEM(eu_ID_FUID); } + + // we must also accommodate an out of view sort field... + f = w->rc.sortindx; + if (EU_CMD == f) ckCMDS(w); + else ckITEM(f); + + // lastly, accommodate any special non-display 'tagged' needs... + i = 0; + while (Bot_item[i] > BOT_DELIMIT) { + ckITEM(Bot_item[i]); + ++i; + } + } // end: VIZISw(w) + + if (Rc.mode_altscr) w = w->next; + } while (w != Curwin); + +#ifdef EQUCOLHDRYES + /* now we can finally even out column header lengths + (we're assuming entire columnhdr was memset to '\0') */ + if (Rc.mode_altscr && SCREENMAX > Screen_cols) + for (i = 0; i < GROUPSMAX; i++) { + w = &Winstk[i]; + if (CHKw(w, Show_TASKON)) + if (hdrmax + w->hdrcaplen > (x = strlen(w->columnhdr))) + memset(&w->columnhdr[x], ' ', hdrmax + w->hdrcaplen - x); + } +#endif + + #undef ckITEM + #undef ckCMDS +} // end: build_headers + + + /* + * This guy coordinates the activities surrounding the maintenance of + * each visible window's columns headers plus item enumerators needed */ +static void calibrate_fields (void) { + FLG_t f; + char *s; + const char *h; + WIN_t *w = Curwin; + int i, varcolcnt, len, rc; + + adj_geometry(); + + do { + if (VIZISw(w)) { + w->hdrcaplen = 0; // really only used with USE_X_COLHDR + // build window's pflgsall array, establish upper bounds for maxpflgs + for (i = 0, w->totpflgs = 0; i < EU_MAXPFLGS; i++) { + if (FLDviz(w, i)) { + f = FLDget(w, i); +#ifdef USE_X_COLHDR + w->pflgsall[w->totpflgs++] = f; +#else + if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) { + w->pflgsall[w->totpflgs++] = EU_XON; + w->pflgsall[w->totpflgs++] = f; + w->pflgsall[w->totpflgs++] = EU_XOF; + } else + w->pflgsall[w->totpflgs++] = f; +#endif + } + } + if (!w->totpflgs) w->pflgsall[w->totpflgs++] = EU_PID; + + /* build a preliminary columns header not to exceed screen width + while accounting for a possible leading window number */ + w->varcolsz = varcolcnt = 0; + *(s = w->columnhdr) = '\0'; + if (Rc.mode_altscr) s = scat(s, " "); + for (i = 0; i + w->begpflg < w->totpflgs; i++) { + f = w->pflgsall[i + w->begpflg]; + w->procflgs[i] = f; +#ifndef USE_X_COLHDR + if (EU_MAXPFLGS <= f) continue; +#endif + h = N_col(f); + len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ; + // oops, won't fit -- we're outta here... + if (Screen_cols < ((int)(s - w->columnhdr) + len)) break; + if (VARcol(f)) { ++varcolcnt; w->varcolsz += strlen(h); } + s = scat(s, fmtmk("%*.*s", len, len, h)); + } +#ifndef USE_X_COLHDR + if (i >= 1 && EU_XON == w->procflgs[i - 1]) --i; +#endif + + /* establish the final maxpflgs and prepare to grow the variable column + heading(s) via varcolsz - it may be a fib if their pflags weren't + encountered, but that's ok because they won't be displayed anyway */ + w->maxpflgs = i; + w->varcolsz += Screen_cols - strlen(w->columnhdr); + if (varcolcnt) w->varcolsz /= varcolcnt; + + /* establish the field where all remaining fields would still + fit within screen width, including a leading window number */ + *(s = w->columnhdr) = '\0'; + if (Rc.mode_altscr) s = scat(s, " "); + w->endpflg = 0; + for (i = w->totpflgs - 1; -1 < i; i--) { + f = w->pflgsall[i]; +#ifndef USE_X_COLHDR + if (EU_MAXPFLGS <= f) { w->endpflg = i; continue; } +#endif + h = N_col(f); + len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ; + if (Screen_cols < ((int)(s - w->columnhdr) + len)) break; + s = scat(s, fmtmk("%*.*s", len, len, h)); + w->endpflg = i; + } +#ifndef USE_X_COLHDR + if (EU_XOF == w->pflgsall[w->endpflg]) ++w->endpflg; +#endif + } // end: if (VIZISw(w)) + + if (Rc.mode_altscr) w = w->next; + } while (w != Curwin); + + build_headers(); + + if ((rc = procps_pids_reset(Pids_ctx, Pids_itms, Pids_itms_tot))) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc))); +} // end: calibrate_fields + + + /* + * Display each field represented in the current window's fieldscur + * array along with its description. Mark with bold and a leading + * asterisk those fields associated with the "on" or "active" state. + * + * Special highlighting will be accorded the "focus" field with such + * highlighting potentially extended to include the description. + * + * Below is the current Fieldstab space requirement and how + * we apportion it. The xSUFX is considered sacrificial, + * something we can reduce or do without. + * 0 1 2 3 + * 12345678901234567890123456789012 + * * HEADING = Longest Description! + * xPRFX ----------______________________ xSUFX + * ( xPRFX has pos 2 & 10 for 'extending' when at minimums ) + * + * The first 4 screen rows are reserved for explanatory text, and + * the maximum number of columns is Screen_cols / xPRFX + 1 space + * between columns. Thus, for example, with 42 fields a tty will + * still remain usable under these extremes: + * rows columns what's + * tty top tty top displayed + * --- --- --- --- ------------------ + * 46 42 10 1 xPRFX only + * 46 42 32 1 full xPRFX + xSUFX + * 6 2 231 21 xPRFX only + * 10 6 231 7 full xPRFX + xSUFX + */ +static void display_fields (int focus, int extend) { + #define mkERR { putp("\n"); putp(N_txt(XTRA_winsize_txt)); return; } + #define mxCOL ( (Screen_cols / 11) > 0 ? (Screen_cols / 11) : 1 ) + #define yRSVD 4 + #define xEQUS 2 // length of suffix beginning '= ' + #define xSUFX 22 // total suffix length, incl xEQUS + #define xPRFX (10 + xadd) + #define xTOTL (xPRFX + xSUFX) + static int col_sav, row_sav; + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int i; // utility int (a row, tot cols, ix) + int smax; // printable width of xSUFX + int xadd = 0; // spacing between data columns + int cmax = Screen_cols; // total data column width + int rmax = Screen_rows - yRSVD; // total usable rows + + i = (EU_MAXPFLGS % mxCOL) ? 1 : 0; + if (rmax < i + (EU_MAXPFLGS / mxCOL)) mkERR; + i = EU_MAXPFLGS / rmax; + if (EU_MAXPFLGS % rmax) ++i; + if (i > 1) { cmax /= i; xadd = 1; } + if (cmax > xTOTL) cmax = xTOTL; + smax = cmax - xPRFX; + if (smax < 0) mkERR; + + /* we'll go the extra distance to avoid any potential screen flicker + which occurs under some terminal emulators (but it was our fault) */ + if (col_sav != Screen_cols || row_sav != Screen_rows) { + col_sav = Screen_cols; + row_sav = Screen_rows; + putp(Cap_clr_eos); + } + fflush(stdout); + + for (i = 0; i < EU_MAXPFLGS; ++i) { + int b = FLDviz(w, i), x = (i / rmax) * cmax, y = (i % rmax) + yRSVD; + const char *e = (i == focus && extend) ? w->capclr_hdr : ""; + FLG_t f = FLDget(w, i); + char sbuf[xSUFX*4]; // 4 = max multi-byte + int xcol, xfld; + + /* prep sacrificial suffix (allowing for beginning '= ') + note: width passed to 'utf8_embody' may go negative, but he'll be just fine */ + snprintf(sbuf, sizeof(sbuf), "= %.*s", utf8_embody(N_fld(f), smax - xEQUS), N_fld(f)); + // obtain translated deltas (if any) ... + xcol = utf8_delta(fmtmk("%.*s", utf8_embody(N_col(f), 8), N_col(f))); + xfld = utf8_delta(sbuf + xEQUS); // ignore beginning '= ' + + PUTT("%s%c%s%s %s%-*.*s%s%s%s %-*.*s%s" + , tg2(x, y) + , b ? '*' : ' ' + , b ? w->cap_bold : Cap_norm + , e + , i == focus ? w->capclr_hdr : "" + , 8 + xcol, 8 + xcol + , N_col(f) + , Cap_norm + , b ? w->cap_bold : "" + , e + , smax + xfld, smax + xfld + , sbuf + , Cap_norm); + } + + putp(Caps_off); + #undef mkERR + #undef mxCOL + #undef yRSVD + #undef xEQUS + #undef xSUFX + #undef xPRFX + #undef xTOTL +} // end: display_fields + + + /* + * Manage all fields aspects (order/toggle/sort), for all windows. */ +static void fields_utility (void) { +#ifndef SCROLLVAR_NO + #define unSCRL { w->begpflg = w->varcolbeg = 0; OFFw(w, Show_HICOLS); } +#else + #define unSCRL { w->begpflg = 0; OFFw(w, Show_HICOLS); } +#endif + #define swapEM { int c; unSCRL; c = w->rc.fieldscur[i]; \ + w->rc.fieldscur[i] = *p; *p = c; p = &w->rc.fieldscur[i]; } + #define spewFI { int *t; f = w->rc.sortindx; t = msch(w->rc.fieldscur, ENUcvt(f, FLDon), EU_MAXPFLGS); \ + if (!t) t = msch(w->rc.fieldscur, ENUcvt(f, FLDoff), EU_MAXPFLGS); \ + i = (t) ? (int)(t - w->rc.fieldscur) : 0; } + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + const char *h = NULL; + int *p = NULL; + int i, key; + FLG_t f; + + spewFI +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + do { + if (!h) h = N_col(f); + putp(Cap_home); + show_special(1, fmtmk(N_unq(FIELD_header_fmt) + , w->grpname, CHKw(w, Show_FOREST) ? N_txt(FOREST_views_txt) : h)); + display_fields(i, (p != NULL)); + fflush(stdout); + + if (Frames_signal) goto signify_that; + key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + + switch (key) { + case kbd_UP: + if (i > 0) { --i; if (p) swapEM } + break; + case kbd_DOWN: + if (i + 1 < EU_MAXPFLGS) { ++i; if (p) swapEM } + break; + case kbd_LEFT: + case kbd_ENTER: + p = NULL; + break; + case kbd_RIGHT: + p = &w->rc.fieldscur[i]; + break; + case kbd_HOME: + case kbd_PGUP: + if (!p) i = 0; + break; + case kbd_END: + case kbd_PGDN: + if (!p) i = EU_MAXPFLGS - 1; + break; + case kbd_SPACE: + case 'd': + if (!p) { FLDtog(w, i); unSCRL } + break; + case 's': +#ifdef TREE_NORESET + if (!p && !CHKw(w, Show_FOREST)) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL } +#else + if (!p) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL; OFFw(w, Show_FOREST); } +#endif + break; + case 'a': + case 'w': + Curwin = w = ('a' == key) ? w->next : w->prev; + spewFI + h = NULL; + p = NULL; + break; + default: // keep gcc happy + break; + } + } while (key != 'q' && key != kbd_ESC); + + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; + #undef unSCRL + #undef swapEM + #undef spewFI +} // end: fields_utility + + + /* + * This routine takes care of auto sizing field widths + * if/when the user sets Rc.fixed_widest to -1. Along the + * way he reinitializes some things for the next frame. */ +static inline void widths_resize (void) { + int i; + + // next var may also be set by the guys that actually truncate stuff + Autox_found = 0; + for (i = 0; i < EU_MAXPFLGS; i++) { + if (Autox_array[i]) { + Fieldstab[i].width++; + Autox_array[i] = 0; + Autox_found = 1; + } + } + // trigger a call to calibrate_fields (via zap_fieldstab) + if (Autox_found) Frames_signal = BREAK_autox; +} // end: widths_resize + + + /* + * This routine exists just to consolidate most of the messin' + * around with the Fieldstab array and some related stuff. */ +static void zap_fieldstab (void) { +#ifdef WIDEN_COLUMN + #define maX(E) ( (wtab[E].wnls > wtab[E].wmin) \ + ? wtab[E].wnls : wtab[E].wmin ) + static struct { + int wmin; // minimum field width (-1 == variable width) + int wnls; // translated header column requirements + int watx; // +1 == non-scalable auto sized columns + } wtab[EU_MAXPFLGS]; +#endif + static int once; + int i, digits; + char buf[8]; + + if (!once) { + Fieldstab[EU_CPN].width = 1; + Fieldstab[EU_NMA].width = 2; + Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width + = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width + = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = 5; + if (5 < (digits = (int)procps_pid_length())) { + if (10 < digits) error_exit(N_txt(FAIL_widepid_txt)); + Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width + = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width + = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = digits; + } +#ifdef WIDEN_COLUMN + // identify our non-scalable auto sized columns + wtab[EU_UED].watx = wtab[EU_UEN].watx = wtab[EU_URD].watx + = wtab[EU_URN].watx = wtab[EU_USD].watx = wtab[EU_USN].watx + = wtab[EU_GID].watx = wtab[EU_GRP].watx = wtab[EU_TTY].watx + = wtab[EU_WCH].watx = wtab[EU_NS1].watx = wtab[EU_NS2].watx + = wtab[EU_NS3].watx = wtab[EU_NS4].watx = wtab[EU_NS5].watx + = wtab[EU_NS6].watx = wtab[EU_NS7].watx = wtab[EU_NS8].watx + = wtab[EU_LXC].watx = wtab[EU_LID].watx + = +1; + /* establish translatable header 'column' requirements + and ensure .width reflects the widest value */ + for (i = 0; i < EU_MAXPFLGS; i++) { + wtab[i].wmin = Fieldstab[i].width; + wtab[i].wnls = (int)strlen(N_col(i)) - utf8_delta(N_col(i)); + if (wtab[i].wmin != -1) + Fieldstab[i].width = maX(i); + } +#endif + once = 1; + } + + Cpu_pmax = 99.9; + if (Rc.mode_irixps && Cpu_cnt > 1 && !Thread_mode) { + Cpu_pmax = 100.0 * Cpu_cnt; + if (Cpu_cnt > 1000) { + if (Cpu_pmax > 9999999.0) Cpu_pmax = 9999999.0; + } else if (Cpu_cnt > 100) { + if (Cpu_cnt > 999999.0) Cpu_pmax = 999999.0; + } else if (Cpu_cnt > 10) { + if (Cpu_pmax > 99999.0) Cpu_pmax = 99999.0; + } else { + if (Cpu_pmax > 999.9) Cpu_pmax = 999.9; + } + } + +#ifdef WIDEN_COLUMN + digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt); + if (wtab[EU_CPN].wmin < digits) { + if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt)); + wtab[EU_CPN].wmin = digits; + Fieldstab[EU_CPN].width = maX(EU_CPN); + } + digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot); + if (wtab[EU_NMA].wmin < digits) { + wtab[EU_NMA].wmin = digits; + Fieldstab[EU_NMA].width = maX(EU_NMA); + } + + // and accommodate optional wider non-scalable columns (maybe) + if (!AUTOX_MODE) { + for (i = 0; i < EU_MAXPFLGS; i++) { + if (wtab[i].watx) + Fieldstab[i].width = Rc.fixed_widest ? Rc.fixed_widest + maX(i) : maX(i); + } + } +#else + digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt); + if (1 < digits) { + if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt)); + Fieldstab[EU_CPN].width = digits; + } + digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot); + if (2 < digits) + Fieldstab[EU_NMA].width = digits; + + // and accommodate optional wider non-scalable columns (maybe) + if (!AUTOX_MODE) { + Fieldstab[EU_UED].width = Fieldstab[EU_URD].width + = Fieldstab[EU_USD].width = Fieldstab[EU_GID].width + = Rc.fixed_widest ? 5 + Rc.fixed_widest : 5; + Fieldstab[EU_UEN].width = Fieldstab[EU_URN].width + = Fieldstab[EU_USN].width = Fieldstab[EU_GRP].width + = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8; + Fieldstab[EU_TTY].width = Fieldstab[EU_LXC].width + = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8; + Fieldstab[EU_WCH].width + = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10; + // the initial namespace fields + for (i = EU_NS1; i <= EU_NS6; i++) + Fieldstab[i].width + = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10; + // the later namespace additions + for (i = EU_NS7; i <= EU_NS8; i++) + Fieldstab[i].width + = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10; + } +#endif + + /* plus user selectable scaling */ + Fieldstab[EU_VRT].scale = Fieldstab[EU_SWP].scale + = Fieldstab[EU_RES].scale = Fieldstab[EU_COD].scale + = Fieldstab[EU_DAT].scale = Fieldstab[EU_SHR].scale + = Fieldstab[EU_USE].scale = Fieldstab[EU_RZA].scale + = Fieldstab[EU_RZF].scale = Fieldstab[EU_RZL].scale + = Fieldstab[EU_RZS].scale = Fieldstab[EU_RSS].scale + = Fieldstab[EU_PSS].scale = Fieldstab[EU_PZA].scale + = Fieldstab[EU_PZF].scale = Fieldstab[EU_PZS].scale + = Fieldstab[EU_USS].scale = Rc.task_mscale; + + // lastly, ensure we've got proper column headers... + calibrate_fields(); + #undef maX +} // end: zap_fieldstab + +/*###### Library Interface (as separate threads) #######################*/ + + /* + * This guy's responsible for interfacing with the library <stat> API + * and reaping all cpu or numa node tics. + * ( his task is now embarassingly small under the new api ) */ +static void *cpus_refresh (void *unused) { + enum stat_reap_type which; + + do { +#ifdef THREADED_CPU + sem_wait(&Semaphore_cpus_beg); +#endif + which = STAT_REAP_CPUS_ONLY; + if (CHKw(Curwin, View_CPUNOD)) + which = STAT_REAP_NUMA_NODES_TOO; + + Stat_reap = procps_stat_reap(Stat_ctx, which, Stat_items, MAXTBL(Stat_items)); + if (!Stat_reap) + error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno))); +#ifndef PRETEND0NUMA + // adapt to changes in total numa nodes (assuming it's even possible) + if (Stat_reap->numa->total && Stat_reap->numa->total != Numa_node_tot) { + Numa_node_tot = Stat_reap->numa->total; + Numa_node_sel = -1; + } +#endif + if (Stat_reap->cpus->total && Stat_reap->cpus->total != Cpu_cnt) { + Cpu_cnt = Stat_reap->cpus->total; +#ifdef PRETEND48CPU + Cpu_cnt = 48; +#endif + } +#ifdef PRETENDECORE +{ int i, x; + x = Cpu_cnt - (Cpu_cnt / 4); + for (i = 0; i < Cpu_cnt; i++) + Stat_reap->cpus->stacks[i]->head[stat_COR_TYP].result.s_int = (i < x) ? P_CORE : E_CORE; +} +#endif +#ifdef THREADED_CPU + sem_post(&Semaphore_cpus_end); + } while (1); +#else + } while (0); +#endif + return NULL; + (void)unused; +} // end: cpus_refresh + + + /* + * This serves as our interface to the memory portion of libprocps. + * The sampling frequency is reduced in order to minimize overhead. */ +static void *memory_refresh (void *unused) { + static time_t sav_secs; + time_t cur_secs; + + do { +#ifdef THREADED_MEM + sem_wait(&Semaphore_memory_beg); +#endif + if (Frames_signal) + sav_secs = 0; + cur_secs = time(NULL); + + if (3 <= cur_secs - sav_secs) { + if (!(Mem_stack = procps_meminfo_select(Mem_ctx, Mem_items, MAXTBL(Mem_items)))) + error_exit(fmtmk(N_fmt(LIB_errormem_fmt), __LINE__, strerror(errno))); + sav_secs = cur_secs; + } +#ifdef THREADED_MEM + sem_post(&Semaphore_memory_end); + } while (1); +#else + } while (0); +#endif + return NULL; + (void)unused; +} // end: memory_refresh + + + /* + * This guy's responsible for interfacing with the library <pids> API + * then refreshing the WIN_t ptr arrays, growing them as appropirate. */ +static void *tasks_refresh (void *unused) { + #define nALIGN(n,m) (((n + m - 1) / m) * m) // unconditionally align + #define nALGN2(n,m) ((n + m - 1) & ~(m - 1)) // with power of 2 align + #define n_reap Pids_reap->counts->total + static double uptime_sav; + static int n_alloc = -1; // size of windows stacks arrays + double uptime_cur; + float et; + int i, what; + + do { +#ifdef THREADED_TSK + sem_wait(&Semaphore_tasks_beg); +#endif + procps_uptime(&uptime_cur, NULL); + et = uptime_cur - uptime_sav; + if (et < 0.01) et = 0.005; + uptime_sav = uptime_cur; + // if in Solaris mode, adjust our scaling for all cpus + Frame_etscale = 100.0f / ((float)Hertz * (float)et * (Rc.mode_irixps ? 1 : Cpu_cnt)); + + what = Thread_mode ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY; + if (Monpidsidx) { + what |= PIDS_SELECT_PID; + Pids_reap = procps_pids_select(Pids_ctx, (unsigned *)Monpids, Monpidsidx, what); + } else + Pids_reap = procps_pids_reap(Pids_ctx, what); + if (!Pids_reap) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno))); + + // now refresh each window's stacks pointer array... + if (n_alloc < n_reap) { +// n_alloc = nALIGN(n_reap, 100); + n_alloc = nALGN2(n_reap, 128); + for (i = 0; i < GROUPSMAX; i++) { + Winstk[i].ppt = alloc_r(Winstk[i].ppt, sizeof(void *) * n_alloc); + memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt); + } + } else { + for (i = 0; i < GROUPSMAX; i++) + memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt); + } +#ifdef THREADED_TSK + sem_post(&Semaphore_tasks_end); + } while (1); +#else + } while (0); +#endif + return NULL; + (void)unused; + #undef nALIGN + #undef nALGN2 + #undef n_reap +} // end: tasks_refresh + +/*###### Inspect Other Output ##########################################*/ + + /* + * HOWTO Extend the top 'inspect' functionality: + * + * To exploit the 'Y' interactive command, one must add entries to + * the top personal configuration file. Such entries simply reflect + * a file to be read or command/pipeline to be executed whose results + * will then be displayed in a separate scrollable window. + * + * Entries beginning with a '#' character are ignored, regardless of + * content. Otherwise they consist of the following 3 elements, each + * of which must be separated by a tab character (thus 2 '\t' total): + * type: literal 'file' or 'pipe' + * name: selection shown on the Inspect screen + * fmts: string representing a path or command + * + * The two types of Inspect entries are not interchangeable. + * Those designated 'file' will be accessed using fopen/fread and must + * reference a single file in the 'fmts' element. Entries specifying + * 'pipe' will employ popen/fread, their 'fmts' element could contain + * many pipelined commands and, none can be interactive. + * + * Here are some examples of both types of inspection entries. + * The first entry will be ignored due to the initial '#' character. + * For clarity, the pseudo tab depictions (^I) are surrounded by an + * extra space but the actual tabs would not be. + * + * # pipe ^I Sockets ^I lsof -n -P -i 2>&1 + * pipe ^I Open Files ^I lsof -P -p %d 2>&1 + * file ^I NUMA Info ^I /proc/%d/numa_maps + * pipe ^I Log ^I tail -n100 /var/log/syslog | sort -Mr + * + * Caution: If the output contains unprintable characters they will + * be displayed in either the ^I notation or hexadecimal <FF> form. + * This applies to tab characters as well. So if one wants a more + * accurate display, any tabs should be expanded within the 'fmts'. + * + * The following example takes what could have been a 'file' entry + * but employs a 'pipe' instead so as to expand the tabs. + * + * # next would have contained '\t' ... + * # file ^I <your_name> ^I /proc/%d/status + * # but this will eliminate embedded '\t' ... + * pipe ^I <your_name> ^I cat /proc/%d/status | expand - + * + * Note: If a pipe such as the following was established, one must + * use Ctrl-C to terminate that pipe in order to review the results. + * This is the single occasion where a '^C' will not terminate top. + * + * pipe ^I Trace ^I /usr/bin/strace -p %d 2>&1 + */ + + /* + * Our driving table support, the basis for generalized inspection, + * built at startup (if at all) from rcfile or demo entries. */ +struct I_ent { + void (*func)(char *, int); // a pointer to file/pipe/demo function + char *type; // the type of entry ('file' or 'pipe') + char *name; // the selection label for display + char *fmts; // format string to build path or command + int farg; // 1 = '%d' in fmts, 0 = not (future use) + const char *caps; // not really caps, show_special() delim's + char *fstr; // entry's current/active search string + int flen; // above's strlen, without call overhead +}; +struct I_struc { + int demo; // do NOT save table entries in rcfile + int total; // total I_ent table entries + char *raw; // all entries for 'W', incl '#' & blank + struct I_ent *tab; +}; +static struct I_struc Inspect; + +static char **Insp_p; // pointers to each line start +static int Insp_nl; // total lines, total Insp_p entries +static int Insp_utf8; // treat Insp_buf as translatable, else raw +static char *Insp_buf; // the results from insp_do_file/pipe +static size_t Insp_bufsz; // allocated size of Insp_buf +static size_t Insp_bufrd; // bytes actually in Insp_buf +static struct I_ent *Insp_sel; // currently selected Inspect entry + + // Our 'make status line' macro +#define INSP_MKSL(big,txt) { int _sz = big ? Screen_cols : 80; \ + const char *_p; \ + _sz += utf8_delta(txt); \ + _p = fmtmk("%-*.*s", _sz, _sz, txt); \ + PUTT("%s%s%.*s%s", tg2(0, (Msg_row = 3)), Curwin->capclr_hdr \ + , utf8_embody(_p, Screen_cols), _p, Cap_clr_eol); \ + putp(Caps_off); fflush(stdout); } + + // Our 'row length' macro, equivalent to a strlen() call +#define INSP_RLEN(idx) (int)(Insp_p[idx +1] - Insp_p[idx] -1) + + // Our 'busy/working' macro +#define INSP_BUSY(enu) { INSP_MKSL(0, N_txt(enu)) } + + + /* + * Establish the number of lines present in the Insp_buf glob plus + * build the all important row start array. It is that array that + * others will rely on since we dare not try to use strlen() on what + * is potentially raw binary data. Who knows what some user might + * name as a file or include in a pipeline (scary, ain't it?). */ +static void insp_cnt_nl (void) { + char *beg = Insp_buf; + char *cur = Insp_buf; + char *end = Insp_buf + Insp_bufrd + 1; + +#ifdef INSP_SAVEBUF +{ + static int n = 1; + char fn[SMLBUFSIZ]; + FILE *fd; + snprintf(fn, sizeof(fn), "%s.Insp_buf.%02d.txt", Myname, n++); + fd = fopen(fn, "w"); + if (fd) { + fwrite(Insp_buf, 1, Insp_bufrd, fd); + fclose(fd); + } +} +#endif + Insp_p = alloc_c(sizeof(char *) * 2); + + for (Insp_nl = 0; beg < end; beg++) { + if (*beg == '\n') { + Insp_p[Insp_nl++] = cur; + // keep our array ahead of next potential need (plus the 2 above) + Insp_p = alloc_r(Insp_p, (sizeof(char *) * (Insp_nl +3))); + cur = beg +1; + } + } + Insp_p[0] = Insp_buf; + Insp_p[Insp_nl++] = cur; + Insp_p[Insp_nl] = end; + if ((end - cur) == 1) // if there's an eof null delimiter, + --Insp_nl; // don't count it as a new line +} // end: insp_cnt_nl + + +#ifndef INSP_OFFDEMO + /* + * The pseudo output DEMO utility. */ +static void insp_do_demo (char *fmts, int pid) { + (void)fmts; (void)pid; + /* next will put us on a par with the real file/pipe read buffers + ( and also avoid a harmless, but evil sounding, valgrind warning ) */ + Insp_bufsz = READMINSZ + strlen(N_txt(YINSP_dstory_txt)); + Insp_buf = alloc_c(Insp_bufsz); + Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s", N_txt(YINSP_dstory_txt)); + insp_cnt_nl(); +} // end: insp_do_demo +#endif + + + /* + * The generalized FILE utility. */ +static void insp_do_file (char *fmts, int pid) { + char buf[LRGBUFSIZ]; + FILE *fp; + int rc; + + snprintf(buf, sizeof(buf), fmts, pid); + fp = fopen(buf, "r"); + rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd); + if (fp) fclose(fp); + if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s" + , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno))); + insp_cnt_nl(); +} // end: insp_do_file + + + /* + * The generalized PIPE utility. */ +static void insp_do_pipe (char *fmts, int pid) { + char buf[LRGBUFSIZ]; + struct sigaction sa; + FILE *fp; + int rc; + + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + sigaction(SIGINT, &sa, NULL); + + snprintf(buf, sizeof(buf), fmts, pid); + fp = popen(buf, "r"); + rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd); + if (fp) pclose(fp); + if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s" + , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno))); + insp_cnt_nl(); + + sa.sa_handler = sig_endpgm; + sigaction(SIGINT, &sa, NULL); +} // end: insp_do_pipe + + + /* + * This guy is a *Helper* function serving the following two masters: + * insp_find_str() - find the next Insp_sel->fstr match + * insp_mkrow_... - highlight any Insp_sel->fstr matches in-view + * If Insp_sel->fstr is found in the designated row, he returns the + * offset from the start of the row, otherwise he returns a huge + * integer so traditional fencepost usage can be employed. */ +static inline int insp_find_ofs (int col, int row) { + #define begFS (int)(fnd - Insp_p[row]) + char *p, *fnd = NULL; + + if (Insp_sel->fstr[0]) { + // skip this row, if there's no chance of a match + if (memchr(Insp_p[row], Insp_sel->fstr[0], INSP_RLEN(row))) { + for ( ; col < INSP_RLEN(row); col++) { + if (!*(p = Insp_p[row] + col)) // skip any empty strings + continue; + fnd = STRSTR(p, Insp_sel->fstr); // with binary data, each + if (fnd) // row may have '\0'. so + break; // our scans must be done + col += strlen(p); // as individual strings. + } + if (fnd && fnd < Insp_p[row + 1]) // and, we must watch out + return begFS; // for potential overrun! + } + } + return INT_MAX; + #undef begFS +} // end: insp_find_ofs + + + /* + * This guy supports the inspect 'L' and '&' search provisions + * and returns the row and *optimal* column for viewing any match + * ( we'll always opt for left column justification since any ) + * ( preceding ctrl chars appropriate an unpredictable amount ) */ +static void insp_find_str (int ch, int *col, int *row) { + #define reDUX (found) ? N_txt(WORD_another_txt) : "" + static int found; + + if ((ch == '&' || ch == 'n') && !Insp_sel->fstr[0]) { + show_msg(N_txt(FIND_no_next_txt)); + return; + } + if (ch == 'L' || ch == '/') { + char *str = ioline(N_txt(GET_find_str_txt)); + if (*str == kbd_ESC) return; + snprintf(Insp_sel->fstr, FNDBUFSIZ, "%s", str); + Insp_sel->flen = strlen(Insp_sel->fstr); + found = 0; + } + if (Insp_sel->fstr[0]) { + int xx, yy; + + INSP_BUSY(YINSP_waitin_txt); + for (xx = *col, yy = *row; yy < Insp_nl; ) { + xx = insp_find_ofs(xx, yy); + if (xx < INSP_RLEN(yy)) { + found = 1; + if (xx == *col && yy == *row) { // matched where we were! + ++xx; // ( was the user maybe ) + continue; // ( trying to fool us? ) + } + *col = xx; + *row = yy; + return; + } + xx = 0; + ++yy; + } + show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Insp_sel->fstr)); + } + #undef reDUX +} // end: insp_find_str + + + /* + * This guy is a *Helper* function responsible for positioning a + * single row in the current 'X axis', then displaying the results. + * Along the way, he makes sure control characters and/or unprintable + * characters display in a less-like fashion: + * '^A' for control chars + * '<BC>' for other unprintable stuff + * Those will be highlighted with the current windows's capclr_msg, + * while visible search matches display with capclr_hdr for emphasis. + * ( we hide ugly plumbing in macros to concentrate on the algorithm ) */ +static void insp_mkrow_raw (int col, int row) { + #define maxSZ ( Screen_cols - to ) + #define capNO { if (hicap) { putp(Caps_off); hicap = 0; } } + #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \ + fr += Insp_sel->flen -1; to += Insp_sel->flen; hicap = 0; } +#ifndef INSP_JUSTNOT + #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \ + PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 2; hicap = 1; } + #define mkUNP { const char *p = fmtmk("<%02X>", uch); \ + PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 4; hicap = 1; } +#else + #define mkCTL { if ((to += 2) <= Screen_cols) \ + PUTT("%s^%c", (!hicap) ? Curwin->capclr_msg : "", uch + '@'); hicap = 1; } + #define mkUNP { if ((to += 4) <= Screen_cols) \ + PUTT("%s<%02X>", (!hicap) ? Curwin->capclr_msg : "", uch); hicap = 1; } +#endif + #define mkSTD { capNO; if (++to <= Screen_cols) { static char _str[2]; \ + _str[0] = uch; putp(_str); } } + unsigned char tline[SCREENMAX]; + int fr, to, ofs; + int hicap = 0; + + if (col < INSP_RLEN(row)) + memcpy(tline, Insp_p[row] + col, sizeof(tline)); + else tline[0] = '\n'; + + for (fr = 0, to = 0, ofs = 0; to < Screen_cols; fr++) { + if (!ofs) + ofs = insp_find_ofs(col + fr, row); + if (col + fr < ofs) { + unsigned char uch = tline[fr]; + if (uch == '\n') break; // a no show (he,he) + if (uch > 126) mkUNP // show as: '<AB>' + else if (uch < 32) mkCTL // show as: '^C' + else mkSTD // a show off (he,he) + } else { mkFND // a big show (he,he) + ofs = 0; + } + if (col + fr >= INSP_RLEN(row)) break; + } + capNO; + putp(Cap_clr_eol); + + #undef maxSZ + #undef capNO + #undef mkFND + #undef mkCTL + #undef mkUNP + #undef mkSTD +} // end: insp_mkrow_raw + + + /* + * This guy is a *Helper* function responsible for positioning a + * single row in the current 'X axis' within a multi-byte string + * then displaying the results. Along the way he ensures control + * characters will then be displayed in two positions like '^A'. + * ( assuming they can even get past those 'gettext' utilities ) */ +static void insp_mkrow_utf8 (int col, int row) { + #define maxSZ ( Screen_cols - to ) + #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \ + fr += Insp_sel->flen; to += Insp_sel->flen; } +#ifndef INSP_JUSTNOT + #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \ + PUTT("%s%.*s%s", Curwin->capclr_msg, maxSZ, p, Caps_off); to += 2; } +#else + #define mkCTL { if ((to += 2) <= Screen_cols) \ + PUTT("%s^%c%s", Curwin->capclr_msg, uch + '@', Caps_off); } +#endif + #define mkNUL { buf1[0] = ' '; doPUT(buf1) } + #define doPUT(buf) if ((to += cno) <= Screen_cols) putp(buf); + static char buf1[2], buf2[3], buf3[4], buf4[5]; + unsigned char tline[BIGBUFSIZ]; + int fr, to, ofs; + + col = utf8_proper_col(Insp_p[row], col, 1); + if (col < INSP_RLEN(row)) + memcpy(tline, Insp_p[row] + col, sizeof(tline)); + else tline[0] = '\n'; + + for (fr = 0, to = 0, ofs = 0; to < Screen_cols; ) { + if (!ofs) + ofs = insp_find_ofs(col + fr, row); + if (col + fr < ofs) { + unsigned char uch = tline[fr]; + int bno = UTF8_tab[uch]; + int cno = utf8_cols(&tline[fr++], bno); + switch (bno) { + case 1: + if (uch == '\n') break; + if (uch < 32) mkCTL + else if (uch == 127) mkNUL + else { buf1[0] = uch; doPUT(buf1) } + break; + case 2: + buf2[0] = uch; buf2[1] = tline[fr++]; + doPUT(buf2) + break; + case 3: + buf3[0] = uch; buf3[1] = tline[fr++]; buf3[2] = tline[fr++]; + doPUT(buf3) + break; + case 4: + buf4[0] = uch; buf4[1] = tline[fr++]; buf4[2] = tline[fr++]; buf4[3] = tline[fr++]; + doPUT(buf4) + break; + default: + mkNUL + break; + } + } else { + mkFND + ofs = 0; + } + if (col + fr >= INSP_RLEN(row)) break; + } + putp(Cap_clr_eol); + + #undef maxSZ + #undef mkFND + #undef mkCTL + #undef mkNUL + #undef doPUT +} // end: insp_mkrow_utf8 + + + /* + * This guy is an insp_view_choice() *Helper* function who displays + * a page worth of of the user's damages. He also creates a status + * line based on maximum digits for the current selection's lines and + * hozizontal position (so it serves to inform, not distract, by + * otherwise being jumpy). */ +static inline void insp_show_pgs (int col, int row, int max) { + char buf[SMLBUFSIZ]; + void (*mkrow_func)(int, int); + int r = snprintf(buf, sizeof(buf), "%d", Insp_nl); + int c = snprintf(buf, sizeof(buf), "%d", col +Screen_cols); + int l = row +1, ls = Insp_nl; + + if (!Insp_bufrd) + l = ls = 0; + snprintf(buf, sizeof(buf), N_fmt(YINSP_status_fmt) + , Insp_sel->name + , r, l, r, ls + , c, col + 1, c, col + Screen_cols + , (unsigned long)Insp_bufrd); + INSP_MKSL(0, buf); + + mkrow_func = Insp_utf8 ? insp_mkrow_utf8 : insp_mkrow_raw; + + for ( ; max && row < Insp_nl; row++) { + putp("\n"); + mkrow_func(col, row); + --max; + } + + if (max) + putp(Cap_nl_clreos); +} // end: insp_show_pgs + + + /* + * This guy is responsible for displaying the Insp_buf contents and + * managing all scrolling/locate requests until the user gives up. */ +static int insp_view_choice (struct pids_stack *p) { +#ifdef INSP_SLIDE_1 + #define hzAMT 1 +#else + #define hzAMT 8 +#endif + #define maxLN (Screen_rows - (Msg_row +1)) + #define makHD(b1,b2) { \ + snprintf(b1, sizeof(b1), "%d", PID_VAL(EU_PID, s_int, p)); \ + snprintf(b2, sizeof(b2), "%s", PID_VAL(EU_CMD, str, p)); } + #define makFS(dst) { if (Insp_sel->flen < 22) \ + snprintf(dst, sizeof(dst), "%s", Insp_sel->fstr); \ + else snprintf(dst, sizeof(dst), "%.19s...", Insp_sel->fstr); } + char buf[LRGBUFSIZ]; + int key, curlin = 0, curcol = 0; + +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + for (;;) { + char pid[6], cmd[64]; + + if (curcol < 0) curcol = 0; + if (curlin >= Insp_nl) curlin = Insp_nl -1; + if (curlin < 0) curlin = 0; + + makFS(buf) + makHD(pid,cmd) + putp(Cap_home); + show_special(1, fmtmk(N_unq(YINSP_hdview_fmt) + , pid, cmd, (Insp_sel->fstr[0]) ? buf : " N/A ")); // nls_maybe + insp_show_pgs(curcol, curlin, maxLN); + fflush(stdout); + /* fflush(stdin) didn't do the trick, so we'll just dip a little deeper + lest repeated <Enter> keys produce immediate re-selection in caller */ + tcflush(STDIN_FILENO, TCIFLUSH); + + if (Frames_signal) goto signify_that; + key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + + switch (key) { + case kbd_ENTER: // must force new iokey() + key = INT_MAX; // fall through ! + case kbd_ESC: + case 'q': + putp(Cap_clr_scr); + return key; + case kbd_LEFT: + curcol -= hzAMT; + break; + case kbd_RIGHT: + curcol += hzAMT; + break; + case kbd_UP: + --curlin; + break; + case kbd_DOWN: + ++curlin; + break; + case kbd_PGUP: + case 'b': + curlin -= maxLN -1; // keep 1 line for reference + break; + case kbd_PGDN: + case kbd_SPACE: + curlin += maxLN -1; // ditto + break; + case kbd_HOME: + case 'g': + curcol = curlin = 0; + break; + case kbd_END: + case 'G': + curcol = 0; + curlin = Insp_nl - maxLN; + break; + case 'L': + case '&': + case '/': + case 'n': + if (!Insp_utf8) + insp_find_str(key, &curcol, &curlin); + else { + int tmpcol = utf8_proper_col(Insp_p[curlin], curcol, 1); + insp_find_str(key, &tmpcol, &curlin); + curcol = utf8_proper_col(Insp_p[curlin], tmpcol, 0); + } + // must re-hide cursor in case a prompt for a string makes it huge + putp((Cursor_state = Cap_curs_hide)); + break; + case '=': + snprintf(buf, sizeof(buf), "%s: %s", Insp_sel->type, Insp_sel->fmts); + INSP_MKSL(1, buf); // show an extended SL + if (iokey(IOKEY_ONCE) < 1) + goto signify_that; + break; + default: // keep gcc happy + break; + } + } + #undef hzAMT + #undef maxLN + #undef makHD + #undef makFS +} // end: insp_view_choice + + + /* + * This is the main Inspect routine, responsible for: + * 1) validating the passed pid (required, but not always used) + * 2) presenting/establishing the target selection + * 3) arranging to fill Insp_buf (via the Inspect.tab[?].func) + * 4) invoking insp_view_choice for viewing/scrolling/searching + * 5) cleaning up the dynamically acquired memory afterwards */ +static void inspection_utility (int pid) { + #define mkSEL(dst) { for (i = 0; i < Inspect.total; i++) Inspect.tab[i].caps = "~1"; \ + Inspect.tab[sel].caps = "~4"; dst[0] = '\0'; \ + for (i = 0; i < Inspect.total; i++) { char _s[SMLBUFSIZ]; \ + snprintf(_s, sizeof(_s), " %s %s", Inspect.tab[i].name, Inspect.tab[i].caps); \ + strncat(dst, _s, (sizeof(dst) - 1) - strlen(dst)); } } + char sels[SCREENMAX]; + static int sel; + int i, key; + struct pids_stack *p; + + for (i = 0, p = NULL; i < PIDSmaxt; i++) + if (pid == PID_VAL(EU_PID, s_int, Curwin->ppt[i])) { + p = Curwin->ppt[i]; + break; + } + if (!p) { + show_msg(fmtmk(N_fmt(YINSP_pidbad_fmt), pid)); + return; + } + // must re-hide cursor since the prompt for a pid made it huge + putp((Cursor_state = Cap_curs_hide)); +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + key = INT_MAX; + do { + mkSEL(sels); + putp(Cap_home); + show_special(1, fmtmk(N_unq(YINSP_hdsels_fmt) + , pid, PID_VAL(EU_CMD, str, Curwin->ppt[i]), sels)); + INSP_MKSL(0, " "); + + if (Frames_signal) goto signify_that; + if (key == INT_MAX) key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + + switch (key) { + case 'q': + case kbd_ESC: + break; + case kbd_END: + sel = 0; // fall through ! + case kbd_LEFT: + if (--sel < 0) sel = Inspect.total -1; + key = INT_MAX; + break; + case kbd_HOME: + sel = Inspect.total; // fall through ! + case kbd_RIGHT: + if (++sel >= Inspect.total) sel = 0; + key = INT_MAX; + break; + case kbd_ENTER: + INSP_BUSY(!strcmp("file", Inspect.tab[sel].type) + ? YINSP_waitin_txt : YINSP_workin_txt); + Insp_sel = &Inspect.tab[sel]; + Inspect.tab[sel].func(Inspect.tab[sel].fmts, pid); + Insp_utf8 = utf8_delta(Insp_buf); + key = insp_view_choice(p); + free(Insp_buf); + free(Insp_p); + break; + default: + goto signify_that; + } + } while (key != 'q' && key != kbd_ESC); + + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; + #undef mkSEL +} // end: inspection_utility + +#undef INSP_MKSL +#undef INSP_RLEN +#undef INSP_BUSY + +/*###### Other Filtering ###############################################*/ + + /* + * This structure is hung from a WIN_t when other filtering is active */ +struct osel_s { + struct osel_s *nxt; // the next criteria or NULL. + int (*rel)(const char *, const char *); // relational strings compare + char *(*sel)(const char *, const char *); // for selection str compares + char *raw; // raw user input (dup check) + char *val; // value included or excluded + int ops; // filter delimiter/operation + int inc; // include == 1, exclude == 0 + int enu; // field (procflag) to filter + int typ; // typ used to set: rel & sel +}; + + /* + * A function to parse, validate and build a single 'other filter' */ +static const char *osel_add (WIN_t *q, int ch, char *glob, int push) { + int (*rel)(const char *, const char *); + char *(*sel)(const char *, const char *); + char raw[MEDBUFSIZ], ops, *pval; + struct osel_s *osel; + int inc, enu; + + if (ch == 'o') { + rel = strcasecmp; + sel = strcasestr; + } else { + rel = strcmp; + sel = strstr; + } + + if (!snprintf(raw, sizeof(raw), "%s", glob)) + return NULL; + for (osel = q->osel_1st; osel; ) { + if (!strcmp(osel->raw, raw)) // #1: is criteria duplicate? + return N_txt(OSEL_errdups_txt); + osel = osel->nxt; + } + if (*glob != '!') inc = 1; // #2: is it include/exclude? + else { ++glob; inc = 0; } + + if (!(pval = strpbrk(glob, "<=>"))) // #3: do we see a delimiter? + return fmtmk(N_fmt(OSEL_errdelm_fmt) + , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt)); + ops = *(pval); + *(pval++) = '\0'; + + for (enu = 0; enu < EU_MAXPFLGS; enu++) // #4: is this a valid field? + if (!STRCMP(N_col(enu), glob)) break; + if (enu == EU_MAXPFLGS) + return fmtmk(N_fmt(XTRA_badflds_fmt), glob); + + if (!(*pval)) // #5: did we get some value? + return fmtmk(N_fmt(OSEL_errvalu_fmt) + , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt)); + + osel = alloc_c(sizeof(struct osel_s)); + osel->typ = ch; + osel->inc = inc; + osel->enu = enu; + osel->ops = ops; + if (ops == '=') osel->val = alloc_s(pval); + else osel->val = alloc_s(justify_pad(pval, Fieldstab[enu].width, Fieldstab[enu].align)); + osel->rel = rel; + osel->sel = sel; + osel->raw = alloc_s(raw); + + if (push) { + // a LIFO queue was used when we're interactive + osel->nxt = q->osel_1st; + q->osel_1st = osel; + } else { + // a FIFO queue must be employed for the rcfile + if (!q->osel_1st) + q->osel_1st = osel; + else { + struct osel_s *prev, *walk = q->osel_1st; + do { + prev = walk; + walk = walk->nxt; + } while (walk); + prev->nxt = osel; + } + } + q->osel_tot += 1; + + return NULL; +} // end: osel_add + + + /* + * A function to turn off entire other filtering in the given window */ +static void osel_clear (WIN_t *q) { + struct osel_s *osel = q->osel_1st; + + while (osel) { + struct osel_s *nxt = osel->nxt; + free(osel->val); + free(osel->raw); + free(osel); + osel = nxt; + } + q->osel_tot = 0; + q->osel_1st = NULL; +} // end: osel_clear + + + /* + * Determine if there are matching values or relationships among the + * other criteria in this passed window -- it's called from only one + * place, and likely inlined even without the directive */ +static inline int osel_matched (const WIN_t *q, FLG_t enu, const char *str) { + struct osel_s *osel = q->osel_1st; + + while (osel) { + if (osel->enu == enu) { + int r; + switch (osel->ops) { + case '<': // '<' needs the r < 0 unless + r = osel->rel(str, osel->val); // '!' which needs an inverse + if ((r >= 0 && osel->inc) || (r < 0 && !osel->inc)) return 0; + break; + case '>': // '>' needs the r > 0 unless + r = osel->rel(str, osel->val); // '!' which needs an inverse + if ((r <= 0 && osel->inc) || (r > 0 && !osel->inc)) return 0; + break; + default: + { char *p = osel->sel(str, osel->val); + if ((!p && osel->inc) || (p && !osel->inc)) return 0; + } + break; + } + } + osel = osel->nxt; + } + return 1; +} // end: osel_matched + +/*###### Startup routines ##############################################*/ + + /* + * No matter what *they* say, we handle the really really BIG and + * IMPORTANT stuff upon which all those lessor functions depend! */ +static void before (char *me) { + #define doALL STAT_REAP_NUMA_NODES_TOO + int i, rc; + int linux_version_code = procps_linux_version(); + + atexit(close_stdout); + + // setup our program name + Myname = strrchr(me, '/'); + if (Myname) ++Myname; else Myname = me; + + // accommodate nls/gettext potential translations + // ( must 'setlocale' before our libproc called ) + initialize_nls(); + + // is /proc mounted? + fatal_proc_unmounted(NULL, 0); + +#ifndef OFF_STDERROR + /* there's a chance that damn libnuma may spew to stderr so we gotta + make sure he does not corrupt poor ol' top's first output screen! + Yes, he provides some overridable 'weak' functions to change such + behavior but we can't exploit that since we don't follow a normal + ld route to symbol resolution (we use that dlopen() guy instead)! */ + Stderr_save = dup(fileno(stderr)); + if (-1 < Stderr_save && freopen("/dev/null", "w", stderr)) + ; // avoid -Wunused-result +#endif + + // establish some cpu particulars + Hertz = procps_hertz_get(); + Cpu_States_fmts = N_unq(STATE_lin2x6_fmt); + if (linux_version_code >= LINUX_VERSION(2, 6, 11)) + Cpu_States_fmts = N_unq(STATE_lin2x7_fmt); + + // get the total cpus (and, if possible, numa node total) + if ((rc = procps_stat_new(&Stat_ctx))) + Restrict_some = Cpu_cnt = 1; + else { + if (!(Stat_reap = procps_stat_reap(Stat_ctx, doALL, Stat_items, MAXTBL(Stat_items)))) + error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno))); +#ifndef PRETEND0NUMA + Numa_node_tot = Stat_reap->numa->total; +#endif + Cpu_cnt = Stat_reap->cpus->total; +#ifdef PRETEND48CPU + Cpu_cnt = 48; +#endif + } + + // prepare for memory stats from new library API ... + if ((rc = procps_meminfo_new(&Mem_ctx))) + Restrict_some = 1; + + // establish max depth for newlib pids stack (# of result structs) + Pids_itms = alloc_c(sizeof(enum pids_item) * MAXTBL(Fieldstab)); + if (PIDS_noop != 0) + for (i = 0; i < MAXTBL(Fieldstab); i++) + Pids_itms[i] = PIDS_noop; + Pids_itms_tot = MAXTBL(Fieldstab); + // we will identify specific items in the build_headers() function + if ((rc = procps_pids_new(&Pids_ctx, Pids_itms, Pids_itms_tot))) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc))); + +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK +{ struct sigaction sa; + Thread_id_main = pthread_self(); + /* in case any of our threads have been enabled, they'll inherit this mask + with everything blocked. therefore, signals go to the main thread (us). */ + sigfillset(&sa.sa_mask); + pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL); +} +#endif + +#ifdef THREADED_CPU + if (0 != sem_init(&Semaphore_cpus_beg, 0, 0) + || (0 != sem_init(&Semaphore_cpus_end, 0, 0))) + error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno))); + if (0 != pthread_create(&Thread_id_cpus, NULL, cpus_refresh, NULL)) + error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno))); + pthread_setname_np(Thread_id_cpus, "update cpus"); +#endif +#ifdef THREADED_MEM + if (0 != sem_init(&Semaphore_memory_beg, 0, 0) + || (0 != sem_init(&Semaphore_memory_end, 0, 0))) + error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno))); + if (0 != pthread_create(&Thread_id_memory, NULL, memory_refresh, NULL)) + error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno))); + pthread_setname_np(Thread_id_memory, "update memory"); +#endif +#ifdef THREADED_TSK + if (0 != sem_init(&Semaphore_tasks_beg, 0, 0) + || (0 != sem_init(&Semaphore_tasks_end, 0, 0))) + error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno))); + if (0 != pthread_create(&Thread_id_tasks, NULL, tasks_refresh, NULL)) + error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno))); + pthread_setname_np(Thread_id_tasks, "update tasks"); +#endif + // lastly, establish support for graphing cpus & memory + Graph_cpus = alloc_c(sizeof(struct graph_parms)); + Graph_mems = alloc_c(sizeof(struct graph_parms)); + #undef doALL + + // don't distort startup cpu(s) display ... + usleep(LIB_USLEEP); +} // end: before + + + /* + * A configs_file *Helper* function responsible for transforming + * a 3.2.8 - 3.3.17 format 'fieldscur' into our integer based format */ +static int cfg_xform (WIN_t *q, char *flds, const char *defs) { + #define CVTon(c) ((c) |= 0x80) + static struct { + int old, new; + } flags_tab[] = { + #define old_View_NOBOLD 0x000001 + #define old_VISIBLE_tsk 0x000008 + #define old_Qsrt_NORMAL 0x000010 + #define old_Show_HICOLS 0x000200 + #define old_Show_THREAD 0x010000 + { old_View_NOBOLD, View_NOBOLD }, + { old_VISIBLE_tsk, Show_TASKON }, + { old_Qsrt_NORMAL, Qsrt_NORMAL }, + { old_Show_HICOLS, Show_HICOLS }, + { old_Show_THREAD, 0 } + #undef old_View_NOBOLD + #undef old_VISIBLE_tsk + #undef old_Qsrt_NORMAL + #undef old_Show_HICOLS + #undef old_Show_THREAD + }; + static char null_flds[] = "abcdefghijklmnopqrstuvwxyz"; + static const char fields_src[] = CVT_FORMER; + char fields_dst[PFLAGSSIZ], *p1, *p2; + int c, f, i, x, *pn; + + if (Rc.id == 'a') { + // first we'll touch up this window's winflags ... + x = q->rc.winflags; + q->rc.winflags = 0; + for (i = 0; i < MAXTBL(flags_tab); i++) { + if (x & flags_tab[i].old) { + x &= ~flags_tab[i].old; + q->rc.winflags |= flags_tab[i].new; + } + } + q->rc.winflags |= x; + + // now let's convert old top's more limited fields ... + f = strlen(flds); + if (f >= CVT_FLDMAX) + return 1; + strcpy(fields_dst, fields_src); + /* all other fields represent the 'on' state with a capitalized version + of a particular qwerty key. for the 2 additional suse out-of-memory + fields it makes perfect sense to do the exact opposite, doesn't it? + in any case, we must turn them 'off' temporarily ... */ + if ((p1 = strchr(flds, '['))) *p1 = '{'; + if ((p2 = strchr(flds, '\\'))) *p2 = '|'; + for (i = 0; i < f; i++) { + c = flds[i]; + x = tolower(c) - 'a'; + if (x < 0 || x >= CVT_FLDMAX) + return 1; + fields_dst[i] = fields_src[x]; + if (isupper(c)) + CVTon(fields_dst[i]); + } + // if we turned any suse only fields off, turn 'em back on OUR way ... + if (p1) CVTon(fields_dst[p1 - flds]); + if (p2) CVTon(fields_dst[p2 - flds]); + + // next, we must adjust the old sort field enum ... + x = q->rc.sortindx; + c = null_flds[x]; + q->rc.sortindx = 0; + if ((p1 = memchr(flds, c, CVT_FLDMAX)) + || ((p1 = memchr(flds, toupper(c), CVT_FLDMAX)))) { + x = p1 - flds; + q->rc.sortindx = (fields_dst[x] & 0x7f) - FLD_OFFSET; + } + // now we're in a 3.3.0 format (soon to be transformed) ... + strcpy(flds, fields_dst); + } + + // lastly, let's attend to the 3.3.0 - 3.3.17 fieldcurs format ... + pn = &q->rc.fieldscur[0]; + x = strlen(defs); + for (i = 0; i < x; i++) { + f = ((unsigned char)flds[i] & 0x7f); + f = f << 1; + if ((unsigned char)flds[i] & 0x80) f |= FLDon; + *(pn + i) = f; + } + + return 0; + #undef CVTon +} // end: cfg_xform + + + /* + * A configs_file *Helper* function responsible for reading + * and validating a configuration file's 'Inspection' entries */ +static int config_insp (FILE *fp, char *buf, size_t size) { + int i; + + // we'll start off with a 'potential' blank or empty line + // ( only realized if we end up with Inspect.total > 0 ) + if (!buf[0] || buf[0] != '\n') Inspect.raw = alloc_s("\n"); + else Inspect.raw = alloc_c(1); + + for (i = 0;;) { + #define iT(element) Inspect.tab[i].element + #define nxtLINE { buf[0] = '\0'; continue; } + size_t lraw = strlen(Inspect.raw) +1; + int n, x; + char *s1, *s2, *s3; + + if (i < 0 || (size_t)i >= INT_MAX / sizeof(struct I_ent)) break; + if (lraw >= INT_MAX - size) break; + + if (!buf[0] && !fgets(buf, size, fp)) break; + lraw += strlen(buf) +1; + Inspect.raw = alloc_r(Inspect.raw, lraw); + strcat(Inspect.raw, buf); + + if (buf[0] == '#' || buf[0] == '\n') nxtLINE; + Inspect.tab = alloc_r(Inspect.tab, sizeof(struct I_ent) * (i + 1)); + + // part of this is used in a show_special() call, so let's sanitize it + for (n = 0, x = strlen(buf); n < x; n++) { + if ((buf[n] != '\t' && buf[n] != '\n') + && (buf[n] < ' ')) { + buf[n] = '.'; + Rc_questions = 1; + } + } + if (!(s1 = strtok(buf, "\t\n"))) { Rc_questions = 1; nxtLINE; } + if (!(s2 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; } + if (!(s3 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; } + + switch (toupper(buf[0])) { + case 'F': + iT(func) = insp_do_file; + break; + case 'P': + iT(func) = insp_do_pipe; + break; + default: + Rc_questions = 1; + nxtLINE; + } + iT(type) = alloc_s(s1); + iT(name) = alloc_s(s2); + iT(fmts) = alloc_s(s3); + iT(farg) = (strstr(iT(fmts), "%d")) ? 1 : 0; + iT(fstr) = alloc_c(FNDBUFSIZ); + iT(flen) = 0; + + buf[0] = '\0'; + ++i; + #undef iT + #undef nxtLINE + } // end: for ('inspect' entries) + + Inspect.total = i; +#ifndef INSP_OFFDEMO + if (!Inspect.total) { + #define mkS(n) N_txt(YINSP_demo ## n ## _txt) + const char *sels[] = { mkS(01), mkS(02), mkS(03) }; + Inspect.total = Inspect.demo = MAXTBL(sels); + Inspect.tab = alloc_c(sizeof(struct I_ent) * Inspect.total); + for (i = 0; i < Inspect.total; i++) { + Inspect.tab[i].type = alloc_s(N_txt(YINSP_deqtyp_txt)); + Inspect.tab[i].name = alloc_s(sels[i]); + Inspect.tab[i].func = insp_do_demo; + Inspect.tab[i].fmts = alloc_s(N_txt(YINSP_deqfmt_txt)); + Inspect.tab[i].fstr = alloc_c(FNDBUFSIZ); + } + #undef mkS + } +#endif + return 0; +} // end: config_insp + + + /* + * A configs_file *Helper* function responsible for reading + * and validating a configuration file's 'Other Filter' entries */ +static int config_osel (FILE *fp, char *buf, size_t size) { + int i, ch, tot, wno, begun; + char *p; + + for (begun = 0;;) { + if (!fgets(buf, size, fp)) return 0; + if (buf[0] == '\n') continue; + // whoa, must be an 'inspect' entry + if (!begun && !strstr(buf, Osel_delim_1_txt)) + return 0; + // ok, we're now beginning + if (!begun && strstr(buf, Osel_delim_1_txt)) { + begun = 1; + continue; + } + // this marks the end of our stuff + if (begun && strstr(buf, Osel_delim_2_txt)) + break; + + if (2 != sscanf(buf, Osel_window_fmts, &wno, &tot)) + goto end_oops; + if (wno < 0 || wno >= GROUPSMAX) goto end_oops; + if (tot < 0) goto end_oops; + + for (i = 0; i < tot; i++) { + if (!fgets(buf, size, fp)) return 1; + if (1 > sscanf(buf, Osel_filterI_fmt, &ch)) goto end_oops; + if ((p = strchr(buf, '\n'))) *p = '\0'; + if (!(p = strstr(buf, OSEL_FILTER))) goto end_oops; + p += sizeof(OSEL_FILTER) - 1; + if (osel_add(&Winstk[wno], ch, p, 0)) goto end_oops; + } + } + // let's prime that buf for the next guy... + fgets(buf, size, fp); + return 0; + +end_oops: + Rc_questions = 1; + return 1; +} // end: config_osel + + + /* + * A configs_reads *Helper* function responsible for processing + * a configuration file (personal or system-wide default) */ +static const char *configs_file (FILE *fp, const char *name, float *delay) { + char fbuf[LRGBUFSIZ]; + int i, n, tmp_whole, tmp_fract; + const char *p = NULL; + + p = fmtmk(N_fmt(RC_bad_files_fmt), name); + (void)fgets(fbuf, sizeof(fbuf), fp); // ignore eyecatcher + if (6 != fscanf(fp + , "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n" + , &Rc.id, &Rc.mode_altscr, &Rc.mode_irixps, &tmp_whole, &tmp_fract, &i)) { + return p; + } + if (Rc.id < 'a' || Rc.id > RCF_VERSION_ID) + return p; + if (strchr("bcde", Rc.id)) + return p; + if (Rc.mode_altscr < 0 || Rc.mode_altscr > 1) + return p; + if (Rc.mode_irixps < 0 || Rc.mode_irixps > 1) + return p; + if (tmp_whole < 0) + return p; + // you saw that, right? (fscanf stickin' it to 'i') + if (i < 0 || i >= GROUPSMAX) + return p; + Curwin = &Winstk[i]; + // this may be ugly, but it keeps us locale independent... + *delay = (float)tmp_whole + (float)tmp_fract / 1000; + + for (i = 0 ; i < GROUPSMAX; i++) { + static const char *def_flds[] = { DEF_FORMER, JOB_FORMER, MEM_FORMER, USR_FORMER }; + int j, x; + WIN_t *w = &Winstk[i]; + p = fmtmk(N_fmt(RC_bad_entry_fmt), i+1, name); + + if (1 != fscanf(fp, "%3s\tfieldscur=", w->rc.winname)) + return p; + if (Rc.id < RCF_XFORMED_ID) + fscanf(fp, "%s\n", fbuf); + else { + for (j = 0; ; j++) + if (1 != fscanf(fp, "%d", &w->rc.fieldscur[j])) + break; + } + + // be tolerant of missing release 3.3.10 graph modes additions + if (3 > fscanf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d" + ", double_up=%d, combine_cpus=%d, core_types=%d\n" + , &w->rc.winflags, &w->rc.sortindx, &w->rc.maxtasks, &w->rc.graph_cpus, &w->rc.graph_mems + , &w->rc.double_up, &w->rc.combine_cpus, &w->rc.core_types)) + return p; + if (w->rc.sortindx < 0 || w->rc.sortindx >= EU_MAXPFLGS) + return p; + if (w->rc.maxtasks < 0) + return p; + if (w->rc.graph_cpus < 0 || w->rc.graph_cpus > 2) + return p; + if (w->rc.graph_mems < 0 || w->rc.graph_mems > 2) + return p; + if (w->rc.double_up < 0 || w->rc.double_up >= ADJOIN_limit) + return p; + // can't check upper bounds until Cpu_cnt is known + if (w->rc.combine_cpus < 0) + return p; + if (w->rc.core_types < 0 || w->rc.core_types > E_CORES_ONLY) + return p; + + if (4 != fscanf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n" + , &w->rc.summclr, &w->rc.msgsclr, &w->rc.headclr, &w->rc.taskclr)) + return p; + // would prefer to use 'max_colors', but it isn't available yet... + if (w->rc.summclr < 0 || w->rc.summclr > 255) return p; + if (w->rc.msgsclr < 0 || w->rc.msgsclr > 255) return p; + if (w->rc.headclr < 0 || w->rc.headclr > 255) return p; + if (w->rc.taskclr < 0 || w->rc.taskclr > 255) return p; + + switch (Rc.id) { + case 'a': // 3.2.8 (former procps) + // fall through + case 'f': // 3.3.0 thru 3.3.3 (ng) + SETw(w, Show_JRNUMS); + // fall through + case 'g': // from 3.3.4 thru 3.3.8 + if (Rc.id > 'a') scat(fbuf, RCF_PLUS_H); + // fall through + case 'h': // this is release 3.3.9 + w->rc.graph_cpus = w->rc.graph_mems = 0; + // these next 2 are really global, but best documented here + Rc.summ_mscale = Rc.task_mscale = SK_Kb; + // fall through + case 'i': // from 3.3.10 thru 3.3.16 + if (Rc.id > 'a') scat(fbuf, RCF_PLUS_J); + w->rc.double_up = w->rc.combine_cpus = 0; + // fall through + case 'j': // this is release 3.3.17 + if (cfg_xform(w, fbuf, def_flds[i])) + return p; + Rc.tics_scaled = 0; + // fall through + case 'k': // current RCF_VERSION_ID + // fall through + default: + if (mlen(w->rc.fieldscur) < EU_MAXPFLGS) + return p; + for (x = 0; x < EU_MAXPFLGS; x++) { + FLG_t f = FLDget(w, x); + if (f >= EU_MAXPFLGS || f < 0) + return p; + } + break; + } + // ensure there's been no manual alteration of fieldscur + for (n = 0 ; n < EU_MAXPFLGS; n++) { + if (&w->rc.fieldscur[n] != msch(w->rc.fieldscur, w->rc.fieldscur[n], EU_MAXPFLGS)) + return p; + } + } // end: for (GROUPSMAX) + + // any new addition(s) last, for older rcfiles compatibility... + (void)fscanf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n" + , &Rc.fixed_widest, &Rc.summ_mscale, &Rc.task_mscale, &Rc.zero_suppress, &Rc.tics_scaled); + if (Rc.fixed_widest < -1 || Rc.fixed_widest > SCREENMAX) + Rc.fixed_widest = 0; + if (Rc.summ_mscale < 0 || Rc.summ_mscale > SK_Eb) + Rc.summ_mscale = 0; + if (Rc.task_mscale < 0 || Rc.task_mscale > SK_Pb) + Rc.task_mscale = 0; + if (Rc.zero_suppress < 0 || Rc.zero_suppress > 1) + Rc.zero_suppress = 0; + if (Rc.tics_scaled < 0 || Rc.tics_scaled > TICS_AS_LAST) + Rc.tics_scaled = 0; + + // prepare to warn that older top can no longer read rcfile ... + if (Rc.id != RCF_VERSION_ID) + Rc_compatibilty = 1; + + // lastly, let's process any optional glob(s) ... + // (darn, must do osel 1st even though alphabetically 2nd) + fbuf[0] = '\0'; + config_osel(fp, fbuf, sizeof(fbuf)); + config_insp(fp, fbuf, sizeof(fbuf)); + + return NULL; +} // end: configs_file + + + /* + * A configs_reads *Helper* function responsible for ensuring the + * complete path was established, otherwise force the 'W' to fail */ +static int configs_path (const char *const fmts, ...) __attribute__((format(printf,1,2))); +static int configs_path (const char *const fmts, ...) { + int len; + va_list ap; + + va_start(ap, fmts); + len = vsnprintf(Rc_name, sizeof(Rc_name), fmts, ap); + va_end(ap); + if (len <= 0 || (size_t)len >= sizeof(Rc_name)) { + Rc_name[0] = '\0'; + len = 0; + } + return len; +} // end: configs_path + + + /* + * Try reading up to 3 rcfiles + * 1. 'SYS_RCRESTRICT' contains two lines consisting of the secure + * mode switch and an update interval. Its presence limits what + * ordinary users are allowed to do. + * 2. 'Rc_name' contains multiple lines - both global & per window. + * line 1 : an eyecatcher and creating program/alias name + * line 2 : an id, Mode_altcsr, Mode_irixps, Delay_time, Curwin. + * For each of the 4 windows: + * lines a: contains w->winname, fieldscur + * line b: contains w->winflags, sortindx, maxtasks, etc + * line c: contains w->summclr, msgsclr, headclr, taskclr + * global : miscellaneous additional settings + * Any remaining lines are devoted to the optional entries + * supporting the 'Other Filter' and 'Inspect' provisions. + * 3. 'SYS_RCDEFAULTS' system-wide defaults if 'Rc_name' absent + * format is identical to #2 above */ +static void configs_reads (void) { + float tmp_delay = DEF_DELAY; + const char *p, *p_home; + FILE *fp; + + fp = fopen(SYS_RCRESTRICT, "r"); + if (fp) { + char fbuf[SMLBUFSIZ]; + if (fgets(fbuf, sizeof(fbuf), fp)) { // sys rc file, line 1 + Secure_mode = 1; + if (fgets(fbuf, sizeof(fbuf), fp)) // sys rc file, line 2 + sscanf(fbuf, "%f", &Rc.delay_time); + } + fclose(fp); + } + + Rc_name[0] = '\0'; // "fopen() shall fail if pathname is an empty string." + // attempt to use the legacy file first, if we cannot access that file, use + // the new XDG basedir locations (XDG_CONFIG_HOME or HOME/.config) instead. + p_home = getenv("HOME"); + if (!p_home || p_home[0] != '/') { + const struct passwd *const pwd = getpwuid(getuid()); + if (!pwd || !(p_home = pwd->pw_dir) || p_home[0] != '/') { + p_home = NULL; + } + } + if (p_home) + configs_path("%s/.%src", p_home, Myname); + + if (!(fp = fopen(Rc_name, "r"))) { + p = getenv("XDG_CONFIG_HOME"); + // ensure the path we get is absolute, fallback otherwise. + if (!p || p[0] != '/') { + if (!p_home) goto system_default; + p = fmtmk("%s/.config", p_home); + (void)mkdir(p, 0700); + } + if (!configs_path("%s/procps", p)) goto system_default; + (void)mkdir(Rc_name, 0700); + if (!configs_path("%s/procps/%src", p, Myname)) goto system_default; + fp = fopen(Rc_name, "r"); + } + + if (fp) { + p = configs_file(fp, Rc_name, &tmp_delay); + fclose(fp); + if (p) goto default_or_error; + } else { +system_default: + fp = fopen(SYS_RCDEFAULTS, "r"); + if (fp) { + p = configs_file(fp, SYS_RCDEFAULTS, &tmp_delay); + fclose(fp); + if (p) goto default_or_error; + } + } + + // lastly, establish the true runtime secure mode and delay time + if (!getuid()) Secure_mode = 0; + if (!Secure_mode) Rc.delay_time = tmp_delay; + return; + +default_or_error: +#ifdef RCFILE_NOERR +{ RCF_t rcdef = DEF_RCFILE; + int i; + Rc = rcdef; + for (i = 0 ; i < GROUPSMAX; i++) + Winstk[i].rc = Rc.win[i]; +} +#else + error_exit(p); +#endif +} // end: configs_reads + + + /* + * Parse command line arguments. + * Note: it's assumed that the rc file(s) have already been read + * and our job is to see if any of those options are to be + * overridden -- we'll force some on and negate others in our + * best effort to honor the loser's (oops, user's) wishes... */ +static void parse_args (int argc, char **argv) { + static const char sopts[] = "bcd:E:e:Hhin:Oo:p:SsU:u:Vw::1"; + static const struct option lopts[] = { + { "batch-mode", no_argument, NULL, 'b' }, + { "cmdline-toggle", no_argument, NULL, 'c' }, + { "delay", required_argument, NULL, 'd' }, + { "scale-summary-mem", required_argument, NULL, 'E' }, + { "scale-task-mem", required_argument, NULL, 'e' }, + { "threads-show", no_argument, NULL, 'H' }, + { "help", no_argument, NULL, 'h' }, + { "idle-toggle", no_argument, NULL, 'i' }, + { "iterations", required_argument, NULL, 'n' }, + { "list-fields", no_argument, NULL, 'O' }, + { "sort-override", required_argument, NULL, 'o' }, + { "pid", required_argument, NULL, 'p' }, + { "accum-time-toggle", no_argument, NULL, 'S' }, + { "secure-mode", no_argument, NULL, 's' }, + { "filter-any-user", required_argument, NULL, 'U' }, + { "filter-only-euser", required_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "width", optional_argument, NULL, 'w' }, + { "single-cpu-toggle", no_argument, NULL, '1' }, + { NULL, 0, NULL, 0 } + }; + float tmp_delay = FLT_MAX; + int ch; + + while (-1 != (ch = getopt_long(argc, argv, sopts, lopts, NULL))) { + int i; + float tmp; + char *cp = optarg; + +#ifndef GETOPTFIX_NO + /* first, let's plug some awful gaps in the getopt implementation, + especially relating to short options with (optional) arguments! */ + if (!cp && optind < argc && argv[optind][0] != '-') + cp = argv[optind++]; + if (cp) { + if (*cp == '=') ++cp; + /* here, if we're actually accessing argv[argc], we'll rely on + the required NULL delimiter which yields an error_exit next */ + if (*cp == '\0') cp = argv[optind++]; + if (!cp) error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch)); + } +#endif + switch (ch) { + case '1': // ensure behavior identical to run-time toggle + if (CHKw(Curwin, View_CPUNOD)) OFFw(Curwin, View_CPUSUM); + else TOGw(Curwin, View_CPUSUM); + OFFw(Curwin, View_CPUNOD); + SETw(Curwin, View_STATES); + break; + case 'b': + Batch = 1; + break; + case 'c': + TOGw(Curwin, Show_CMDLIN); + break; + case 'd': + if (!mkfloat(cp, &tmp_delay, 0)) + error_exit(fmtmk(N_fmt(BAD_delayint_fmt), cp)); + if (0 > tmp_delay) + error_exit(N_txt(DELAY_badarg_txt)); + continue; + case 'E': + { const char *get = "kmgtpe", *got; + if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1) + error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp)); + Rc.summ_mscale = (int)(got - get); + } continue; + case 'e': + { const char *get = "kmgtp", *got; + if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1) + error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp)); + Rc.task_mscale = (int)(got - get); + } continue; + case 'H': + Thread_mode = 1; + break; + case 'h': + puts(fmtmk(N_fmt(HELP_cmdline_fmt), Myname)); + bye_bye(NULL); + case 'i': + TOGw(Curwin, Show_IDLEPS); + Curwin->rc.maxtasks = 0; + break; + case 'n': + if (!mkfloat(cp, &tmp, 1) || 1.0 > tmp) + error_exit(fmtmk(N_fmt(BAD_niterate_fmt), cp)); + Loops = (int)tmp; + continue; + case 'O': + for (i = 0; i < EU_MAXPFLGS; i++) + puts(N_col(i)); + bye_bye(NULL); + case 'o': + if (*cp == '+') { SETw(Curwin, Qsrt_NORMAL); ++cp; } + else if (*cp == '-') { OFFw(Curwin, Qsrt_NORMAL); ++cp; } + for (i = 0; i < EU_MAXPFLGS; i++) + if (!STRCMP(cp, N_col(i))) break; + if (i == EU_MAXPFLGS) + error_exit(fmtmk(N_fmt(XTRA_badflds_fmt), cp)); + OFFw(Curwin, Show_FOREST); + Curwin->rc.sortindx = i; + continue; + case 'p': + { int pid; char *p; + if (Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt)); + do { + if (Monpidsidx >= MONPIDMAX) + error_exit(fmtmk(N_fmt(LIMIT_exceed_fmt), MONPIDMAX)); + if (1 != sscanf(cp, "%d", &pid) + || strpbrk(cp, "+-.")) + error_exit(fmtmk(N_fmt(BAD_mon_pids_fmt), cp)); + if (!pid) pid = getpid(); + for (i = 0; i < Monpidsidx; i++) + if (Monpids[i] == pid) goto next_pid; + Monpids[Monpidsidx++] = pid; + next_pid: + if (!(p = strchr(cp, ','))) break; + cp = p + 1; + } while (*cp); + } continue; + case 'S': + TOGw(Curwin, Show_CTIMES); + break; + case 's': + Secure_mode = 1; + break; + case 'U': + case 'u': + { const char *errmsg; + if (Monpidsidx || Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt)); + if ((errmsg = user_certify(Curwin, cp, ch))) error_exit(errmsg); + } continue; + case 'V': + puts(fmtmk(N_fmt(VERSION_opts_fmt), Myname, PACKAGE_STRING)); + bye_bye(NULL); + case 'w': + tmp = -1; + if (cp && (!mkfloat(cp, &tmp, 1) || tmp < W_MIN_COL || tmp > SCREENMAX)) + error_exit(fmtmk(N_fmt(BAD_widtharg_fmt), cp)); + Width_mode = (int)tmp; + continue; + default: + /* we'll rely on getopt for any error message while + forcing an EXIT_FAILURE with an empty string ... */ + bye_bye(""); + } // end: switch (ch) +#ifndef GETOPTFIX_NO + if (cp) error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), cp)); +#endif + } // end: while getopt_long + + if (optind < argc) + error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), argv[optind])); + + // fixup delay time, maybe... + if (FLT_MAX > tmp_delay) { + if (Secure_mode) + error_exit(N_txt(DELAY_secure_txt)); + Rc.delay_time = tmp_delay; + } +} // end: parse_args + + + /* + * Establish a robust signals environment */ +static void signals_set (void) { + #ifndef SIGRTMAX // not available on hurd, maybe others too + #define SIGRTMAX 32 + #endif + int i; + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + // with user position preserved through SIGWINCH, we must avoid SA_RESTART + sa.sa_flags = 0; + for (i = SIGRTMAX; i; i--) { + switch (i) { + case SIGHUP: + if (Batch) + sa.sa_handler = SIG_IGN; + else + sa.sa_handler = sig_endpgm; + break; + case SIGALRM: case SIGINT: case SIGPIPE: + case SIGQUIT: case SIGTERM: case SIGUSR1: + case SIGUSR2: + sa.sa_handler = sig_endpgm; + break; + case SIGTSTP: case SIGTTIN: case SIGTTOU: + sa.sa_handler = sig_paused; + break; + case SIGCONT: case SIGWINCH: + sa.sa_handler = sig_resize; + break; + default: + sa.sa_handler = sig_abexit; + break; + case SIGKILL: case SIGSTOP: + // because uncatchable, fall through + case SIGCHLD: // we can't catch this + continue; // when opening a pipe + } + sigaction(i, &sa, NULL); + } +} // end: signals_set + + + /* + * Set up the terminal attributes */ +static void whack_terminal (void) { + static char dummy[] = "dumb"; + struct termios tmptty; + + // the curses part... + if (Batch) { + setupterm(dummy, STDOUT_FILENO, NULL); + return; + } +#ifdef PRETENDNOCAP + setupterm(dummy, STDOUT_FILENO, NULL); +#else + setupterm(NULL, STDOUT_FILENO, NULL); +#endif + // our part... + if (-1 == tcgetattr(STDIN_FILENO, &Tty_original)) + error_exit(N_txt(FAIL_tty_get_txt)); + // ok, haven't really changed anything but we do have our snapshot + Ttychanged = 1; + + // first, a consistent canonical mode for interactive line input + tmptty = Tty_original; + tmptty.c_lflag |= (ECHO | ECHOCTL | ECHOE | ICANON | ISIG); + tmptty.c_lflag &= ~NOFLSH; + tmptty.c_oflag &= ~TAB3; + tmptty.c_iflag |= BRKINT; + tmptty.c_iflag &= ~IGNBRK; +#ifdef TERMIOS_ONLY + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty)) + error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno))); + tcgetattr(STDIN_FILENO, &Tty_tweaked); +#endif + // lastly, a nearly raw mode for unsolicited single keystrokes + tmptty.c_lflag &= ~(ECHO | ECHOCTL | ECHOE | ICANON); + tmptty.c_cc[VMIN] = 1; + tmptty.c_cc[VTIME] = 0; + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty)) + error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno))); + tcgetattr(STDIN_FILENO, &Tty_raw); + +#ifndef OFF_STDIOLBF + // thanks anyway stdio, but we'll manage buffering at the frame level... + setbuffer(stdout, Stdout_buf, sizeof(Stdout_buf)); +#endif +#ifdef OFF_SCROLLBK + // this has the effect of disabling any troublesome scrollback buffer... + if (enter_ca_mode) putp(enter_ca_mode); +#endif + // and don't forget to ask iokey to initialize his tinfo_tab + iokey(IOKEY_INIT); +} // end: whack_terminal + +/*###### Windows/Field Groups support #################################*/ + + /* + * Value a window's name and make the associated group name. */ +static void win_names (WIN_t *q, const char *name) { + /* note: sprintf/snprintf results are "undefined" when src==dst, + according to C99 & POSIX.1-2001 (thanks adc) */ + if (q->rc.winname != name) + snprintf(q->rc.winname, sizeof(q->rc.winname), "%s", name); + snprintf(q->grpname, sizeof(q->grpname), "%d:%s", q->winnum, name); +} // end: win_names + + + /* + * This guy just resets (normalizes) a single window + * and he ensures pid monitoring is no longer active. */ +static void win_reset (WIN_t *q) { + SETw(q, Show_IDLEPS | Show_TASKON); +#ifndef SCROLLVAR_NO + q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->varcolbeg = q->focus_pid = 0; +#else + q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->focus_pid = 0; +#endif + mkVIZoff(q) + osel_clear(q); + q->findstr[0] = '\0'; + q->rc.combine_cpus = 0; + q->rc.core_types = 0; + + // these next guys are global, not really windows based + Monpidsidx = 0; + Rc.tics_scaled = 0; + BOT_TOSS; +} // end: win_reset + + + /* + * Display a window/field group (ie. make it "current"). */ +static WIN_t *win_select (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + /* if there's no ch, it means we're supporting the external interface, + so we must try to get our own darn ch by begging the user... */ + if (!ch) { + show_pmt(N_txt(CHOOSE_group_txt)); + if (1 > (ch = iokey(IOKEY_ONCE))) return w; + } + switch (ch) { + case 'a': // we don't carry 'a' / 'w' in our + w = w->next; // pmt - they're here for a good + break; // friend of ours -- wins_colors. + case 'w': // (however those letters work via + w = w->prev; // the pmt too but gee, end-loser + break; // should just press the darn key) + case '1': case '2' : case '3': case '4': + w = &Winstk[ch - '1']; + break; + default: // keep gcc happy + break; + } + Curwin = w; + return Curwin; +} // end: win_select + + + /* + * Just warn the user when a command can't be honored. */ +static int win_warn (int what) { + switch (what) { + case Warn_ALT: + show_msg(N_txt(DISABLED_cmd_txt)); + break; + case Warn_VIZ: + show_msg(fmtmk(N_fmt(DISABLED_win_fmt), Curwin->grpname)); + break; + default: // keep gcc happy + break; + } + /* we gotta' return false 'cause we're somewhat well known within + macro society, by way of that sassy little tertiary operator... */ + return 0; +} // end: win_warn + + + /* + * Change colors *Helper* function to save/restore settings; + * ensure colors will show; and rebuild the terminfo strings. */ +static void wins_clrhlp (WIN_t *q, int save) { + static int flgssav, summsav, msgssav, headsav, tasksav; + + if (save) { + flgssav = q->rc.winflags; summsav = q->rc.summclr; + msgssav = q->rc.msgsclr; headsav = q->rc.headclr; tasksav = q->rc.taskclr; + SETw(q, Show_COLORS); + } else { + q->rc.winflags = flgssav; q->rc.summclr = summsav; + q->rc.msgsclr = msgssav; q->rc.headclr = headsav; q->rc.taskclr = tasksav; + } + capsmk(q); +} // end: wins_clrhlp + + + /* + * Change colors used in display */ +static void wins_colors (void) { + #define kbdABORT 'q' + #define kbdAPPLY kbd_ENTER + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int clr = w->rc.taskclr, *pclr = &w->rc.taskclr; + char tgt = 'T'; + int key; + + if (0 >= max_colors) { + show_msg(N_txt(COLORS_nomap_txt)); + return; + } + wins_clrhlp(w, 1); + putp((Cursor_state = Cap_curs_huge)); +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + do { + putp(Cap_home); + // this string is well above ISO C89's minimum requirements! + show_special(1, fmtmk(N_unq(COLOR_custom_fmt) + , w->grpname + , CHKw(w, View_NOBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , CHKw(w, Show_COLORS) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , CHKw(w, Show_HIBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , tgt, max_colors, clr, w->grpname)); + putp(Cap_clr_eos); + fflush(stdout); + + if (Frames_signal) goto signify_that; + key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + if (key == kbd_ESC) break; + + switch (key) { + case 'S': + pclr = &w->rc.summclr; + clr = *pclr; + tgt = key; + break; + case 'M': + pclr = &w->rc.msgsclr; + clr = *pclr; + tgt = key; + break; + case 'H': + pclr = &w->rc.headclr; + clr = *pclr; + tgt = key; + break; + case 'T': + pclr = &w->rc.taskclr; + clr = *pclr; + tgt = key; + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + clr = key - '0'; + *pclr = clr; + break; + case kbd_UP: + ++clr; + if (clr >= max_colors) clr = 0; + *pclr = clr; + break; + case kbd_DOWN: + --clr; + if (clr < 0) clr = max_colors - 1; + *pclr = clr; + break; + case 'B': + TOGw(w, View_NOBOLD); + break; + case 'b': + TOGw(w, Show_HIBOLD); + break; + case 'z': + TOGw(w, Show_COLORS); + break; + case 'a': + case 'w': + wins_clrhlp((w = win_select(key)), 1); + clr = w->rc.taskclr, pclr = &w->rc.taskclr; + tgt = 'T'; + break; + default: + break; // keep gcc happy + } + capsmk(w); + } while (key != kbdAPPLY && key != kbdABORT); + + if (key == kbdABORT || key == kbd_ESC) wins_clrhlp(w, 0); + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; + #undef kbdABORT + #undef kbdAPPLY +} // end: wins_colors + + + /* + * Manipulate flag(s) for all our windows. */ +static void wins_reflag (int what, int flg) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + do { + switch (what) { + case Flags_TOG: + TOGw(w, flg); + break; + case Flags_SET: // Ummmm, i can't find anybody + SETw(w, flg); // who uses Flags_set ... + break; + case Flags_OFF: + OFFw(w, flg); + break; + default: // keep gcc happy + break; + } + /* a flag with special significance -- user wants to rebalance + display so we gotta' off some stuff then force on two flags... */ + if (EQUWINS_xxx == flg) + win_reset(w); + + w = w->next; + } while (w != Curwin); +} // end: wins_reflag + + + /* + * Set up the raw/incomplete field group windows -- + * they'll be finished off after startup completes. + * [ and very likely that will override most/all of our efforts ] + * [ --- life-is-NOT-fair --- ] */ +static void wins_stage_1 (void) { + WIN_t *w; + int i; + + for (i = 0; i < GROUPSMAX; i++) { + w = &Winstk[i]; + w->winnum = i + 1; + w->rc = Rc.win[i]; + w->captab[0] = Cap_norm; + w->captab[1] = Cap_norm; + w->captab[2] = w->cap_bold; + w->captab[3] = w->capclr_sum; + w->captab[4] = w->capclr_msg; + w->captab[5] = w->capclr_pmt; + w->captab[6] = w->capclr_hdr; + w->captab[7] = w->capclr_rowhigh; + w->captab[8] = w->capclr_rownorm; + w->next = w + 1; + w->prev = w - 1; + } + + // fixup the circular chains... + Winstk[GROUPSMAX - 1].next = &Winstk[0]; + Winstk[0].prev = &Winstk[GROUPSMAX - 1]; + Curwin = Winstk; + + for (i = 1; i < BOT_MSGSMAX; i++) + Msg_tab[i].prev = &Msg_tab[i - 1]; + Msg_tab[0].prev = &Msg_tab[BOT_MSGSMAX -1]; +} // end: wins_stage_1 + + + /* + * This guy just completes the field group windows after the + * rcfiles have been read and command line arguments parsed. + * And since he's the cabose of startup, he'll also tidy up + * a few final things... */ +static void wins_stage_2 (void) { + int i; + + for (i = 0; i < GROUPSMAX; i++) { + win_names(&Winstk[i], Winstk[i].rc.winname); + capsmk(&Winstk[i]); + Winstk[i].findstr = alloc_c(FNDBUFSIZ); + Winstk[i].findlen = 0; + if (Winstk[i].rc.combine_cpus >= Cpu_cnt) + Winstk[i].rc.combine_cpus = 0; + if (CHKw(&Winstk[i], (View_CPUSUM | View_CPUNOD))) + Winstk[i].rc.double_up = 0; + } + if (!Batch) + putp((Cursor_state = Cap_curs_hide)); + else { + OFFw(Curwin, View_SCROLL); + signal(SIGHUP, SIG_IGN); // allow running under nohup + } + // fill in missing Fieldstab members and build each window's columnhdr + zap_fieldstab(); + + // with preserved 'other filters' & command line 'user filters', + // we must ensure that we always have a visible task on row one. + mkVIZrow1 + + // lastly, initialize a signal set used to throttle one troublesome signal + sigemptyset(&Sigwinch_set); +#ifdef SIGNALS_LESS + sigaddset(&Sigwinch_set, SIGWINCH); +#endif +} // end: wins_stage_2 + + + /* + * Determine if this task matches the 'u/U' selection + * criteria for a given window */ +static inline int wins_usrselect (const WIN_t *q, int idx) { + // a tailored 'results stack value' extractor macro + #define rSv(E) PID_VAL(E, u_int, p) + struct pids_stack *p = q->ppt[idx]; + + switch (q->usrseltyp) { + case 0: // uid selection inactive + return 1; + case 'U': // match any uid + if (rSv(EU_URD) == (unsigned)q->usrseluid) return q->usrselflg; + if (rSv(EU_USD) == (unsigned)q->usrseluid) return q->usrselflg; + if (rSv(eu_ID_FUID) == (unsigned)q->usrseluid) return q->usrselflg; + // fall through... + case 'u': // match effective uid + if (rSv(EU_UED) == (unsigned)q->usrseluid) return q->usrselflg; + // fall through... + default: // no match... + ; + } + return !q->usrselflg; + #undef rSv +} // end: wins_usrselect + +/*###### Forest View support ###########################################*/ + + /* + * We try keeping most existing code unaware of these activities | + * ( plus, maintain alphabetical order within carefully chosen ) | + * ( names beginning forest_a, forest_b, forest_c and forest_d ) | + * ( with each name exactly 1 letter more than its predecessor ) | */ +static struct pids_stack **Seed_ppt; // temporary win ppt pointer | +static struct pids_stack **Tree_ppt; // forest_begin resizes this | +static int Tree_idx; // frame_make resets to zero | + /* those next two support collapse/expand children. the Hide_pid | + array holds parent pids whose children have been manipulated. | + positive pid values represent parents with collapsed children | + while a negative pid value means children have been expanded. | + ( both of these are managed under the 'keys_task()' routine ) | */ +static int *Hide_pid; // collapsible process array | +static int Hide_tot; // total used in above array | + + /* + * This little recursive guy was the real forest view workhorse. | + * He fills in the Tree_ppt array and also sets the child indent | + * level which is stored in an 'extra' result struct as a u_int. | */ +static void forest_adds (const int self, int level) { + // tailored 'results stack value' extractor macros + #define rSv(E,X) PID_VAL(E, s_int, Seed_ppt[X]) + // if xtra-procps-debug.h active, can't use PID_VAL with assignment + #define rSv_Lvl Tree_ppt[Tree_idx]->head[eu_TREE_LVL].result.s_int + int i; + + if (Tree_idx < PIDSmaxt) { // immunize against insanity | + if (level > 100) level = 101; // our arbitrary nests limit | + Tree_ppt[Tree_idx] = Seed_ppt[self]; // add this as root or child | + rSv_Lvl = level; // while recording its level | + ++Tree_idx; +#ifdef TREE_SCANALL + for (i = 0; i < PIDSmaxt; i++) { + if (i == self) continue; +#else + for (i = self + 1; i < PIDSmaxt; i++) { +#endif + if (rSv(EU_PID, self) == rSv(EU_TGD, i) + || (rSv(EU_PID, self) == rSv(EU_PPD, i) && rSv(EU_PID, i) == rSv(EU_TGD, i))) + forest_adds(i, level + 1); // got one child any others? + } + } + #undef rSv + #undef rSv_Lvl +} // end: forest_adds + + + /* + * This function is responsible for making that stacks ptr array | + * a forest display in that designated window. After completion, | + * he'll replace that original window ppt array with a specially | + * ordered forest view version. He'll also mark hidden children! | */ +static void forest_begin (WIN_t *q) { + static int hwmsav; + int i, j; + + Seed_ppt = q->ppt; // avoid passing pointers | + if (!Tree_idx) { // do just once per frame | + if (hwmsav < PIDSmaxt) { // grow, but never shrink | + hwmsav = PIDSmaxt; + Tree_ppt = alloc_r(Tree_ppt, sizeof(void *) * hwmsav); + } + +#ifndef TREE_SCANALL + if (!(procps_pids_sort(Pids_ctx, Seed_ppt, PIDSmaxt + , PIDS_TICS_BEGAN, PIDS_SORT_ASCEND))) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno))); +#endif + for (i = 0; i < PIDSmaxt; i++) { // avoid hidepid distorts | + if (!PID_VAL(eu_TREE_LVL, s_int, Seed_ppt[i])) // parents lvl 0 | + forest_adds(i, 0); // add parents + children | + } + + /* we use up to three additional 'PIDS_extra' results in our stack | + eu_TREE_HID (s_ch) : where 'x' == collapsed & 'z' == unseen | + eu_TREE_LVL (s_int): where level number is stored (0 - 100) | + eu_TREE_ADD (u_int): where a children's tics stored (maybe) | */ + for (i = 0; i < Hide_tot; i++) { + + // if have xtra-procps-debug.h, cannot use PID_VAL w/ assignment | + #define rSv(E,T,X) Tree_ppt[X]->head[E].result.T + #define rSv_Pid(X) rSv(EU_PID, s_int, X) + #define rSv_Lvl(X) rSv(eu_TREE_LVL, s_int, X) + #define rSv_Hid(X) rSv(eu_TREE_HID, s_ch, X) + /* next 2 aren't needed if TREE_VCPUOFF but they cost us nothing | + & the EU_CPU slot will now always be present (even if it's 0) | */ + #define rSv_Add(X) rSv(eu_TREE_ADD, u_int, X) + #define rSv_Cpu(X) rSv(EU_CPU, u_int, X) + + if (Hide_pid[i] > 0) { + for (j = 0; j < PIDSmaxt; j++) { + if (rSv_Pid(j) == Hide_pid[i]) { + int parent = j; + int children = 0; + int level = rSv_Lvl(parent); + while (j+1 < PIDSmaxt && rSv_Lvl(j+1) > level) { + ++j; + rSv_Hid(j) = 'z'; +#ifndef TREE_VCPUOFF + rSv_Add(parent) += rSv_Cpu(j); +#endif + children = 1; + } + /* if any children found (& collapsed) mark the parent | + ( when children aren't found don't negate the pid ) | + ( to prevent future scans since who's to say such ) | + ( tasks will not fork more children in the future ) | */ + if (children) rSv_Hid(parent) = 'x'; + // this will force a check of next Hide_pid[i], if any | + j = PIDSmaxt + 1; + } + } + // if a target task disappeared prevent any further scanning | + if (j == PIDSmaxt) Hide_pid[i] = -Hide_pid[i]; + } + #undef rSv + #undef rSv_Pid + #undef rSv_Lvl + #undef rSv_Hid + #undef rSv_Add + #undef rSv_Cpu + } + } // end: !Tree_idx + memcpy(Seed_ppt, Tree_ppt, sizeof(void *) * PIDSmaxt); +} // end: forest_begin + + + /* + * When there's a 'focus_pid' established for a window, this guy | + * determines that window's 'focus_beg' plus 'focus_end' values. | + * But, if the pid can no longer be found, he'll turn off focus! | */ +static void forest_config (WIN_t *q) { + // tailored 'results stack value' extractor macro + #define rSv(x) PID_VAL(eu_TREE_LVL, s_int, q->ppt[(x)]) + int i, level = 0; + + for (i = 0; i < PIDSmaxt; i++) { + if (q->focus_pid == PID_VAL(EU_PID, s_int, q->ppt[i])) { + level = rSv(i); + q->focus_beg = i; + break; + } + } + if (i == PIDSmaxt) + q->focus_pid = q->begtask = 0; + else { +#ifdef FOCUS_TREE_X + q->focus_lvl = rSv(i); +#endif + while (i+1 < PIDSmaxt && rSv(i+1) > level) + ++i; + q->focus_end = i + 1; // make 'focus_end' a proper fencpost + // watch out for newly forked/cloned tasks 'above' us ... + if (q->begtask < q->focus_beg) { + q->begtask = q->focus_beg; + mkVIZoff(q) + } +#ifdef FOCUS_HARD_Y + // if some task 'above' us ended, try to maintain focus + // ( but allow scrolling when there are many children ) + if (q->begtask > q->focus_beg + && (SCREEN_ROWS > (q->focus_end - q->focus_beg))) { + q->begtask = q->focus_beg; + mkVIZoff(q) + } +#endif + } + #undef rSv +} // end: forest_config + + + /* + * This guy adds the artwork to either 'cmd' or 'cmdline' values | + * if we are in forest view mode otherwise he just returns them. | */ +static inline const char *forest_display (const WIN_t *q, int idx) { + // tailored 'results stack value' extractor macros + #define rSv(E) PID_VAL(E, str, p) + #define rSv_Lvl PID_VAL(eu_TREE_LVL, s_int, p) + #define rSv_Hid PID_VAL(eu_TREE_HID, s_ch, p) +#ifndef SCROLLVAR_NO + static char buf[MAXBUFSIZ]; +#else + static char buf[ROWMINSIZ]; +#endif + struct pids_stack *p = q->ppt[idx]; + const char *which = (CHKw(q, Show_CMDLIN)) ? rSv(eu_CMDLINE) : rSv(EU_CMD); + int level = rSv_Lvl; + +#ifdef FOCUS_TREE_X + if (q->focus_pid) { + if (idx >= q->focus_beg && idx < q->focus_end) + level -= q->focus_lvl; + } +#endif + if (!CHKw(q, Show_FOREST) || level == 0) return which; +#ifndef TREE_VWINALL + if (q == Curwin) // note: the following is NOT indented +#endif + if (rSv_Hid == 'x') { +#ifdef TREE_VALTMRK + snprintf(buf, sizeof(buf), "%*s%s", (4 * level), "`+ ", which); +#else + snprintf(buf, sizeof(buf), "+%*s%s", ((4 * level) - 1), "`- ", which); +#endif + return buf; + } + if (level > 100) { + snprintf(buf, sizeof(buf), "%400s%s", " + ", which); + return buf; + } +#ifndef FOCUS_VIZOFF + if (q->focus_pid) snprintf(buf, sizeof(buf), "|%*s%s", ((4 * level) - 1), "`- ", which); + else snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which); +#else + snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which); +#endif + return buf; + #undef rSv + #undef rSv_Lvl + #undef rSv_Hid +} // end: forest_display + +/*###### Special Separate Bottom Window support ########################*/ + + /* + * This guy actually draws the parsed strings | + * including adding a highlight if necessary. | */ +static void bot_do (const char *str, int focus) { + char *cap = Cap_norm; + + while (*str == ' ') putchar(*(str++)); + if (focus) +#ifdef BOT_STRV_OFF + cap = Cap_reverse; +#else + cap = strchr(str, Bot_sep) ? Curwin->capclr_msg : Cap_reverse; +#endif + putp(fmtmk("%s%s%s", cap, str, Cap_norm)); +} // end: bot_do + + + /* + * This guy draws that bottom window's header | + * then parses/arranges to show the contents. | + * ( returns relative # of elements printed ) | */ +static int bot_focus_str (const char *hdr, const char *str) { + #define maxRSVD ( Screen_rows - 1 ) + char *beg, *end; + char tmp[BIGBUFSIZ]; + int n, x; + + if (str) { + // we're a little careless with overhead here (it's a one time cost) + memset(Bot_buf, '\0', sizeof(Bot_buf)); + n = strlen(str); + if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1; + if (!*str || !strcmp(str, "-")) strcpy(Bot_buf, N_txt(X_BOT_nodata_txt)); + else memccpy(Bot_buf, str, '\0', n); + Bot_rsvd = 1 + BOT_RSVD + ((strlen(Bot_buf) - utf8_delta(Bot_buf)) / Screen_cols); + if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD; + // somewhere down call chain fmtmk() may be used, so we'll old school it + snprintf(tmp, sizeof(tmp), "%s%s%-*s" + , tg2(0, SCREEN_ROWS) + , Curwin->capclr_hdr + , Screen_cols + utf8_delta(hdr) + , hdr); + putp(tmp); + } + // now fmtmk is safe to use ... + putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm)); + + beg = &Bot_buf[0]; + x = BOT_UNFOCUS; + + while (*beg) { + if (!(end = strchr(beg, Bot_sep))) + end = beg + strlen(beg); + if ((n = end - beg) >= sizeof(tmp)) + n = sizeof(tmp) - 1; + memccpy(tmp, beg, '\0', n); + tmp[n] = '\0'; + bot_do(tmp, (++x == Bot_indx)); + if (*(beg += n)) + putchar(*(beg++)); + while (*beg == ' ') putchar(*(beg++)); + } + return x; + #undef maxRSVD +} // end: bot_focus_str + + + /* + * This guy draws that bottom window's header | + * & parses/arranges to show vector contents. | + * ( returns relative # of elements printed ) | */ +static int bot_focus_strv (const char *hdr, const char **strv) { + #define maxRSVD ( Screen_rows - 1 ) + static int nsav; + char tmp[SCREENMAX], *p; + int i, n, x; + + if (strv) { + // we're a little careless with overhead here (it's a one time cost) + memset(Bot_buf, '\0', sizeof(Bot_buf)); + n = (char *)&strv[0] - strv[0]; + if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1; + memcpy(Bot_buf, strv[0], n); + if ((!Bot_buf[0] || !strcmp(Bot_buf, "-")) && n <= sizeof(char *)) + strcpy(Bot_buf, N_txt(X_BOT_nodata_txt)); + for (nsav= 0, p = Bot_buf, x = 0; strv[nsav] != NULL; nsav++) { + p += strlen(strv[nsav]) + 1; + if ((p - Bot_buf) >= sizeof(Bot_buf)) + break; + x += utf8_delta(strv[nsav]); + } + n = (p - Bot_buf) - (x + 1); + Bot_rsvd = 1 + BOT_RSVD + (n / Screen_cols); + if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD; + // somewhere down call chain fmtmk() may be used, so we'll old school it + snprintf(tmp, sizeof(tmp), "%s%s%-*s" + , tg2(0, SCREEN_ROWS) + , Curwin->capclr_hdr + , Screen_cols + utf8_delta(hdr) + , hdr); + putp(tmp); + } + // now fmtmk is safe to use ... + putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm)); + + p = Bot_buf; + x = BOT_UNFOCUS; + + for (i = 0; i < nsav; i++) { + bot_do(p, (++x == Bot_indx)); + p += strlen(p) + 1; + putchar(' '); + } + return x; + #undef maxRSVD +} // end: bot_focus_strv + + +static struct { + enum pflag this; + enum namespace_type that; +} ns_tab[] = { + // careful, cgroup & time were late additions ... + { EU_NS7, PROCPS_NS_CGROUP }, { EU_NS1, PROCPS_NS_IPC }, + { EU_NS2, PROCPS_NS_MNT }, { EU_NS3, PROCPS_NS_NET }, + { EU_NS4, PROCPS_NS_PID }, { EU_NS8, PROCPS_NS_TIME }, + { EU_NS5, PROCPS_NS_USER }, { EU_NS6, PROCPS_NS_UTS } +}; + + + /* + * A helper function that will gather various | + * stuff for display by the bot_item_show guy. | */ +static void *bot_item_hlp (struct pids_stack *p) { + static char buf[BIGBUFSIZ]; + char tmp[SMLBUFSIZ], *b; + struct msg_node *m; + int i; + + switch (Bot_what) { + case BOT_MSG_LOG: + *(b = &buf[0]) = '\0'; + m = Msg_this->prev; + do { + if (m->msg[0]) { + b = scat(b, m->msg); + if (m != Msg_this && m->prev->msg[0]) { + // caller itself may have used fmtmk, so we'll old school it ... + snprintf(tmp, sizeof(tmp), "%c ", BOT_SEP_SMI); + b = scat(b, tmp); + } + } + m = m->prev; + } while (m != Msg_this->prev); + return buf; + case BOT_ITEM_NS: + *(b = &buf[0]) = '\0'; + for (i = 0; i < MAXTBL(ns_tab); i++) { + // caller itself may have used fmtmk, so we'll old school it ... + snprintf(tmp, sizeof(tmp), "%s: %-10lu" + , procps_ns_get_name(ns_tab[i].that) + , PID_VAL(ns_tab[i].this, ul_int, p)); + b = scat(b, tmp); + if (i < (MAXTBL(ns_tab) - 1)) b = scat(b, ", "); + } + return buf; + case eu_CMDLINE_V: + case eu_ENVIRON_V: + return p->head[Bot_item[0]].result.strv; + default: + return p->head[Bot_item[0]].result.str; + } +} // end: bot_item_hlp + + + /* + * This guy manages that bottom margin window | + * which shows various process related stuff. | */ +static void bot_item_show (void) { + #define mkHDR fmtmk(Bot_head, Bot_task, PID_VAL(EU_CMD, str, p)) + struct pids_stack *p; + int i; + + for (i = 0; i < PIDSmaxt; i++) { + p = Curwin->ppt[i]; + if (Bot_task == PID_VAL(EU_PID, s_int, p)) + break; + } + if (i < PIDSmaxt) { + Bot_focus_func(mkHDR, bot_item_hlp(p)); + } +#ifdef BOT_DEAD_ZAP + else + BOT_TOSS; +#else + BOT_KEEP; +#endif + #undef mkHDR +} // end: bot_item_show + + + /* + * This guy can toggle between displaying the | + * bottom window or arranging to turn it off. | */ +static void bot_item_toggle (int what, const char *head, char sep) { + int i; + + // if already targeted, assume user wants to turn it off ... + if (Bot_what == what) { + BOT_TOSS; + } else { + // accommodate transition from larger to smaller window + Bot_rsvd = 0; + switch (what) { + case BOT_ITEM_NS: + for (i = 0; i < MAXTBL(ns_tab); i++) + Bot_item[i] = ns_tab[i].this; + Bot_item[i] = BOT_DELIMIT; + Bot_focus_func = (BOT_f)bot_focus_str; + break; + case eu_CMDLINE_V: + case eu_ENVIRON_V: + Bot_item[0] = what; + Bot_item[1] = BOT_DELIMIT; + Bot_focus_func = (BOT_f)bot_focus_strv; + break; + default: + Bot_item[0] = what; + Bot_item[1] = BOT_DELIMIT; + Bot_focus_func = (BOT_f)bot_focus_str; + break; + } + Bot_sep = sep; + Bot_what = what; + Bot_indx = BOT_UNFOCUS; + Bot_head = (char *)head; + Bot_show_func = bot_item_show; + Bot_task = PID_VAL(EU_PID, s_int, Curwin->ppt[Curwin->begtask]); + } +} // end: bot_item_toggle + +/*###### Interactive Input Tertiary support ############################*/ + + /* + * This section exists so as to offer some function naming freedom + * while also maintaining the strict alphabetical order protocol + * within each section. */ + + /* + * This guy is a *Helper* function serving the following two masters: + * find_string() - find the next match in a given window + * task_show() - highlight all matches currently in-view + * If q->findstr is found in the designated buffer, he returns the + * offset from the start of the buffer, otherwise he returns -1. */ +static inline int find_ofs (const WIN_t *q, const char *buf) { + char *fnd; + + if (q->findstr[0] && (fnd = STRSTR(buf, q->findstr))) + return (int)(fnd - buf); + return -1; +} // end: find_ofs + + + + /* This is currently the only true prototype required by top. + It is placed here, instead of top.h, to avoid one compiler + warning when the top_nls.c source was compiled separately. */ +static const char *task_show (const WIN_t *q, int idx); + +static void find_string (int ch) { + #define reDUX (found) ? N_txt(WORD_another_txt) : "" + static int found; + int i; + + if ('&' == ch && !Curwin->findstr[0]) { + show_msg(N_txt(FIND_no_next_txt)); + return; + } + if ('L' == ch) { + char *str = ioline(N_txt(GET_find_str_txt)); + if (*str == kbd_ESC) return; + snprintf(Curwin->findstr, FNDBUFSIZ, "%s", str); + Curwin->findlen = strlen(Curwin->findstr); + found = 0; + } + if (Curwin->findstr[0]) { + SETw(Curwin, NOPRINT_xxx); + for (i = Curwin->begtask; i < PIDSmaxt; i++) { + const char *row = task_show(Curwin, i); + if (*row && -1 < find_ofs(Curwin, row)) { + found = 1; + if (i == Curwin->begtask) continue; + Curwin->begtask = i; + return; + } + } + show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Curwin->findstr)); + } + #undef reDUX +} // end: find_string + + +static void help_view (void) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + char key = 1; + + putp((Cursor_state = Cap_curs_huge)); +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + show_special(1, fmtmk(N_unq(KEYS_helpbas_fmt) + , PACKAGE_STRING + , w->grpname + , CHKw(w, Show_CTIMES) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , Rc.delay_time + , Secure_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , Secure_mode ? "" : N_unq(KEYS_helpext_fmt))); + putp(Cap_clr_eos); + fflush(stdout); + + if (Frames_signal) goto signify_that; + key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + + switch (key) { + // these keys serve the primary help screen + case kbd_ESC: case 'q': + break; + case '?': case 'h': case 'H': + do { +signify_this: + putp(Cap_clr_scr); + adj_geometry(); + show_special(1, fmtmk(N_unq(WINDOWS_help_fmt) + , w->grpname + , Winstk[0].rc.winname, Winstk[1].rc.winname + , Winstk[2].rc.winname, Winstk[3].rc.winname)); + putp(Cap_clr_eos); + fflush(stdout); + if (Frames_signal || (key = iokey(IOKEY_ONCE)) < 1) + goto signify_this; + else w = win_select(key); + // these keys serve the secondary help screen + } while (key != kbd_ENTER && key != kbd_ESC && key != 'q'); + break; + default: + goto signify_that; + } + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; +} // end: help_view + + +static void other_filters (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + const char *txt, *p; + char *glob; + + switch (ch) { + case 'o': + case 'O': + if (ch == 'o') txt = N_txt(OSEL_casenot_txt); + else txt = N_txt(OSEL_caseyes_txt); + glob = ioline(fmtmk(N_fmt(OSEL_prompts_fmt), w->osel_tot + 1, txt)); + if (*glob == kbd_ESC || *glob == '\0') + return; + if ((p = osel_add(w, ch, glob, 1))) { + show_msg(p); + return; + } + break; + case kbd_CtrlO: + if (VIZCHKw(w)) { + char buf[SCREENMAX], **pp; + struct osel_s *osel; + int i; + + i = 0; + osel = w->osel_1st; + pp = alloc_c((w->osel_tot + 1) * sizeof(char *)); + while (osel && i < w->osel_tot) { + pp[i++] = osel->raw; + osel = osel->nxt; + } + buf[0] = '\0'; + for ( ; i > 0; ) + strncat(buf, fmtmk("%s'%s'", " + " , pp[--i]), sizeof(buf) - (strlen(buf) + 1)); + if (buf[0]) p = buf + strspn(buf, " + "); + else p = N_txt(WORD_noneone_txt); + ioline(fmtmk(N_fmt(OSEL_statlin_fmt), p)); + free(pp); + } + break; + default: // keep gcc happy + break; + } +} // end: other_filters + + +static void write_rcfile (void) { + FILE *fp; + int i, j, n; + + if (Rc_questions) { + show_pmt(N_txt(XTRA_warncfg_txt)); + if ('y' != tolower(iokey(IOKEY_ONCE))) + return; + Rc_questions = 0; + } + if (Rc_compatibilty) { + show_pmt(N_txt(XTRA_warnold_txt)); + if ('y' != tolower(iokey(IOKEY_ONCE))) + return; + Rc_compatibilty = 0; + } + if (!(fp = fopen(Rc_name, "w"))) { + show_msg(fmtmk(N_fmt(FAIL_rc_open_fmt), Rc_name, strerror(errno))); + return; + } + fprintf(fp, "%s's " RCF_EYECATCHER, Myname); + fprintf(fp, "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n" + , RCF_VERSION_ID + , Rc.mode_altscr, Rc.mode_irixps + // this may be ugly, but it keeps us locale independent... + , (int)Rc.delay_time, (int)((Rc.delay_time - (int)Rc.delay_time) * 1000) + , (int)(Curwin - Winstk)); + + for (i = 0 ; i < GROUPSMAX; i++) { + n = mlen(Winstk[i].rc.fieldscur); + fprintf(fp, "%s\tfieldscur=", Winstk[i].rc.winname); + for (j = 0; j < n; j++) { + if (j && !(j % FLD_ROWMAX) && j < n) + fprintf(fp, "\n\t\t "); + fprintf(fp, "%4d ", (int)Winstk[i].rc.fieldscur[j]); + } + fprintf(fp, "\n"); + fprintf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d" + ", double_up=%d, combine_cpus=%d, core_types=%d\n" + , Winstk[i].rc.winflags, Winstk[i].rc.sortindx, Winstk[i].rc.maxtasks + , Winstk[i].rc.graph_cpus, Winstk[i].rc.graph_mems, Winstk[i].rc.double_up + , Winstk[i].rc.combine_cpus, Winstk[i].rc.core_types); + fprintf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n" + , Winstk[i].rc.summclr, Winstk[i].rc.msgsclr + , Winstk[i].rc.headclr, Winstk[i].rc.taskclr); + } + + // any new addition(s) last, for older rcfiles compatibility... + fprintf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n" + , Rc.fixed_widest, Rc.summ_mscale, Rc.task_mscale, Rc.zero_suppress, Rc.tics_scaled); + + if (Winstk[0].osel_tot + Winstk[1].osel_tot + + Winstk[2].osel_tot + Winstk[3].osel_tot) { + fprintf(fp, "\n"); + fprintf(fp, Osel_delim_1_txt); + for (i = 0 ; i < GROUPSMAX; i++) { + struct osel_s *osel = Winstk[i].osel_1st; + if (osel) { + fprintf(fp, Osel_window_fmts, i, Winstk[i].osel_tot); + do { + fprintf(fp, Osel_filterO_fmt, osel->typ, osel->raw); + osel = osel->nxt; + } while (osel); + } + } + fprintf(fp, Osel_delim_2_txt); + } + + if (Inspect.raw && strcmp(Inspect.raw, "\n")) + fputs(Inspect.raw, fp); + + fclose(fp); + show_msg(fmtmk(N_fmt(WRITE_rcfile_fmt), Rc_name)); +} // end: write_rcfile + +/*###### Interactive Input Secondary support (do_key helpers) ##########*/ + + /* + * These routines exist just to keep the do_key() function + * a reasonably modest size. */ + +static void keys_global (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int i, num, def, pid; + + switch (ch) { + case '?': + case 'h': + help_view(); + break; + case 'B': + TOGw(w, View_NOBOLD); + capsmk(w); + break; + case 'd': + case 's': + if (Secure_mode) + show_msg(N_txt(NOT_onsecure_txt)); + else { + float tmp = + get_float(fmtmk(N_fmt(DELAY_change_fmt), Rc.delay_time)); + if (tmp > -1) Rc.delay_time = tmp; + } + break; + case 'E': + if (++Rc.summ_mscale > SK_Eb) Rc.summ_mscale = SK_Kb; + break; + case 'e': + if (++Rc.task_mscale > SK_Pb) Rc.task_mscale = SK_Kb; + break; + case 'f': + fields_utility(); + break; + case 'g': + win_select(0); + break; + case 'H': + Thread_mode = !Thread_mode; + if (!CHKw(w, View_STATES)) + show_msg(fmtmk(N_fmt(THREADS_show_fmt) + , Thread_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt))); + for (i = 0 ; i < GROUPSMAX; i++) + Winstk[i].begtask = Winstk[i].focus_pid = 0; + // force an extra procs refresh to avoid %cpu distortions... + Pseudo_row = PROC_XTRA; + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; + break; + case 'I': + if (Cpu_cnt > 1) { + Rc.mode_irixps = !Rc.mode_irixps; + show_msg(fmtmk(N_fmt(IRIX_curmode_fmt) + , Rc.mode_irixps ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt))); + } else + show_msg(N_txt(NOT_smp_cpus_txt)); + break; + case 'k': + if (Secure_mode) + show_msg(N_txt(NOT_onsecure_txt)); + else { + num = SIGTERM; + def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + pid = get_int(fmtmk(N_txt(GET_pid2kill_fmt), def)); + if (pid > GET_NUM_ESC) { + char *str; + if (pid == GET_NUM_NOT) pid = def; + str = ioline(fmtmk(N_fmt(GET_sigs_num_fmt), pid, SIGTERM)); + if (*str != kbd_ESC) { + if (*str) num = signal_name_to_number(str); + if (Frames_signal) break; + if (0 < num && kill(pid, num)) + show_msg(fmtmk(N_fmt(FAIL_signals_fmt) + , pid, num, strerror(errno))); + else if (0 > num) show_msg(N_txt(BAD_signalid_txt)); + } + } + } + break; + case 'r': + if (Secure_mode) + show_msg(N_txt(NOT_onsecure_txt)); + else { + def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def)); + if (pid > GET_NUM_ESC) { + if (pid == GET_NUM_NOT) pid = def; + num = get_int(fmtmk(N_fmt(GET_nice_num_fmt), pid)); + if (num > GET_NUM_NOT + && setpriority(PRIO_PROCESS, (unsigned)pid, num)) + show_msg(fmtmk(N_fmt(FAIL_re_nice_fmt) + , pid, num, strerror(errno))); + } + } + break; + case 'X': + num = get_int(fmtmk(N_fmt(XTRA_fixwide_fmt), Rc.fixed_widest)); + if (num > GET_NUM_NOT) { + if (num >= 0 && num <= SCREENMAX) Rc.fixed_widest = num; + else Rc.fixed_widest = -1; + } + break; + case 'Y': + if (!Inspect.total) +#ifndef INSP_OFFDEMO + ioline(N_txt(YINSP_noent1_txt)); +#else + ioline(N_txt(YINSP_noent2_txt)); +#endif + else { + def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + pid = get_int(fmtmk(N_fmt(YINSP_pidsee_fmt), def)); + if (pid > GET_NUM_ESC) { + if (pid == GET_NUM_NOT) pid = def; + if (pid) inspection_utility(pid); + } + } + break; + case 'Z': + wins_colors(); + break; + case '0': + Rc.zero_suppress = !Rc.zero_suppress; + break; + case kbd_CtrlE: +#ifndef SCALE_FORMER + Rc.tics_scaled++; + if (Rc.tics_scaled > TICS_AS_LAST) + Rc.tics_scaled = 0; +#endif + break; + case kbd_CtrlG: + bot_item_toggle(EU_CGR, N_fmt(X_BOT_ctlgrp_fmt), BOT_SEP_SLS); + break; + case kbd_CtrlI: + if (BOT_PRESENT) { + ++Bot_indx; + if (Bot_indx > Bot_focus_func(NULL, NULL)) + Bot_indx = BOT_UNFOCUS; + } + break; + case kbd_CtrlK: + // with string vectors, the 'separator' may serve a different purpose + bot_item_toggle(eu_CMDLINE_V, N_fmt(X_BOT_cmdlin_fmt), BOT_SEP_SPC); + break; + case kbd_CtrlL: + bot_item_toggle(BOT_MSG_LOG, N_txt(X_BOT_msglog_txt), BOT_SEP_SMI); + break; + case kbd_CtrlN: + // with string vectors, the 'separator' may serve a different purpose + bot_item_toggle(eu_ENVIRON_V, N_fmt(X_BOT_envirn_fmt), BOT_SEP_SPC); + break; + case kbd_CtrlP: + bot_item_toggle(BOT_ITEM_NS, N_fmt(X_BOT_namesp_fmt), BOT_SEP_CMA); + break; + case kbd_CtrlR: + if (Secure_mode) + show_msg(N_txt(NOT_onsecure_txt)); + else { + def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def)); + if (pid > GET_NUM_ESC) { + int fd; + if (pid == GET_NUM_NOT) pid = def; + num = get_int(fmtmk(N_fmt(AGNI_valueof_fmt), pid)); + if (num > GET_NUM_NOT) { + if (num < -20 || num > +19) + show_msg(N_txt(AGNI_invalid_txt)); + else if (0 > (fd = open(fmtmk("/proc/%d/autogroup", pid), O_WRONLY))) + show_msg(fmtmk(N_fmt(AGNI_notopen_fmt), strerror(errno))); + else { + char buf[TNYBUFSIZ]; + snprintf(buf, sizeof(buf), "%d", num); + if (0 >= write(fd, buf, strlen(buf))) + show_msg(fmtmk(N_fmt(AGNI_nowrite_fmt), strerror(errno))); + close(fd); + } + } + } + } + break; + case kbd_CtrlU: + bot_item_toggle(EU_SGN, N_fmt(X_BOT_supgrp_fmt), BOT_SEP_CMA); + break; + case kbd_BTAB: + if (BOT_PRESENT) { + --Bot_indx; + num = Bot_focus_func(NULL, NULL); + if (Bot_indx <= BOT_UNFOCUS) + Bot_indx = num + 1; + } + break; + case kbd_ENTER: // these two have the effect of waking us + case kbd_SPACE: // from 'pselect', refreshing the display + break; // and updating any hot-plugged resources + default: // keep gcc happy + break; + } +} // end: keys_global + + +static void keys_summary (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + if (Restrict_some && ch != 'C') { + show_msg(N_txt(X_RESTRICTED_txt)); + return; + } + switch (ch) { + case '!': + if (CHKw(w, View_CPUSUM) || CHKw(w, View_CPUNOD)) + show_msg(N_txt(XTRA_modebad_txt)); + else { + if (!w->rc.combine_cpus) w->rc.combine_cpus = 2; + else w->rc.combine_cpus *= 2; + if (w->rc.combine_cpus >= Cpu_cnt) w->rc.combine_cpus = 0; + w->rc.core_types = 0; + } + break; + case '1': + if (CHKw(w, View_CPUNOD)) OFFw(w, View_CPUSUM); + else TOGw(w, View_CPUSUM); + OFFw(w, View_CPUNOD); + SETw(w, View_STATES); + w->rc.double_up = 0; + w->rc.core_types = 0; + break; + case '2': + if (!Numa_node_tot) + show_msg(N_txt(NUMA_nodenot_txt)); + else { + if (Numa_node_sel < 0) TOGw(w, View_CPUNOD); + if (!CHKw(w, View_CPUNOD)) SETw(w, View_CPUSUM); + SETw(w, View_STATES); + Numa_node_sel = -1; + w->rc.double_up = 0; + w->rc.core_types = 0; + } + break; + case '3': + if (!Numa_node_tot) + show_msg(N_txt(NUMA_nodenot_txt)); + else { + int num = get_int(fmtmk(N_fmt(NUMA_nodeget_fmt), Numa_node_tot -1)); + if (num > GET_NUM_NOT) { + if (num >= 0 && num < Numa_node_tot) { + Numa_node_sel = num; + SETw(w, View_CPUNOD | View_STATES); + OFFw(w, View_CPUSUM); + w->rc.double_up = 0; + w->rc.core_types = 0; + } else + show_msg(N_txt(NUMA_nodebad_txt)); + } + } + break; + case '4': + w->rc.double_up += 1; + if ((w->rc.double_up >= ADJOIN_limit) + || ((w->rc.double_up >= Cpu_cnt))) + w->rc.double_up = 0; + if ((w->rc.double_up > 1) + && (!w->rc.graph_cpus)) + w->rc.double_up = 0; + OFFw(w, (View_CPUSUM | View_CPUNOD)); + break; +#ifndef CORE_TYPE_NO + case '5': + if (!CHKw(w, View_STATES) + || ((CHKw(w, View_CPUSUM | View_CPUNOD)) + || ((w->rc.combine_cpus)))) + show_msg(N_txt(XTRA_modebad_txt)); + else { + int scanned; + for (scanned = 0; scanned < Cpu_cnt; scanned++) + if (CPU_VAL(stat_COR_TYP, scanned) == E_CORE) + break; + if (scanned < Cpu_cnt) { + w->rc.core_types += 1; + if (w->rc.core_types > E_CORES_ONLY) + w->rc.core_types = 0; + } else w->rc.core_types = 0; + } + break; +#endif + case 'C': + VIZTOGw(w, View_SCROLL); + break; + case 'l': + TOGw(w, View_LOADAV); + break; + case 'm': + if (!CHKw(w, View_MEMORY)) + SETw(w, View_MEMORY); + else if (++w->rc.graph_mems > 2) { + w->rc.graph_mems = 0; + OFFw(w, View_MEMORY); + } + break; + case 't': + if (!CHKw(w, View_STATES)) + SETw(w, View_STATES); + else if (++w->rc.graph_cpus > 2) { + w->rc.graph_cpus = 0; + OFFw(w, View_STATES); + } + if ((w->rc.double_up > 1) + && (!w->rc.graph_cpus)) + w->rc.double_up = 0; + break; + default: // keep gcc happy + break; + } +} // end: keys_summary + + +static void keys_task (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + switch (ch) { + case '#': + case 'n': + if (VIZCHKw(w)) { + int num = get_int(fmtmk(N_fmt(GET_max_task_fmt), w->rc.maxtasks)); + if (num > GET_NUM_NOT) { + if (-1 < num ) w->rc.maxtasks = num; + else show_msg(N_txt(BAD_max_task_txt)); + } + } + break; + case '<': +#ifdef TREE_NORESET + if (CHKw(w, Show_FOREST)) break; +#endif + if (VIZCHKw(w)) { + FLG_t *p = w->procflgs + w->maxpflgs - 1; + while (p > w->procflgs && *p != w->rc.sortindx) --p; + if (*p == w->rc.sortindx) { + --p; +#ifndef USE_X_COLHDR + if (EU_MAXPFLGS < *p) --p; +#endif + if (p >= w->procflgs) { + w->rc.sortindx = *p; +#ifndef TREE_NORESET + OFFw(w, Show_FOREST); +#endif + } + } + } + break; + case '>': +#ifdef TREE_NORESET + if (CHKw(w, Show_FOREST)) break; +#endif + if (VIZCHKw(w)) { + FLG_t *p = w->procflgs + w->maxpflgs - 1; + while (p > w->procflgs && *p != w->rc.sortindx) --p; + if (*p == w->rc.sortindx) { + ++p; +#ifndef USE_X_COLHDR + if (EU_MAXPFLGS < *p) ++p; +#endif + if (p < w->procflgs + w->maxpflgs) { + w->rc.sortindx = *p; +#ifndef TREE_NORESET + OFFw(w, Show_FOREST); +#endif + } + } + } + break; + case 'b': + TOGw(w, Show_HIBOLD); + capsmk(w); + break; + case 'c': + VIZTOGw(w, Show_CMDLIN); + break; + case 'F': + if (VIZCHKw(w)) { + if (CHKw(w, Show_FOREST)) { + int n = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + if (w->focus_pid == n) w->focus_pid = 0; + else w->focus_pid = n; + } + } + break; + case 'i': + { static WIN_t *w_sav; + static int beg_sav; + if (w_sav != w) { beg_sav = 0; w_sav = w; } + if (CHKw(w, Show_IDLEPS)) { beg_sav = w->begtask; w->begtask = 0; } + else { w->begtask = beg_sav; beg_sav = 0; } + } + VIZTOGw(w, Show_IDLEPS); + break; + case 'J': + VIZTOGw(w, Show_JRNUMS); + break; + case 'j': + VIZTOGw(w, Show_JRSTRS); + break; + case 'R': +#ifdef TREE_NORESET + if (!CHKw(w, Show_FOREST)) VIZTOGw(w, Qsrt_NORMAL); +#else + if (VIZCHKw(w)) { + TOGw(w, Qsrt_NORMAL); + OFFw(w, Show_FOREST); + } +#endif + break; + case 'S': + if (VIZCHKw(w)) { + TOGw(w, Show_CTIMES); + show_msg(fmtmk(N_fmt(TIME_accumed_fmt) , CHKw(w, Show_CTIMES) + ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt))); + } + break; + case 'O': + case 'o': + case kbd_CtrlO: + if (VIZCHKw(w)) other_filters(ch); + break; + case 'U': + case 'u': + if (VIZCHKw(w)) { + const char *errmsg, *str = ioline(N_txt(GET_user_ids_txt)); + if (*str != kbd_ESC + && (errmsg = user_certify(w, str, ch))) + show_msg(errmsg); + } + break; + case 'V': + if (VIZCHKw(w)) { + TOGw(w, Show_FOREST); + if (!ENUviz(w, EU_CMD)) + show_msg(fmtmk(N_fmt(FOREST_modes_fmt) , CHKw(w, Show_FOREST) + ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt))); + if (!CHKw(w, Show_FOREST)) w->focus_pid = 0; + } + break; + case 'v': + if (VIZCHKw(w)) { + if (CHKw(w, Show_FOREST)) { + int i, pid = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); +#ifdef TREE_VPROMPT + int got = get_int(fmtmk(N_txt(XTRA_vforest_fmt), pid)); + if (got < GET_NUM_NOT) break; + if (got > GET_NUM_NOT) pid = got; +#endif + for (i = 0; i < Hide_tot; i++) { + if (Hide_pid[i] == pid || Hide_pid[i] == -pid) { + Hide_pid[i] = -Hide_pid[i]; + break; + } + } + if (i == Hide_tot) { + static int totsav; + if (Hide_tot >= totsav) { + totsav += 128; + Hide_pid = alloc_r(Hide_pid, sizeof(int) * totsav); + } + Hide_pid[Hide_tot++] = pid; + } else { + // if everything's expanded, let's empty the array ... + for (i = 0; i < Hide_tot; i++) + if (Hide_pid[i] > 0) break; + if (i == Hide_tot) Hide_tot = 0; + } + } + } + break; + case 'x': + if (VIZCHKw(w)) { +#ifdef USE_X_COLHDR + TOGw(w, Show_HICOLS); + capsmk(w); +#else + if (ENUviz(w, w->rc.sortindx)) { + TOGw(w, Show_HICOLS); + if (ENUpos(w, w->rc.sortindx) < w->begpflg) { + if (CHKw(w, Show_HICOLS)) w->begpflg += 2; + else w->begpflg -= 2; + if (0 > w->begpflg) w->begpflg = 0; + } + capsmk(w); + } +#endif + } + break; + case 'y': + if (VIZCHKw(w)) { + TOGw(w, Show_HIROWS); + capsmk(w); + } + break; + case 'z': + if (VIZCHKw(w)) { + TOGw(w, Show_COLORS); + capsmk(w); + } + break; + default: // keep gcc happy + break; + } +} // end: keys_task + + +static void keys_window (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + switch (ch) { + case '+': + if (ALTCHKw) wins_reflag(Flags_OFF, EQUWINS_xxx); + Hide_tot = 0; + break; + case '-': + if (ALTCHKw) TOGw(w, Show_TASKON); + break; + case '=': + win_reset(w); + Hide_tot = 0; + break; + case '_': + if (ALTCHKw) wins_reflag(Flags_TOG, Show_TASKON); + break; + case '&': + case 'L': + if (VIZCHKw(w)) find_string(ch); + break; + case 'A': + Rc.mode_altscr = !Rc.mode_altscr; + break; + case 'a': + case 'w': + if (ALTCHKw) win_select(ch); + break; + case 'G': + if (ALTCHKw) { + char tmp[SMLBUFSIZ]; + STRLCPY(tmp, ioline(fmtmk(N_fmt(NAME_windows_fmt), w->rc.winname))); + if (tmp[0] && tmp[0] != kbd_ESC) win_names(w, tmp); + } + break; + case kbd_UP: + if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(-1) + break; + case kbd_DOWN: + if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(+1) + break; +#ifdef USE_X_COLHDR // ------------------------------------ + case kbd_LEFT: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) { + if (VARleft(w)) + w->varcolbeg -= SCROLLAMT; + else if (0 < w->begpflg) + w->begpflg -= 1; + } +#else + if (VIZCHKw(w)) if (0 < w->begpflg) w->begpflg -= 1; +#endif + break; + case kbd_RIGHT: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) { + if (VARright(w)) { + w->varcolbeg += SCROLLAMT; + if (0 > w->varcolbeg) w->varcolbeg = 0; + } else if (w->begpflg + 1 < w->totpflgs) + w->begpflg += 1; + } +#else + if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) w->begpflg += 1; +#endif + break; +#else // USE_X_COLHDR ------------------------------------ + case kbd_LEFT: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) { + if (VARleft(w)) + w->varcolbeg -= SCROLLAMT; + else if (0 < w->begpflg) { + w->begpflg -= 1; + if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2; + } + } +#else + if (VIZCHKw(w)) if (0 < w->begpflg) { + w->begpflg -= 1; + if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2; + } +#endif + break; + case kbd_RIGHT: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) { + if (VARright(w)) { + w->varcolbeg += SCROLLAMT; + if (0 > w->varcolbeg) w->varcolbeg = 0; + } else if (w->begpflg + 1 < w->totpflgs) { + if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) + w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0; + else w->begpflg += 1; + } + } +#else + if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) { + if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) + w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0; + else w->begpflg += 1; + } +#endif + break; +#endif // USE_X_COLHDR ------------------------------------ + case kbd_PGUP: + if (VIZCHKw(w)) { + if (CHKw(w, Show_IDLEPS) && 0 < w->begtask) { + mkVIZrowX(-(w->winlines - (Rc.mode_altscr ? 1 : 2))) + } + } + break; + case kbd_PGDN: + if (VIZCHKw(w)) { + if (CHKw(w, Show_IDLEPS) && w->begtask < PIDSmaxt - 1) { + mkVIZrowX(+(w->winlines - (Rc.mode_altscr ? 1 : 2))) + } + } + break; + case kbd_HOME: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = w->varcolbeg = 0; +#else + if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = 0; +#endif + break; + case kbd_END: + if (VIZCHKw(w)) { + if (CHKw(w, Show_IDLEPS)) { + mkVIZrowX((PIDSmaxt - w->winlines) + 1) + w->begpflg = w->endpflg; +#ifndef SCROLLVAR_NO + w->varcolbeg = 0; +#endif + } + } + break; + default: // keep gcc happy + break; + } +} // end: keys_window + + +static void keys_xtra (int ch) { +// const char *xmsg; + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + +#ifdef TREE_NORESET + if (CHKw(w, Show_FOREST)) return; +#else + OFFw(w, Show_FOREST); +#endif + /* these keys represent old-top compatibility -- + they're grouped here so that if users could ever be weaned, + we would just whack do_key's key_tab entry and this function... */ + switch (ch) { + case 'M': + w->rc.sortindx = EU_MEM; +// xmsg = "Memory"; + break; + case 'N': + w->rc.sortindx = EU_PID; +// xmsg = "Numerical"; + break; + case 'P': + w->rc.sortindx = EU_CPU; +// xmsg = "CPU"; + break; + case 'T': + w->rc.sortindx = EU_TM2; +// xmsg = "Time"; + break; + default: // keep gcc happy + break; + } +// some have objected to this message, so we'll just keep silent... +// show_msg(fmtmk("%s sort compatibility key honored", xmsg)); +} // end: keys_xtra + +/*###### Tertiary summary display support (summary_show helpers) #######*/ + + /* + * note how alphabetical order is maintained within carefully chosen | + * function names such as: (s)sum_see, (t)sum_tics, and (u)sum_unify | + * with every name exactly 1 letter more than the preceding function | + * ( surely, this must make us run much more efficiently. amirite? ) | */ + +struct rx_st { + float pcnt_one, pcnt_two, pcnt_tot; + char graph[MEDBUFSIZ]; +}; + + /* + * A *Helper* function to produce the actual cpu & memory graphs for | + * these functions -- sum_tics (tertiary) and do_memory (secondary). | + * (sorry about the name, but it keeps the above comment commitment) | */ +static struct rx_st *sum_rx (struct graph_parms *these) { + static struct { + const char *part1, *part2, *style; + } gtab[] = { + { "%-.*s~7", "%-.*s~8", Graph_bars }, + { "%-.*s~4", "%-.*s~6", Graph_blks } + }; + static __thread struct rx_st rx; + char buf1[SMLBUFSIZ], buf2[SMLBUFSIZ], buf3[MEDBUFSIZ]; + int ix, num1, num2, width; + float scale = 0.0; + + if (these->total > 0) + scale = 100.0 / these->total; + rx.pcnt_one = scale * these->part1; + rx.pcnt_two = scale * these->part2; + if (rx.pcnt_one + rx.pcnt_two > 100.0 || rx.pcnt_two < 0) + rx.pcnt_two = 0; + rx.pcnt_tot = rx.pcnt_one + rx.pcnt_two; + + num1 = (int)((rx.pcnt_one * these->adjust) + .5); + num2 = (int)((rx.pcnt_two * these->adjust) + .5); + if (num1 + num2 > these->length) { + if (num1 > these->length) num1 = these->length; + num2 = these->length - num1; + } + + width = these->length; + buf1[0] = buf2[0] = buf3[0] = '\0'; + ix = these->style - 1; // now relative to zero + if (num1) { + snprintf(buf1, sizeof(buf1), gtab[ix].part1, num1, gtab[ix].style); + width += 2; + } + if (num2) { + snprintf(buf2, sizeof(buf2), gtab[ix].part2, num2, gtab[ix].style); + width += 2; + } + snprintf(buf3, sizeof(buf3), "%s%s", buf1, buf2); + // 'width' has accounted for any show_special directives embedded above + snprintf(rx.graph, sizeof(rx.graph), "[~1%-*.*s] ~1", width, width, buf3); + + return ℞ +} // end: sum_rx + + + /* + * A *Helper* function to show multiple lines of summary information | + * as a single line. We return the number of lines actually printed. | */ +static inline int sum_see (const char *str, int nobuf) { + static char row[ROWMAXSIZ]; + static int tog; + char *p; + + p = scat(row, str); + if (!str[0]) goto flush_it; + if (Curwin->rc.double_up + && (!nobuf)) { + if (++tog <= Curwin->rc.double_up) { + scat(p, Adjoin_sp); + return 0; + } + } +flush_it: + if (!row[0]) return 0; + scat(p, "\n"); + show_special(0, row); + row[0] = '\0'; + tog = 0; + return 1; +} // end: sum_see + + + /* + * State display *Helper* function to calculate plus display (maybe) | + * the percentages for a single cpu. In this way, we'll support the | + * following environments without (hopefully) that usual code bloat: | + * 1) single cpu platforms (no matter the paucity of these types) | + * 2) modest smp boxes with ample room for each cpu's percentages | + * 3) massive smp guys leaving little or no room for that process | + * display and thus requiring the '1', '4', or '!' cpu toggles | + * ( we return the number of lines printed, as reported by sum_see ) | */ +static int sum_tics (struct stat_stack *this, const char *pfx, int nobuf) { + // tailored 'results stack value' extractor macros + #define qSv(E) STAT_VAL(E, s_int, this, Stat_ctx) + #define rSv(E) TIC_VAL(E, this) + SIC_t idl_frme, tot_frme; + struct rx_st *rx; + float scale; + +#ifndef CORE_TYPE_NO + if (Curwin->rc.core_types == P_CORES_ONLY && qSv(stat_COR_TYP) != P_CORE) return 0; + if (Curwin->rc.core_types == E_CORES_ONLY && qSv(stat_COR_TYP) != E_CORE) return 0; +#endif + idl_frme = rSv(stat_IL); + tot_frme = rSv(stat_SUM_TOT); + if (1 > tot_frme) idl_frme = tot_frme = 1; + scale = 100.0 / (float)tot_frme; + + /* account for VM tics not otherwise provided for ... + ( with xtra-procps-debug.h, can't use PID_VAL w/ assignment ) */ + this->head[stat_SY].result.sl_int += rSv(stat_GU) + rSv(stat_GN); + this->head[stat_SUM_SYS].result.sl_int += rSv(stat_GU) + rSv(stat_GN); + + /* display some kinda' cpu state percentages + (who or what is explained by the passed prefix) */ + if (Curwin->rc.graph_cpus) { + Graph_cpus->total = tot_frme; + Graph_cpus->part1 = rSv(stat_SUM_USR); + Graph_cpus->part2 = rSv(stat_SUM_SYS); + rx = sum_rx(Graph_cpus); + if (Curwin->rc.double_up > 1) + return sum_see(fmtmk("%s~3%3.0f%s", pfx, rx->pcnt_tot, rx->graph), nobuf); + else { + return sum_see(fmtmk("%s ~3%#5.1f~2/%-#5.1f~3 %3.0f%s" + , pfx, rx->pcnt_one, rx->pcnt_two, rx->pcnt_tot + , rx->graph) + , nobuf); + } + } else { + return sum_see(fmtmk(Cpu_States_fmts, pfx + , (float)rSv(stat_US) * scale, (float)rSv(stat_SY) * scale + , (float)rSv(stat_NI) * scale, (float)idl_frme * scale + , (float)rSv(stat_IO) * scale, (float)rSv(stat_IR) * scale + , (float)rSv(stat_SI) * scale, (float)rSv(stat_ST) * scale), nobuf); + } + #undef qSv + #undef rSv +} // end: sum_tics + + + /* + * Cpu *Helper* function to combine additional cpu statistics in our | + * efforts to reduce the total number of processors that'll be shown | + * ( we return the number of lines printed, as reported by sum_see ) | */ +static int sum_unify (struct stat_stack *this, int nobuf) { + // a tailored 'results stack value' extractor macro + #define rSv(E,T) STAT_VAL(E, T, this, Stat_ctx) + static struct stat_result stack[MAXTBL(Stat_items)]; + static struct stat_stack accum = { &stack[0] }; + static int ix, beg; + char pfx[16]; + int n; + + // entries for stat_ID & stat_NU are unused + stack[stat_US].result.sl_int += rSv(stat_US, sl_int); + stack[stat_SY].result.sl_int += rSv(stat_SY, sl_int); + stack[stat_NI].result.sl_int += rSv(stat_NI, sl_int); + stack[stat_IL].result.sl_int += rSv(stat_IL, sl_int); + stack[stat_IO].result.sl_int += rSv(stat_IO, sl_int); + stack[stat_IR].result.sl_int += rSv(stat_IR, sl_int); + stack[stat_SI].result.sl_int += rSv(stat_SI, sl_int); + stack[stat_ST].result.sl_int += rSv(stat_ST, sl_int); + stack[stat_GU].result.sl_int += rSv(stat_GU, sl_int); + stack[stat_GN].result.sl_int += rSv(stat_GN, sl_int); + stack[stat_SUM_USR].result.sl_int += rSv(stat_SUM_USR, sl_int); + stack[stat_SUM_SYS].result.sl_int += rSv(stat_SUM_SYS, sl_int); + stack[stat_SUM_TOT].result.sl_int += rSv(stat_SUM_TOT, sl_int); + + if (!ix) beg = rSv(stat_ID, s_int); + if (nobuf || ix >= (Curwin->rc.combine_cpus - 1)) { + snprintf(pfx, sizeof(pfx), "%-7.7s:", fmtmk("%d-%d", beg, rSv(stat_ID, s_int))); + n = sum_tics(&accum, pfx, nobuf); + memset(&stack, 0, sizeof(stack)); + ix = 0; + return n; + } + ++ix; + return 0; + #undef rSv +} // end: sum_unify + +/*###### Secondary summary display support (summary_show helpers) ######*/ + + /* + * A helper function that displays cpu and/or numa node stuff | + * ( so as to keep the 'summary_show' guy a reasonable size ) | */ +static void do_cpus (void) { + #define noMAS (Msg_row + 1 >= SCREEN_ROWS - 1) + #define eachCPU(x) N_fmt(WORD_eachcpu_fmt), 'u', x + char tmp[MEDBUFSIZ]; + int i; + + if (CHKw(Curwin, View_CPUNOD)) { + if (Numa_node_sel < 0) { +numa_oops: + /* + * display the 1st /proc/stat line, then the nodes (if room) ... */ + Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1); + // display each cpu node's states + for (i = 0; i < Numa_node_tot; i++) { + struct stat_stack *nod_ptr = Stat_reap->numa->stacks[i]; + if (NOD_VAL(stat_NU, i) == STAT_NODE_INVALID) continue; + if (noMAS) break; + snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), NOD_VAL(stat_ID, i)); + Msg_row += sum_tics(nod_ptr, tmp, 1); + } + } else { + /* + * display the node summary, then the associated cpus (if room) ... */ + for (i = 0; i < Numa_node_tot; i++) { + if (Numa_node_sel == NOD_VAL(stat_ID, i) + && (NOD_VAL(stat_NU, i) != STAT_NODE_INVALID)) break; + } + if (i == Numa_node_tot) { + Numa_node_sel = -1; + goto numa_oops; + } + snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), Numa_node_sel); + Msg_row += sum_tics(Stat_reap->numa->stacks[Numa_node_sel], tmp, 1); +#ifdef PRETEND48CPU + #define deLIMIT Stat_reap->cpus->total +#else + #define deLIMIT Cpu_cnt +#endif + for (i = 0; i < deLIMIT; i++) { + if (Numa_node_sel == CPU_VAL(stat_NU, i)) { + if (noMAS) break; + snprintf(tmp, sizeof(tmp), eachCPU(CPU_VAL(stat_ID, i))); + Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, 1); + } + } + #undef deLIMIT + } + + } else if (!CHKw(Curwin, View_CPUSUM)) { + /* + * display each cpu's states separately, screen height permitting ... */ +#ifdef PRETEND48CPU + int j; + if (Curwin->rc.combine_cpus) { + for (i = 0, j = 0; i < Cpu_cnt; i++) { + Stat_reap->cpus->stacks[j]->head[stat_ID].result.s_int = i; + Msg_row += sum_unify(Stat_reap->cpus->stacks[j], (i+1 >= Cpu_cnt)); + if (++j >= Stat_reap->cpus->total) j = 0; + if (noMAS) break; + } + } else { + for (i = 0, j = 0; i < Cpu_cnt; i++) { + snprintf(tmp, sizeof(tmp), eachCPU(i)); + Msg_row += sum_tics(Stat_reap->cpus->stacks[j], tmp, (i+1 >= Cpu_cnt)); + if (++j >= Stat_reap->cpus->total) j = 0; + if (noMAS) break; + } + } +#else + if (Curwin->rc.combine_cpus) { + for (i = 0; i < Cpu_cnt; i++) { + Msg_row += sum_unify(Stat_reap->cpus->stacks[i], (i+1 >= Cpu_cnt)); + if (noMAS) break; + } + } else { + for (i = 0; i < Cpu_cnt; i++) { +#ifndef CORE_TYPE_NO + #ifdef CORE_TYPE_LO + char ctab[] = { 'u', 'e', 'p' }; + #else + char ctab[] = { 'u', 'E', 'P' }; + #endif + int cid = CPU_VAL(stat_ID, i), typ = CPU_VAL(stat_COR_TYP, i); + char chr = Curwin->rc.core_types ? ctab[typ] : 'u' ; + snprintf(tmp, sizeof(tmp), N_fmt(WORD_eachcpu_fmt), chr, cid); +#else + snprintf(tmp, sizeof(tmp), eachCPU(CPU_VAL(stat_ID, i))); +#endif + Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, (i+1 >= Cpu_cnt)); + if (noMAS) break; + } + } +#endif + + } else { + /* + * display just the 1st /proc/stat line ... */ + Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1); + } + + // coax this guy into flushing any pending cpu stuff ... + Msg_row += sum_see("", 1); + #undef noMAS + #undef eachCPU +} // end: do_cpus + + + /* + * A helper function which will display the memory/swap stuff | + * ( so as to keep the 'summary_show' guy a reasonable size ) | */ +static void do_memory (void) { + #define bfT(n) buftab[n].buf + #define scT(e) scaletab[Rc.summ_mscale]. e + #define mkM(x) (float) x / scT(div) + #define prT(b,z) { if (9 < snprintf(b, 10, scT(fmts), z)) b[8] = '+'; } +#ifdef TOG4_MEM_1UP + #define mem2UP 1 +#else + #define mem2UP 0 +#endif + static struct { + float div; + const char *fmts; + const char *label; + } scaletab[] = { + { 1, "%.0f ", NULL }, // kibibytes +#ifdef BOOST_MEMORY + { 1024.0, "%#.3f ", NULL }, // mebibytes + { 1024.0*1024, "%#.3f ", NULL }, // gibibytes + { 1024.0*1024*1024, "%#.3f ", NULL }, // tebibytes + { 1024.0*1024*1024*1024, "%#.3f ", NULL }, // pebibytes + { 1024.0*1024*1024*1024*1024, "%#.3f ", NULL } // exbibytes +#else + { 1024.0, "%#.1f ", NULL }, // mebibytes + { 1024.0*1024, "%#.1f ", NULL }, // gibibytes + { 1024.0*1024*1024, "%#.1f ", NULL }, // tebibytes + { 1024.0*1024*1024*1024, "%#.1f ", NULL }, // pebibytes + { 1024.0*1024*1024*1024*1024, "%#.1f ", NULL } // exbibytes +#endif + }; + struct { // 0123456789 + // snprintf contents of each buf (after SK_Kb): 'nnnn.nnn 0' + // & prT macro might replace space at buf[8] with: -------> + + char buf[10]; // MEMORY_lines_fmt provides for 8+1 bytes + } buftab[8]; + char row[ROWMINSIZ]; + long my_qued, my_misc, my_used; + struct rx_st *rx; + + if (!scaletab[0].label) { + scaletab[0].label = N_txt(AMT_kilobyte_txt); + scaletab[1].label = N_txt(AMT_megabyte_txt); + scaletab[2].label = N_txt(AMT_gigabyte_txt); + scaletab[3].label = N_txt(AMT_terabyte_txt); + scaletab[4].label = N_txt(AMT_petabyte_txt); + scaletab[5].label = N_txt(AMT_exxabyte_txt); + } + my_qued = MEM_VAL(mem_BUF) + MEM_VAL(mem_QUE); + + if (Curwin->rc.graph_mems) { + my_used = MEM_VAL(mem_TOT) - MEM_VAL(mem_FRE) - my_qued; +#ifdef MEMGRAPH_OLD + my_misc = my_qued; +#else + my_misc = MEM_VAL(mem_TOT) - MEM_VAL(mem_AVL) - my_used; +#endif + Graph_mems->total = MEM_VAL(mem_TOT); + Graph_mems->part1 = my_used; + Graph_mems->part2 = my_misc; + rx = sum_rx(Graph_mems); +#ifdef TOG4_MEM_1UP + prT(bfT(0), mkM(MEM_VAL(mem_TOT))); + snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s" + , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0), rx->graph); +#else + if (Curwin->rc.double_up > 1) + snprintf(row, sizeof(row), "%s %s~3%3.0f%s" + , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, rx->graph); + else { + prT(bfT(0), mkM(MEM_VAL(mem_TOT))); + snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s" + , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0), rx->graph); + } +#endif + Msg_row += sum_see(row, mem2UP); + + Graph_mems->total = MEM_VAL(swp_TOT); + Graph_mems->part1 = 0; + Graph_mems->part2 = MEM_VAL(swp_USE); + rx = sum_rx(Graph_mems); +#ifdef TOG4_MEM_1UP + prT(bfT(1), mkM(MEM_VAL(swp_TOT))); + snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s" + , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph); +#else + if (Curwin->rc.double_up > 1) + snprintf(row, sizeof(row), "%s %s~3%3.0f%s" + , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, rx->graph); + else { + prT(bfT(1), mkM(MEM_VAL(swp_TOT))); + snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s" + , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph); + } +#endif + Msg_row += sum_see(row, 1); + + } else { + prT(bfT(0), mkM(MEM_VAL(mem_TOT))); prT(bfT(1), mkM(MEM_VAL(mem_FRE))); + prT(bfT(2), mkM(MEM_VAL(mem_USE))); prT(bfT(3), mkM(my_qued)); + prT(bfT(4), mkM(MEM_VAL(swp_TOT))); prT(bfT(5), mkM(MEM_VAL(swp_FRE))); + prT(bfT(6), mkM(MEM_VAL(swp_USE))); prT(bfT(7), mkM(MEM_VAL(mem_AVL))); + + snprintf(row, sizeof(row), N_unq(MEMORY_line1_fmt) + , scT(label), N_txt(WORD_abv_mem_txt), bfT(0), bfT(1), bfT(2), bfT(3)); + Msg_row += sum_see(row, mem2UP); + + snprintf(row, sizeof(row), N_unq(MEMORY_line2_fmt) + , scT(label), N_txt(WORD_abv_swp_txt), bfT(4), bfT(5), bfT(6), bfT(7) + , N_txt(WORD_abv_mem_txt)); + Msg_row += sum_see(row, 1); + } + #undef bfT + #undef scT + #undef mkM + #undef prT + #undef mem2UP +} // end: do_memory + +/*###### Main Screen routines ##########################################*/ + + /* + * Process keyboard input during the main loop */ +static void do_key (int ch) { + static struct { + void (*func)(int ch); + char keys[SMLBUFSIZ]; + } key_tab[] = { + { keys_global, + { '?', 'B', 'd', 'E', 'e', 'f', 'g', 'H', 'h' + , 'I', 'k', 'r', 's', 'X', 'Y', 'Z', '0' + , kbd_CtrlE, kbd_CtrlG, kbd_CtrlI, kbd_CtrlK, kbd_CtrlL + , kbd_CtrlN, kbd_CtrlP, kbd_CtrlR, kbd_CtrlU + , kbd_ENTER, kbd_SPACE, kbd_BTAB, '\0' } }, + { keys_summary, + #ifdef CORE_TYPE_NO + { '!', '1', '2', '3', '4', 'C', 'l', 'm', 't', '\0' } }, + #else + { '!', '1', '2', '3', '4', '5', 'C', 'l', 'm', 't', '\0' } }, + #endif + { keys_task, + { '#', '<', '>', 'b', 'c', 'F', 'i', 'J', 'j', 'n', 'O', 'o' + , 'R', 'S', 'U', 'u', 'V', 'v', 'x', 'y', 'z' + , kbd_CtrlO, '\0' } }, + { keys_window, + { '+', '-', '=', '_', '&', 'A', 'a', 'G', 'L', 'w' + , kbd_UP, kbd_DOWN, kbd_LEFT, kbd_RIGHT, kbd_PGUP, kbd_PGDN + , kbd_HOME, kbd_END, '\0' } }, + { keys_xtra, + { 'M', 'N', 'P', 'T', '\0'} } + }; + int i; + + Frames_signal = BREAK_off; + switch (ch) { + case 0: // ignored (always) + case kbd_ESC: // ignored (sometimes) + goto all_done; + case 'q': // no return from this guy + bye_bye(NULL); + case 'W': // no need for rebuilds + write_rcfile(); + goto all_done; + default: // and now, the real work... + // and just in case 'Monpids' is active but matched no processes ... + if (!PIDSmaxt && ch != '=') goto all_done; + for (i = 0; i < MAXTBL(key_tab); ++i) + if (strchr(key_tab[i].keys, ch)) { + key_tab[i].func(ch); + if (Frames_signal == BREAK_off) + Frames_signal = BREAK_kbd; + /* due to the proliferation of the need for 'mkVIZrow1', | + aside from 'wins_stage_2' use, we'll now issue it one | + time here. there will remain several places where the | + companion 'mkVIZrowX' macro is issued, thus the check | + for a value already in 'begnext' in this conditional. | */ + if (CHKw(Curwin, Show_TASKON) && !mkVIZyes) + mkVIZrow1 + goto all_done; + } + }; + /* The Frames_signal above will force a rebuild of column headers. + It's NOT simply lazy programming. Below are some keys that may + require new column headers and/or new library item enumerators: + 'A' - likely + 'c' - likely when !Mode_altscr, maybe when Mode_altscr + 'F' - likely + 'f' - likely + 'g' - likely + 'H' - likely + 'I' - likely + 'J' - always + 'j' - always + 'Z' - likely, if 'Curwin' changed when !Mode_altscr + '-' - likely (restricted to Mode_altscr) + '_' - likely (restricted to Mode_altscr) + '=' - maybe, but only when Mode_altscr + '+' - likely (restricted to Mode_altscr) + PLUS, likely for FOUR of the EIGHT cursor motion keys (scrolled) + ( At this point we have a human being involved and so have all the time ) + ( in the world. We can afford a few extra cpu cycles every now & then! ) + */ + + show_msg(N_txt(UNKNOWN_cmds_txt)); +all_done: + putp((Cursor_state = Cap_curs_hide)); +} // end: do_key + + + /* + * In support of a new frame: + * 1) Display uptime and load average (maybe) + * 2) Display task/cpu states (maybe) + * 3) Display memory & swap usage (maybe) */ +static void summary_show (void) { + #define isROOM(f,n) (CHKw(Curwin, f) && Msg_row + (n) < SCREEN_ROWS - 1) + + if (Restrict_some) { +#ifdef THREADED_TSK + sem_wait(&Semaphore_tasks_end); +#endif + // Display Task States only + if (isROOM(View_STATES, 1)) { + show_special(0, fmtmk(N_unq(STATE_line_1_fmt) + , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt) + , PIDSmaxt, Pids_reap->counts->running + , Pids_reap->counts->sleeping + Pids_reap->counts->other + , Pids_reap->counts->stopped, Pids_reap->counts->zombied)); + Msg_row += 1; + } + return; + } + + // Display Uptime and Loadavg + if (isROOM(View_LOADAV, 1)) { + if (!Rc.mode_altscr) + show_special(0, fmtmk(LOADAV_line, Myname, procps_uptime_sprint())); + else + show_special(0, fmtmk(CHKw(Curwin, Show_TASKON)? LOADAV_line_alt : LOADAV_line + , Curwin->grpname, procps_uptime_sprint())); + Msg_row += 1; + } // end: View_LOADAV + +#ifdef THREADED_CPU + sem_wait(&Semaphore_cpus_end); +#endif +#ifdef THREADED_TSK + sem_wait(&Semaphore_tasks_end); +#endif + // Display Task and Cpu(s) States + if (isROOM(View_STATES, 2)) { + show_special(0, fmtmk(N_unq(STATE_line_1_fmt) + , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt) + , PIDSmaxt, Pids_reap->counts->running + , Pids_reap->counts->sleeping + Pids_reap->counts->other + , Pids_reap->counts->stopped, Pids_reap->counts->zombied)); + Msg_row += 1; + + do_cpus(); + } + +#ifdef THREADED_MEM + sem_wait(&Semaphore_memory_end); +#endif + // Display Memory and Swap stats + if (isROOM(View_MEMORY, 2)) { + do_memory(); + } + + #undef isROOM +} // end: summary_show + + + /* + * Build the information for a single task row and + * display the results or return them to the caller. */ +static const char *task_show (const WIN_t *q, int idx) { + // a tailored 'results stack value' extractor macro + #define rSv(E,T) PID_VAL(E, T, p) +#ifndef SCROLLVAR_NO + #define makeVAR(S) { const char *pv = S; \ + if (!q->varcolbeg) cp = make_str(pv, q->varcolsz, Js, AUTOX_NO); \ + else cp = make_str(q->varcolbeg < (int)strlen(pv) ? pv + q->varcolbeg : "", q->varcolsz, Js, AUTOX_NO); } + #define varUTF8(S) { const char *pv = S; \ + if (!q->varcolbeg) cp = make_str_utf8(pv, q->varcolsz, Js, AUTOX_NO); \ + else cp = make_str_utf8((q->varcolbeg < ((int)strlen(pv) - utf8_delta(pv))) \ + ? pv + utf8_embody(pv, q->varcolbeg) : "", q->varcolsz, Js, AUTOX_NO); } +#else + #define makeVAR(S) { cp = make_str(S, q->varcolsz, Js, AUTOX_NO); } + #define varUTF8(S) { cp = make_str_utf8(S, q->varcolsz, Js, AUTOX_NO); } +#endif + struct pids_stack *p = q->ppt[idx]; + static char rbuf[ROWMINSIZ]; + char *rp; + int x; + + /* we use up to three additional 'PIDS_extra' results in our stacks + eu_TREE_HID (s_ch) : where 'x' == collapsed and 'z' == unseen + eu_TREE_LVL (s_int): where a level number is stored (0 - 100) + eu_TREE_ADD (u_int): where children's tics are stored (maybe) */ +#ifndef TREE_VWINALL + if (q == Curwin) // note: the following is NOT indented +#endif + if (CHKw(q, Show_FOREST) && rSv(eu_TREE_HID, s_ch) == 'z') + return ""; + + // we must begin a row with a possible window number in mind... + *(rp = rbuf) = '\0'; + if (Rc.mode_altscr) rp = scat(rp, " "); + + for (x = 0; x < q->maxpflgs; x++) { + const char *cp = NULL; + FLG_t i = q->procflgs[x]; + #define S Fieldstab[i].scale // these used to be variables + #define W Fieldstab[i].width // but it's much better if we + #define Js CHKw(q, Show_JRSTRS) // represent them as #defines + #define Jn CHKw(q, Show_JRNUMS) // and only exec code if used + + /* except for the XOF/XON pseudo flags the following case labels are grouped + by result type according to capacity (small -> large) and then ordered by + additional processing requirements (as in plain, scaled, decorated, etc.) */ + + switch (i) { +#ifndef USE_X_COLHDR + // these 2 aren't real procflgs, they're used in column highlighting! + case EU_XOF: + case EU_XON: + cp = NULL; + if (!CHKw(q, NOPRINT_xxx)) { + /* treat running tasks specially - entire row may get highlighted + so we needn't turn it on and we MUST NOT turn it off */ + if (!('R' == rSv(EU_STA, s_ch) && CHKw(q, Show_HIROWS))) + cp = (EU_XON == i ? q->capclr_rowhigh : q->capclr_rownorm); + } + break; +#endif + /* s_ch, make_chr */ + case EU_STA: // PIDS_STATE + cp = make_chr(rSv(EU_STA, s_ch), W, Js); + break; + /* s_int, make_num with auto width */ + case EU_LID: // PIDS_ID_LOGIN + cp = make_num(rSv(EU_LID, s_int), W, Jn, EU_LID, 0); + break; + /* s_int, make_num without auto width */ + case EU_AGI: // PIDS_AUTOGRP_ID + case EU_CPN: // PIDS_PROCESSOR + case EU_NMA: // PIDS_PROCESSOR_NODE + case EU_PGD: // PIDS_ID_PGRP + case EU_PID: // PIDS_ID_PID + case EU_PPD: // PIDS_ID_PPID + case EU_SID: // PIDS_ID_SESSION + case EU_TGD: // PIDS_ID_TGID + case EU_THD: // PIDS_NLWP + case EU_TPG: // PIDS_ID_TPGID + cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 0); + break; + /* s_int, make_num without auto width, but with zero suppression */ + case EU_AGN: // PIDS_AUTOGRP_NICE + case EU_NCE: // PIDS_NICE + case EU_OOA: // PIDS_OOM_ADJ + case EU_OOM: // PIDS_OOM_SCORE + cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 1); + break; + /* s_int, scale_num */ + case EU_FV1: // PIDS_FLT_MAJ_DELTA + case EU_FV2: // PIDS_FLT_MIN_DELTA + cp = scale_num(rSv(i, s_int), W, Jn); + break; + /* s_int, make_num or make_str */ + case EU_PRI: // PIDS_PRIORITY + if (-99 > rSv(EU_PRI, s_int) || 999 < rSv(EU_PRI, s_int)) + cp = make_str("rt", W, Jn, AUTOX_NO); + else + cp = make_num(rSv(EU_PRI, s_int), W, Jn, AUTOX_NO, 0); + break; + /* s_int, scale_pcnt with special handling */ + case EU_CPU: // PIDS_TICS_ALL_DELTA + { float u = (float)rSv(EU_CPU, u_int); + int n = rSv(EU_THD, s_int); + if (Restrict_some) { + cp = justify_pad("?", W, Jn); + break; + } +#ifndef TREE_VCPUOFF + #ifndef TREE_VWINALL + if (q == Curwin) // note: the following is NOT indented + #endif + if (CHKw(q, Show_FOREST)) u += rSv(eu_TREE_ADD, u_int); + u *= Frame_etscale; + /* technically, eu_TREE_HID is only valid if Show_FOREST is active + but its zeroed out slot will always be present now */ + if (rSv(eu_TREE_HID, s_ch) != 'x' && u > 100.0 * n) u = 100.0 * n; +#else + u *= Frame_etscale; + /* process can't use more %cpu than number of threads it has + ( thanks Jaromir Capik <jcapik@redhat.com> ) */ + if (u > 100.0 * n) u = 100.0 * n; +#endif + if (u > Cpu_pmax) u = Cpu_pmax; + cp = scale_pcnt(u, W, Jn, 0); + } + break; + /* ull_int, scale_pcnt for 'utilization' */ + case EU_CUU: // PIDS_UTILIZATION + case EU_CUC: // PIDS_UTILIZATION_C + if (Restrict_some) { + cp = justify_pad("?", W, Jn); + break; + } + cp = scale_pcnt(rSv(i, real), W, Jn, 1); + break; + /* u_int, make_num with auto width */ + case EU_GID: // PIDS_ID_EGID + case EU_UED: // PIDS_ID_EUID + case EU_URD: // PIDS_ID_RUID + case EU_USD: // PIDS_ID_SUID + cp = make_num(rSv(i, u_int), W, Jn, i, 0); + break; + /* ul_int, make_num with auto width and zero suppression */ + case EU_NS1: // PIDS_NS_IPC + case EU_NS2: // PIDS_NS_MNT + case EU_NS3: // PIDS_NS_NET + case EU_NS4: // PIDS_NS_PID + case EU_NS5: // PIDS_NS_USER + case EU_NS6: // PIDS_NS_UTS + case EU_NS7: // PIDS_NS_CGROUP + case EU_NS8: // PIDS_NS_TIME + cp = make_num(rSv(i, ul_int), W, Jn, i, 1); + break; + /* ul_int, scale_mem */ + case EU_COD: // PIDS_MEM_CODE + case EU_DAT: // PIDS_MEM_DATA + case EU_DRT: // PIDS_noop, really # pgs, but always 0 since 2.6 + case EU_PZA: // PIDS_SMAP_PSS_ANON + case EU_PZF: // PIDS_SMAP_PSS_FILE + case EU_PZS: // PIDS_SMAP_PSS_SHMEM + case EU_PSS: // PIDS_SMAP_PSS + case EU_RES: // PIDS_MEM_RES + case EU_RSS: // PIDS_SMAP_RSS + case EU_RZA: // PIDS_VM_RSS_ANON + case EU_RZF: // PIDS_VM_RSS_FILE + case EU_RZL: // PIDS_VM_RSS_LOCKED + case EU_RZS: // PIDS_VM_RSS_SHARED + case EU_SHR: // PIDS_MEM_SHR + case EU_SWP: // PIDS_VM_SWAP + case EU_USE: // PIDS_VM_USED + case EU_USS: // PIDS_SMAP_PRV_TOTAL + case EU_VRT: // PIDS_MEM_VIRT + cp = scale_mem(S, rSv(i, ul_int), W, Jn); + break; + /* ul_int, scale_num */ + case EU_FL1: // PIDS_FLT_MAJ + case EU_FL2: // PIDS_FLT_MIN + case EU_IRB: // PIDS_IO_READ_BYTES + case EU_IRO: // PIDS_IO_READ_OPS + case EU_IWB: // PIDS_IO_WRITE_BYTES + case EU_IWO: // PIDS_IO_WRITE_OPS + cp = scale_num(rSv(i, ul_int), W, Jn); + break; + /* ul_int, scale_pcnt */ + case EU_MEM: // derive from PIDS_MEM_RES + if (Restrict_some) { + cp = justify_pad("?", W, Jn); + break; + } + cp = scale_pcnt((float)rSv(EU_MEM, ul_int) * 100 / MEM_VAL(mem_TOT), W, Jn, 0); + break; + /* ul_int, make_str with special handling */ + case EU_FLG: // PIDS_FLAGS + cp = make_str(hex_make(rSv(EU_FLG, ul_int), 1), W, Js, AUTOX_NO); + break; + /* ull_int, scale_tics (try 'minutes:seconds.hundredths') */ + case EU_TM2: // PIDS_TICS_ALL + case EU_TME: // PIDS_TICS_ALL + { TIC_t t; + if (CHKw(q, Show_CTIMES)) t = rSv(eu_TICS_ALL_C, ull_int); + else t = rSv(i, ull_int); + cp = scale_tics(t, W, Jn, TICS_AS_SECS); + } + break; + /* ull_int, scale_tics (try 'minutes:seconds') */ + case EU_TM3: // PIDS_TICS_BEGAN + cp = scale_tics(rSv(EU_TM3, ull_int), W, Jn, TICS_AS_MINS); + break; + /* real, scale_tics (try 'hour,minutes') */ + case EU_TM4: // PIDS_TIME_ELAPSED + cp = scale_tics(rSv(EU_TM4, real) * Hertz, W, Jn, TICS_AS_HOUR); + break; + /* str, make_str (all AUTOX yes) */ + case EU_LXC: // PIDS_LXCNAME + case EU_TTY: // PIDS_TTY_NAME + case EU_WCH: // PIDS_WCHAN_NAME + cp = make_str(rSv(i, str), W, Js, i); + break; + /* str, make_str_utf8 (all AUTOX yes) */ + case EU_GRP: // PIDS_ID_EGROUP + case EU_UEN: // PIDS_ID_EUSER + case EU_URN: // PIDS_ID_RUSER + case EU_USN: // PIDS_ID_SUSER + cp = make_str_utf8(rSv(i, str), W, Js, i); + break; + /* str, make_str_utf8 with variable width */ + case EU_CGN: // PIDS_CGNAME + case EU_CGR: // PIDS_CGROUP + case EU_ENV: // PIDS_ENVIRON + case EU_EXE: // PIDS_EXE + case EU_SGN: // PIDS_SUPGROUPS + varUTF8(rSv(i, str)) + break; + /* str, make_str with variable width */ + case EU_SGD: // PIDS_SUPGIDS + makeVAR(rSv(EU_SGD, str)) + break; + /* str, make_str with variable width + additional decoration */ + case EU_CMD: // PIDS_CMD or PIDS_CMDLINE + varUTF8(forest_display(q, idx)) + break; + default: // keep gcc happy + continue; + } // end: switch 'procflag' + + if (cp) { + if (q->osel_tot && !osel_matched(q, i, cp)) return ""; + rp = scat(rp, cp); + } + #undef S + #undef W + #undef Js + #undef Jn + } // end: for 'maxpflgs' + + if (!CHKw(q, NOPRINT_xxx)) { + const char *cap = ((CHKw(q, Show_HIROWS) && 'R' == rSv(EU_STA, s_ch))) + ? q->capclr_rowhigh : q->capclr_rownorm; + char *row = rbuf; + int ofs; + /* since we can't predict what the search string will be and, + considering what a single space search request would do to + potential buffer needs, when any matches are found we skip + normal output routing and send all of the results directly + to the terminal (and we sound asthmatic: poof, putt, puff) */ + if (-1 < (ofs = find_ofs(q, row))) { + POOF("\n", cap); + do { + row[ofs] = '\0'; + PUTT("%s%s%s%s", row, q->capclr_hdr, q->findstr, cap); + row += (ofs + q->findlen); + ofs = find_ofs(q, row); + } while (-1 < ofs); + PUTT("%s%s", row, Caps_endline); + // with a corrupted rbuf, ensure row is 'counted' by window_show + rbuf[0] = '!'; + } else + PUFF("\n%s%s%s", cap, row, Caps_endline); + } + return rbuf; + #undef rSv + #undef makeVAR + #undef varUTF8 +} // end: task_show + + + /* + * A window_show *Helper* function ensuring that a window 'begtask' | + * represents a visible process (not any hidden/filtered-out task). | + * In reality this function is called exclusively for the 'current' | + * window and only after available user keystroke(s) are processed. | + * Note: it's entirely possible there are NO visible tasks to show! | */ +static void window_hlp (void) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int i, reversed; + int beg = w->focus_pid ? w->focus_beg : 0; + int end = w->focus_pid ? w->focus_end : PIDSmaxt; + + SETw(w, NOPRINT_xxx); + w->begtask += w->begnext; + // next 'if' will force a forward scan ... + if (w->begtask <= beg) { w->begtask = beg; w->begnext = +1; } + else if (w->begtask >= end) w->begtask = end - 1; + + reversed = 0; + // potentially scroll forward ... + if (w->begnext > 0) { +fwd_redux: + for (i = w->begtask; i < end; i++) { + if (wins_usrselect(w, i) + && (*task_show(w, i))) + break; + } + if (i < end) { + w->begtask = i; + goto wrap_up; + } + // no luck forward, so let's try backward + w->begtask = end - 1; + } + + // potentially scroll backward ... + for (i = w->begtask; i > beg; i--) { + if (wins_usrselect(w, i) + && (*task_show(w, i))) + break; + } + w->begtask = i; + + // reached the top, but maybe this guy ain't visible + if (w->begtask == beg && !reversed) { + if (!(wins_usrselect(w, beg)) + || (!(*task_show(w, beg)))) { + reversed = 1; + goto fwd_redux; + } + } + +wrap_up: + mkVIZoff(w) + OFFw(w, NOPRINT_xxx); +} // end: window_hlp + + + /* + * Squeeze as many tasks as we can into a single window, + * after sorting the passed proc table. */ +static int window_show (WIN_t *q, int wmax) { + #define sORDER CHKw(q, Qsrt_NORMAL) ? PIDS_SORT_DESCEND : PIDS_SORT_ASCEND + /* the isBUSY macro determines if a task is 'active' -- + it returns true if some cpu was used since the last sample. + ( actual 'running' tasks will be a subset of those selected ) */ + #define isBUSY(x) (0 < PID_VAL(EU_CPU, u_int, (x))) + #define winMIN(a,b) (((a) < (b)) ? (a) : (b)) + int i, lwin, numtasks; + + // Display Column Headings -- and distract 'em while we sort (maybe) + PUFF("\n%s%s%s", q->capclr_hdr, q->columnhdr, Caps_endline); + // and just in case 'Monpids' is active but matched no processes ... + if (!PIDSmaxt) return 1; // 1 for the column header + + if (CHKw(q, Show_FOREST)) { + forest_begin(q); + if (q->focus_pid) forest_config(q); + } else { + enum pids_item item = Fieldstab[q->rc.sortindx].item; + if (item == PIDS_CMD && CHKw(q, Show_CMDLIN)) + item = PIDS_CMDLINE; + else if (item == PIDS_TICS_ALL && CHKw(q, Show_CTIMES)) + item = PIDS_TICS_ALL_C; + if (!(procps_pids_sort(Pids_ctx, q->ppt , PIDSmaxt, item, sORDER))) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno))); + } + + if (mkVIZyes) window_hlp(); + else OFFw(q, NOPRINT_xxx); + + i = q->begtask; + lwin = 1; // 1 for the column header + wmax = winMIN(wmax, q->winlines + 1); // ditto for winlines, too + numtasks = q->focus_pid ? winMIN(q->focus_end, PIDSmaxt) : PIDSmaxt; + + /* the least likely scenario is also the most costly, so we'll try to avoid + checking some stuff with each iteration and check it just once... */ + if (CHKw(q, Show_IDLEPS) && !q->usrseltyp) + while (i < numtasks && lwin < wmax) { + if (*task_show(q, i++)) + ++lwin; + } + else + while (i < numtasks && lwin < wmax) { + if ((CHKw(q, Show_IDLEPS) || isBUSY(q->ppt[i])) + && wins_usrselect(q, i) + && *task_show(q, i)) + ++lwin; + ++i; + } + + return lwin; + #undef sORDER + #undef isBUSY + #undef winMIN +} // end: window_show + +/*###### Entry point plus two ##########################################*/ + + /* + * This guy's just a *Helper* function who apportions the + * remaining amount of screen real estate under multiple windows */ +static void frame_hlp (int wix, int max) { + int i, size, wins; + + // calc remaining number of visible windows + for (i = wix, wins = 0; i < GROUPSMAX; i++) + if (CHKw(&Winstk[i], Show_TASKON)) + ++wins; + + if (!wins) wins = 1; + // deduct 1 line/window for the columns heading + size = (max - wins) / wins; + + /* for subject window, set WIN_t winlines to either the user's + maxtask (1st choice) or our 'foxized' size calculation + (foxized adj. - 'fair and balanced') */ + Winstk[wix].winlines = + Winstk[wix].rc.maxtasks ? Winstk[wix].rc.maxtasks : size; +} // end: frame_hlp + + + /* + * Initiate the Frame Display Update cycle at someone's whim! + * This routine doesn't do much, mostly he just calls others. + * + * (Whoa, wait a minute, we DO caretake those row guys, plus) + * (we CALCULATE that IMPORTANT Max_lines thingy so that the) + * (*subordinate* functions invoked know WHEN the user's had) + * (ENOUGH already. And at Frame End, it SHOULD be apparent) + * (WE am d'MAN -- clearing UNUSED screen LINES and ensuring) + * (that those auto-sized columns are addressed, know what I) + * (mean? Huh, "doesn't DO MUCH"! Never, EVER think or say) + * (THAT about THIS function again, Ok? Good that's better.) + * + * (ps. we ARE the UNEQUALED justification KING of COMMENTS!) + * (No, I don't mean significance/relevance, only alignment.) + */ +static void frame_make (void) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int i, scrlins; + + // check auto-sized width increases from the last iteration... + if (AUTOX_MODE && Autox_found) + widths_resize(); + + /* deal with potential signal(s) since the last time around + plus any input which may change 'tasks_refresh' needs... */ + if (Frames_signal) { + if (Frames_signal == BREAK_sig + || (Frames_signal == BREAK_screen)) + BOT_TOSS; + zap_fieldstab(); + } + +#ifdef THREADED_TSK + sem_post(&Semaphore_tasks_beg); +#else + tasks_refresh(NULL); +#endif + + if (!Restrict_some) { +#ifdef THREADED_CPU + sem_post(&Semaphore_cpus_beg); +#else + cpus_refresh(NULL); +#endif +#ifdef THREADED_MEM + sem_post(&Semaphore_memory_beg); +#else + memory_refresh(NULL); +#endif + } + + // whoa either first time or thread/task mode change, (re)prime the pump... + if (Pseudo_row == PROC_XTRA) { + usleep(LIB_USLEEP); +#ifdef THREADED_TSK + sem_wait(&Semaphore_tasks_end); + sem_post(&Semaphore_tasks_beg); +#else + tasks_refresh(NULL); +#endif + putp(Cap_clr_scr); + } else + putp(Batch ? "\n\n" : Cap_home); + + Tree_idx = Pseudo_row = Msg_row = scrlins = 0; + summary_show(); + Max_lines = (SCREEN_ROWS - Msg_row) - 1; + + // we're now on Msg_row so clear out any residual messages ... + putp(Cap_clr_eol); + + if (!Rc.mode_altscr) { + // only 1 window to show so, piece o' cake + w->winlines = w->rc.maxtasks ? w->rc.maxtasks : Max_lines; + scrlins = window_show(w, Max_lines); + } else { + // maybe NO window is visible but assume, pieces o' cakes + for (i = 0 ; i < GROUPSMAX; i++) { + if (CHKw(&Winstk[i], Show_TASKON)) { + frame_hlp(i, Max_lines - scrlins); + scrlins += window_show(&Winstk[i], Max_lines - scrlins); + } + if (Max_lines <= scrlins) break; + } + } + + /* clear to end-of-screen - critical if last window is 'idleps off' + (main loop must iterate such that we're always called before sleep) */ + if (!Batch && scrlins < Max_lines) { + if (!BOT_PRESENT) + putp(Cap_nl_clreos); + else { + for (i = scrlins + Msg_row + 1; i < SCREEN_ROWS; i++) { + putp(tg2(0, i)); + putp(Cap_clr_eol); + } + } + PSU_CLREOS(Pseudo_row); + } + + if (CHKw(w, View_SCROLL) && VIZISw(Curwin)) show_scroll(); + if (Bot_show_func) Bot_show_func(); + fflush(stdout); + + /* we'll deem any terminal not supporting tgoto as dumb and disable + the normal non-interactive output optimization... */ + if (!Cap_can_goto) PSU_CLREOS(0); +} // end: frame_make + + + /* + * duh... */ +int main (int argc, char *argv[]) { + before(*argv); + // +-------------+ + wins_stage_1(); // top (sic) slice + configs_reads(); // > spread etc, < + parse_args(argc, argv); // > onions etc, < + signals_set(); // > lean stuff, < + whack_terminal(); // > more stuff. < + wins_stage_2(); // as bottom slice + // +-------------+ + + for (;;) { + struct timespec ts; + + frame_make(); + + if (0 < Loops) --Loops; + if (!Loops) bye_bye(NULL); + + ts.tv_sec = Rc.delay_time; + ts.tv_nsec = (Rc.delay_time - (int)Rc.delay_time) * 1000000000; + + if (Batch) + pselect(0, NULL, NULL, NULL, &ts, NULL); + else { + if (ioa(&ts)) + do_key(iokey(IOKEY_ONCE)); + } + /* note: that above ioa routine exists to consolidate all logic + which is susceptible to signal interrupt and must then + produce a screen refresh. in this main loop frame_make + assumes responsibility for such refreshes. other logic + in contact with users must deal more obliquely with an + interrupt/refresh (hint: Frames_signal + return code)! + + (everything is perfectly justified plus right margins) + (are completely filled, but of course it must be luck) + */ + } + return 0; +} // end: main diff --git a/src/top/top.h b/src/top/top.h new file mode 100644 index 0000000..5ba79b3 --- /dev/null +++ b/src/top/top.h @@ -0,0 +1,792 @@ +/* top.h - Header file: show Linux processes */ +/* + * Copyright © 2002-2023 Jim Warner <james.warner@comcast.net + * + * This file may be used subject to the terms and conditions of the + * GNU Library General Public License Version 2, or any later version + * at your option, as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + */ +/* For contributions to this program, the author wishes to thank: + * Craig Small, <csmall@dropbear.xyz> + * Albert D. Cahalan, <albert@users.sf.net> + * Sami Kerola, <kerolasa@iki.fi> + */ +#ifndef _Itop +#define _Itop + + /* Defines represented in configure.ac ----------------------------- */ +//#define BOOST_MEMORY /* enable extra precision in memory fields */ +//#define BOOST_PERCNT /* enable extra precision for two % fields */ +//#define ORIG_TOPDEFS /* with no rcfile retain original defaults */ +//#define SIGNALS_LESS /* favor reduced signal load over response */ + + /* Development/Debugging defines ----------------------------------- */ +//#define ATEOJ_RPTSTD /* report on some miscellany at end-of-job */ +//#define BOT_DEAD_ZAP /* zap Ctrl bottom window when target dies */ +//#define BOT_STRV_OFF /* don't emphasize strv w/ focus if spaces */ +//#define CASEUP_HEXES /* show all those hex values in upper case */ +//#define CASEUP_SUFIX /* show time/mem/cnts suffix in upper case */ +//#define CORE_TYPE_LO /* show the type of cpu core in lower case */ +//#define CORE_TYPE_NO /* don't distinguish the types of cpu core */ +//#define EQUCOLHDRYES /* yes, equalize the column header lengths */ +//#define FOCUS_HARD_Y /* 'F' will avoid topmost task distortions */ +//#define FOCUS_TREE_X /* 'F' resets forest view indentation to 0 */ +//#define FOCUS_VIZOFF /* 'F' doesn't provide the visual clue '|' */ +//#define GETOPTFIX_NO /* do not address getopt_long deficiencies */ +//#define INSP_JUSTNOT /* do not smooth unprintable right margins */ +//#define INSP_OFFDEMO /* disable demo screens, issue msg instead */ +//#define INSP_SAVEBUF /* preserve 'Insp_buf' contents via a file */ +//#define INSP_SLIDE_1 /* when scrolling left/right, don't move 8 */ +//#define MEMGRAPH_OLD /* don't use 'available' when graphing Mem */ +//#define NLS_INCLUDED /* provides for excluding from translation */ +//#define NLS_VALIDATE /* ensure the integrity of four nls tables */ +//#define OFF_SCROLLBK /* disable tty emulators scrollback buffer */ +//#define OFF_STDERROR /* disable our stderr buffering (redirect) */ +//#define OFF_STDIOLBF /* disable our own stdout 'IOFBF' override */ +//#define OFF_XTRAWIDE /* disable our extra wide multi-byte logic */ +//#define OVERTYPE_SEE /* display a visual hint for overtype mode */ +//#define PRETEND0NUMA /* pretend that there ain't any numa nodes */ +//#define PRETEND48CPU /* pretend we're smp with 48 ticsers (sic) */ +//#define PRETENDECORE /* pretend we've got some e-core type cpus */ +//#define PRETENDNOCAP /* pretend terminal missing essential caps */ +//#define RCFILE_NOERR /* rcfile errs silently default, vs. fatal */ +//#define RECALL_FIXED /* don't reorder saved strings if recalled */ +//#define RMAN_IGNORED /* don't consider auto right margin glitch */ +//#define SCALE_FORMER /* scale_tics() guy shouldn't mimic uptime */ +//#define SCALE_POSTFX /* scale_tics() try without a 'h,d' suffix */ +//#define SCROLLVAR_NO /* disable intra-column horizontal scrolls */ +//#define SCROLLV_BY_1 /* when scrolling left/right do not move 8 */ +//#define STRINGCASENO /* case insensitive compare/locate version */ +//#define TERMIOS_ONLY /* use native input only (just limp along) */ +//#define THREADED_CPU /* separate background thread for cpu updt */ +//#define THREADED_MEM /* separate background thread for mem updt */ +//#define THREADED_TSK /* separate background thread for tsk updt */ +//#define TOG4_MEM_1UP /* don't show two abreast memory statistic */ +//#define TOG4_MEM_FIX /* no variable mem graphs, thus misaligned */ +//#define TOG4_SEP_OFF /* don't show two abreast visual separator */ +//#define TOG4_SEP_STD /* normal mem sep if 2 abreast & no graphs */ +//#define TREE_NORESET /* sort keys should not force 'V' view off */ +//#define TREE_SCANALL /* rescan array w/ forest view, avoid sort */ +//#define TREE_VALTMRK /* use an indented '+' with collapsed pids */ +//#define TREE_VCPUOFF /* a collapsed parent excludes child's cpu */ +//#define TREE_VPROMPT /* pid collapse/expand prompt, vs. top row */ +//#define TREE_VWINALL /* pid collapse/expand impacts all windows */ +//#define USE_X_COLHDR /* emphasize header vs. whole col, for 'x' */ +//#define WIDEN_COLUMN /* base column widths on translated header */ + + +/*###### Notes, etc. ###################################################*/ + + /* For introducing inaugural cgroup support, thanks to: + Jan Gorig <jgorig@redhat.com> - April, 2011 */ + + /* For the motivation and path to nls support, thanks to: + Sami Kerola, <kerolasa@iki.fi> - December, 2011 */ + + /* There are still some short strings that may yet be candidates + for nls support inclusion. They're identified with: + // nls_maybe */ + + /* For the impetus and NUMA/Node prototype design, thanks to: + Lance Shelton <LShelton@fusionio.com> - April, 2013 */ + + /* For prompting & helping with top's utf-8 support, thanks to: + Göran Uddeborg <goeran@uddeborg.se> - September, 2017 */ + + // pretend as if #define _GNU_SOURCE +char *strcasestr(const char *haystack, const char *needle); + +#ifdef STRINGCASENO +#define STRSTR strcasestr +#define STRCMP strcasecmp +#else +#define STRSTR strstr +#define STRCMP strcmp +#endif + + +/*###### Some Miscellaneous constants ##################################*/ + + /* The default delay twix updates */ +#ifdef ORIG_TOPDEFS +#define DEF_DELAY 3.0 +#else +#define DEF_DELAY 1.5 +#endif + + /* Length of time a message is displayed and the duration + of a 'priming' wait during library startup (in microseconds) */ +#define MSG_USLEEP 1250000 +#define LIB_USLEEP 100000 + + /* Specific process id monitoring support (command line only) */ +#define MONPIDMAX 20 + + /* Output override minimums (the -w switch and/or env vars) */ +#define W_MIN_COL 3 +#define W_MIN_ROW 3 + + /* Miscellaneous buffers with liberal values and some other defines + -- mostly just to pinpoint source code usage/dependencies */ +#define SCREENMAX 512 + /* the above might seem pretty stingy, until you consider that with every + field displayed the column header would be approximately 250 bytes + -- so SCREENMAX provides for all fields plus a 250+ byte command line */ +#define TNYBUFSIZ 16 +#define CAPBUFSIZ 32 +#define CLRBUFSIZ 64 +#define PFLAGSSIZ 128 +#define SMLBUFSIZ 128 +#define MEDBUFSIZ 256 +#define LRGBUFSIZ 512 +#define OURPATHSZ 1024 +#define BIGBUFSIZ 2048 +#define BOTBUFSIZ 16384 + // next is same as library's max buffer size +#define MAXBUFSIZ (1024*64*2) + /* in addition to the actual display data, our row might have to accommodate + many termcap/color transitions - these definitions ensure we have room */ +#define ROWMINSIZ ( SCREENMAX + 8 * (CAPBUFSIZ + CLRBUFSIZ) ) +#define ROWMAXSIZ ( SCREENMAX + 16 * (CAPBUFSIZ + CLRBUFSIZ) ) + // minimum size guarantee for dynamically acquired 'readfile' buffer +#define READMINSZ 2048 + // size of preallocated search string buffers, same as ioline() +#define FNDBUFSIZ MEDBUFSIZ + + + // space between task fields/columns +#define COLPADSTR " " +#define COLPADSIZ ( sizeof(COLPADSTR) - 1 ) + // continuation ch when field/column truncated +#define COLPLUSCH '+' + + // support for keyboard stuff (cursor motion keystrokes, mostly) +#define kbd_ESC '\033' +#define kbd_SPACE ' ' +#define kbd_ENTER '\n' +#define kbd_UP 129 +#define kbd_DOWN 130 +#define kbd_LEFT 131 +#define kbd_RIGHT 132 +#define kbd_PGUP 133 +#define kbd_PGDN 134 +#define kbd_HOME 135 +#define kbd_END 136 +#define kbd_BKSP 137 +#define kbd_INS 138 +#define kbd_DEL 139 +#define kbd_BTAB 140 +#define kbd_CtrlE '\005' +#define kbd_CtrlG '\007' +#define kbd_CtrlI '\011' +#define kbd_CtrlK '\013' +#define kbd_CtrlL '\014' +#define kbd_CtrlN '\016' +#define kbd_CtrlO '\017' +#define kbd_CtrlP '\020' +#define kbd_CtrlR '\022' +#define kbd_CtrlU '\025' + + /* Special value in Pseudo_row to force an additional procs refresh + -- used at startup and for task/thread mode transitions */ +#define PROC_XTRA -1 + + +/* ##### Enum's and Typedef's ############################################ */ + + /* Flags for each possible field (and then some) -- + these MUST be kept in sync with the Fieldstab[] array !! */ +enum pflag { + EU_PID = 0, EU_PPD, + EU_UED, EU_UEN, EU_URD, EU_URN, EU_USD, EU_USN, + EU_GID, EU_GRP, EU_PGD, EU_TTY, EU_TPG, EU_SID, + EU_PRI, EU_NCE, EU_THD, + EU_CPN, EU_CPU, EU_TME, EU_TM2, + EU_MEM, EU_VRT, EU_SWP, EU_RES, EU_COD, EU_DAT, EU_SHR, + EU_FL1, EU_FL2, EU_DRT, + EU_STA, EU_CMD, EU_WCH, EU_FLG, EU_CGR, + EU_SGD, EU_SGN, EU_TGD, + EU_OOA, EU_OOM, + EU_ENV, + EU_FV1, EU_FV2, + EU_USE, + EU_NS1, EU_NS2, EU_NS3, EU_NS4, EU_NS5, EU_NS6, + EU_LXC, + EU_RZA, EU_RZF, EU_RZL, EU_RZS, + EU_CGN, + EU_NMA, + EU_LID, + EU_EXE, + EU_RSS, EU_PSS, EU_PZA, EU_PZF, EU_PZS, EU_USS, + EU_IRB, EU_IRO, EU_IWB, EU_IWO, + EU_AGI, EU_AGN, + EU_TM3, EU_TM4, EU_CUU, EU_CUC, + EU_NS7, EU_NS8, +#ifdef USE_X_COLHDR + // not really pflags, used with tbl indexing + EU_MAXPFLGS +#else + // not really pflags, used with tbl indexing & col highlighting + EU_MAXPFLGS, EU_XON, EU_XOF +#endif +}; + + /* The scaling 'target' used with memory fields */ +enum scale_enum { + SK_Kb, SK_Mb, SK_Gb, SK_Tb, SK_Pb, SK_Eb +}; + + /* Used to manipulate (and document) the Frames_signal states */ +enum resize_states { + BREAK_off = 0, BREAK_kbd, BREAK_sig, BREAK_autox, BREAK_screen +}; + + /* This typedef just ensures consistent 'process flags' handling */ +typedef int FLG_t; + + /* These typedefs attempt to ensure consistent 'ticks' handling */ +typedef unsigned long long TIC_t; +typedef long long SIC_t; + + + /* /////////////////////////////////////////////////////////////// */ + /* Special Section: multiple windows/field groups --------------- */ + /* ( kind of a header within a header: constants, types & macros ) */ + +#define CAPTABMAX 9 /* max entries in each win's caps table */ +#define GROUPSMAX 4 /* the max number of simultaneous windows */ +#define WINNAMSIZ 4 /* size of RCW_t winname buf (incl '\0') */ +#define GRPNAMSIZ WINNAMSIZ+2 /* window's name + number as in: '#:...' */ + + /* The Persistent 'Mode' flags! + These are preserved in the rc file, as a single integer and the + letter shown is the corresponding 'command' toggle */ + // 'View_' flags affect the summary (minimum), taken from 'Curwin' +#define View_CPUSUM 0x008000 // '1' - show combined cpu stats (vs. each) +#define View_CPUNOD 0x400000 // '2' - show numa node cpu stats ('3' also) +#define View_LOADAV 0x004000 // 'l' - display load avg and uptime summary +#define View_STATES 0x002000 // 't' - display task/cpu(s) states summary +#define View_MEMORY 0x001000 // 'm' - display memory summary +#define View_NOBOLD 0x000008 // 'B' - disable 'bold' attribute globally +#define View_SCROLL 0x080000 // 'C' - enable coordinates msg w/ scrolling + // 'Show_' & 'Qsrt_' flags are for task display in a visible window +#define Show_COLORS 0x000800 // 'z' - show in color (vs. mono) +#define Show_HIBOLD 0x000400 // 'b' - rows and/or cols bold (vs. reverse) +#define Show_HICOLS 0x000200 // 'x' - show sort column emphasized +#define Show_HIROWS 0x000100 // 'y' - show running tasks highlighted +#define Show_CMDLIN 0x000080 // 'c' - show cmdline vs. name +#define Show_CTIMES 0x000040 // 'S' - show times as cumulative +#define Show_IDLEPS 0x000020 // 'i' - show idle processes (all tasks) +#define Show_TASKON 0x000010 // '-' - tasks showable when Mode_altscr +#define Show_FOREST 0x000002 // 'V' - show cmd/cmdlines with ascii art +#define Qsrt_NORMAL 0x000004 // 'R' - reversed column sort (high to low) +#define Show_JRSTRS 0x040000 // 'j' - right justify "string" data cols +#define Show_JRNUMS 0x020000 // 'J' - right justify "numeric" data cols + // these flag(s) have no command as such - they're for internal use +#define NOPRINT_xxx 0x010000 // build task rows only (not for display) +#define EQUWINS_xxx 0x000001 // rebalance all wins & tasks (off i,n,u/U) + + // Default flags if there's no rcfile to provide user customizations +#ifdef ORIG_TOPDEFS +#define DEF_WINFLGS ( View_LOADAV | View_STATES | View_CPUSUM | View_MEMORY \ + | Show_HIBOLD | Show_HIROWS | Show_IDLEPS | Show_TASKON | Show_JRNUMS \ + | Qsrt_NORMAL ) +#define DEF_GRAPHS2 0, 0 +#define DEF_SCALES2 SK_Mb, SK_Kb +#define ALT_WINFLGS DEF_WINFLGS +#define ALT_GRAPHS2 0, 0 +#else +#define DEF_WINFLGS ( View_LOADAV | View_STATES | View_MEMORY | Show_CMDLIN \ + | Show_COLORS | Show_FOREST | Show_HIROWS | Show_IDLEPS | Show_JRNUMS | Show_TASKON \ + | Qsrt_NORMAL ) +#define DEF_GRAPHS2 1, 2 +#define DEF_SCALES2 SK_Gb, SK_Mb +#define ALT_WINFLGS (DEF_WINFLGS | Show_HIBOLD) & ~Show_FOREST +#define ALT_GRAPHS2 2, 0 +#endif + + /* These are used to direct wins_reflag */ +enum reflag_enum { + Flags_TOG, Flags_SET, Flags_OFF +}; + + /* These are used to direct win_warn */ +enum warn_enum { + Warn_ALT, Warn_VIZ +}; + + /* This type helps support both a window AND the rcfile */ +typedef struct RCW_t { // the 'window' portion of an rcfile + int sortindx, // sort field (represented as procflag) + winflags, // 'view', 'show' and 'sort' mode flags + maxtasks, // user requested maximum, 0 equals all + graph_cpus, // 't' - View_STATES supplementary vals + graph_mems, // 'm' - View_MEMORY supplememtary vals + double_up, // '4' - show multiple cpus on one line + combine_cpus, // '!' - keep combining additional cpus + core_types, // '5' - show/filter P-core/E-core cpus + summclr, // a colors 'number' used for summ info + msgsclr, // " in msgs/pmts + headclr, // " in cols head + taskclr; // " in task rows + char winname [WINNAMSIZ]; // name for the window, user changeable + FLG_t fieldscur [PFLAGSSIZ]; // the fields for display & their order +} RCW_t; + + /* This represents the complete rcfile */ +typedef struct RCF_t { + char id; // rcfile version id + int mode_altscr; // 'A' - Alt display mode (multi task windows) + int mode_irixps; // 'I' - Irix vs. Solaris mode (SMP-only) + float delay_time; // 'd'/'s' - How long to sleep twixt updates + int win_index; // Curwin, as index + RCW_t win [GROUPSMAX]; // a 'WIN_t.rc' for each window + int fixed_widest; // 'X' - wider non-scalable col addition + int summ_mscale; // 'E' - scaling of summary memory values + int task_mscale; // 'e' - scaling of process memory values + int zero_suppress; // '0' - suppress scaled zeros toggle + int tics_scaled; // ^E - scale TIME and/or TIME+ columns +} RCF_t; + + /* This structure stores configurable information for each window. + By expending a little effort in its creation and user requested + maintenance, the only real additional per frame cost of having + windows is an extra sort -- but that's just on pointers! */ +typedef struct WIN_t { + FLG_t pflgsall [PFLAGSSIZ], // all 'active/on' fieldscur, as enum + procflgs [PFLAGSSIZ]; // fieldscur subset, as enum + RCW_t rc; // stuff that gets saved in the rcfile + int winnum, // a window's number (array pos + 1) + winlines, // current task window's rows (volatile) + maxpflgs, // number of displayed procflgs ("on" in fieldscur) + totpflgs, // total of displayable procflgs in pflgsall array + begpflg, // scrolled beginning pos into pflgsall array + endpflg, // scrolled ending pos into pflgsall array + begtask, // scrolled beginning pos into total tasks + begnext, // new scrolled delta for next frame's begtask +#ifndef SCROLLVAR_NO + varcolbeg, // scrolled position within variable width col +#endif + varcolsz, // max length of variable width column(s) + usrseluid, // validated uid for 'u/U' user selection + usrseltyp, // the basis for matching above uid + usrselflg, // flag denoting include/exclude matches + hdrcaplen; // column header xtra caps len, if any + char capclr_sum [CLRBUFSIZ], // terminfo strings built from + capclr_msg [CLRBUFSIZ], // RCW_t colors (& rebuilt too), + capclr_pmt [CLRBUFSIZ], // but NO recurring costs ! + capclr_hdr [CLRBUFSIZ], // note: sum, msg and pmt strs + capclr_rowhigh [SMLBUFSIZ], // are only used when this + capclr_rownorm [CLRBUFSIZ], // window is the 'Curwin'! + cap_bold [CAPBUFSIZ], // support for View_NOBOLD toggle + grpname [GRPNAMSIZ], // window number:name, printable +#ifdef USE_X_COLHDR + columnhdr [ROWMINSIZ], // column headings for procflgs +#else + columnhdr [SCREENMAX], // column headings for procflgs +#endif + *captab [CAPTABMAX]; // captab needed by show_special() + struct osel_s *osel_1st; // other selection criteria anchor + int osel_tot; // total of other selection criteria + char *findstr; // window's current/active search string + int findlen; // above's strlen, without call overhead + int focus_pid; // target pid when 'F' toggle is active + int focus_beg; // ppt index where 'F' toggle has begun + int focus_end; // ppt index where 'F' toggle has ended +#ifdef FOCUS_TREE_X + int focus_lvl; // the indentation level of parent task +#endif + struct pids_stack **ppt; // this window's stacks ptr array + struct WIN_t *next, // next window in window stack + *prev; // prior window in window stack +} WIN_t; + + // Used to test/manipulate the window flags +#define CHKw(q,f) (int)((q)->rc.winflags & (f)) +#define TOGw(q,f) (q)->rc.winflags ^= (f) +#define SETw(q,f) (q)->rc.winflags |= (f) +#define OFFw(q,f) (q)->rc.winflags &= ~(f) +#define ALTCHKw (Rc.mode_altscr ? 1 : win_warn(Warn_ALT)) +#define VIZISw(q) (!Rc.mode_altscr || CHKw(q,Show_TASKON)) +#define VIZCHKw(q) (VIZISw(q)) ? 1 : win_warn(Warn_VIZ) +#define VIZTOGw(q,f) (VIZISw(q)) ? TOGw(q,(f)) : win_warn(Warn_VIZ) + + // Used to test/manipulte fieldscur values +#define FLDon 0x01 +#define FLDoff 0x00 +#define FLDget(q,i) ( (((q)->rc.fieldscur[i]) >> 1) - FLD_OFFSET ) +#define FLDtog(q,i) ( (q)->rc.fieldscur[i] ^= FLDon ) +#define FLDviz(q,i) ( (q)->rc.fieldscur[i] & FLDon ) +#define ENUviz(w,E) ( NULL != msch((w)->procflgs, E, w->maxpflgs) ) +#define ENUpos(w,E) ( (int)(msch((w)->pflgsall, E, (w)->totpflgs) - (w)->pflgsall) ) +#define ENUcvt(E,x) ( (int)((E + FLD_OFFSET) << 1) | x ) + + // Support for variable width columns (and potentially scrolling too) +#define VARcol(E) (-1 == Fieldstab[E].width) +#ifndef SCROLLVAR_NO +#ifdef USE_X_COLHDR +#define VARright(w) (1 == w->maxpflgs && VARcol(w->procflgs[0])) +#else +#define VARright(w) ((1 == w->maxpflgs && VARcol(w->procflgs[0])) || \ + (3 == w->maxpflgs && EU_XON == w->procflgs[0] && VARcol(w->procflgs[1]))) +#endif +#define VARleft(w) (w->varcolbeg && VARright(w)) +#ifdef SCROLLV_BY_1 +#define SCROLLAMT 1 +#else +#define SCROLLAMT 8 +#endif +#endif + + // Support for a proper (visible) row #1 whenever Curwin changes + // ( or a key which might affect vertical scrolling was struck ) +#define mkVIZyes ( Curwin->begnext != 0 ) +#define mkVIZrow1 { Curwin->begnext = +1; Curwin->begtask -= 1; } +#define mkVIZrowX(n) { Curwin->begnext = (n); } +#define mkVIZoff(w) { w->begnext = 0; } + + /* Special Section: end ------------------------------------------ */ + /* /////////////////////////////////////////////////////////////// */ + + +/*###### Some Miscellaneous Macro definitions ##########################*/ + + /* Yield table size as 'int' */ +#define MAXTBL(t) (int)(sizeof(t) / sizeof(t[0])) + + /* A null-terminating strncpy, assuming strlcpy is not available. + ( and assuming callers don't need the string length returned ) */ +#define STRLCPY(dst,src) { memccpy(dst, src, '\0', sizeof(dst)); dst[sizeof(dst) - 1] = '\0'; } + + /* Used to clear all or part of our Pseudo_screen */ +#define PSU_CLREOS(y) memset(&Pseudo_screen[ROWMAXSIZ*y], '\0', Pseudo_size-(ROWMAXSIZ*y)) + +/* + * The following three macros are used to 'inline' those portions of the + * display process involved in formatting, while protecting against any + * potential embedded 'millesecond delay' escape sequences. + */ + /** PUTT - Put to Tty (used in many places) + . for temporary, possibly interactive, 'replacement' output + . may contain ANY valid terminfo escape sequences + . need NOT represent an entire screen row */ +#define PUTT(fmt,arg...) do { \ + char _str[ROWMAXSIZ]; \ + snprintf(_str, sizeof(_str), fmt, ## arg); \ + putp(_str); \ + } while (0) + + /** PUFF - Put for Frame (used in only 3 places) + . for more permanent frame-oriented 'update' output + . may NOT contain cursor motion terminfo escapes + . assumed to represent a complete screen ROW + . subject to optimization, thus MAY be discarded */ +#define PUFF(fmt,arg...) do { \ + char _str[ROWMAXSIZ]; \ + const int _len = snprintf(_str, sizeof(_str), fmt, ## arg); \ + if (Batch) { \ + char *_eol = _str + (_len < 0 ? 0 : (size_t)_len >= sizeof(_str) ? sizeof(_str)-1 : (size_t)_len); \ + while (_eol > _str && _eol[-1] == ' ') _eol--; \ + *_eol = '\0'; putp(_str); } \ + else if (Pseudo_row >= 0 && Pseudo_row < Screen_rows) { \ + char *_ptr = &Pseudo_screen[Pseudo_row++ * ROWMAXSIZ]; \ + if (!strcmp(_ptr, _str)) putp("\n"); \ + else { \ + strcpy(_ptr, _str); \ + putp(_ptr); } } \ + } while (0) + + /** POOF - Pulled Out of Frame (used in only 1 place) + . for output that is/was sent directly to the terminal + but would otherwise have been counted as a Pseudo_row */ +#define POOF(str,cap) do { \ + putp(str); putp(cap); \ + Pseudo_screen[Pseudo_row * ROWMAXSIZ] = '\0'; \ + if (Pseudo_row + 1 < Screen_rows) ++Pseudo_row; \ + } while (0) + + /* Orderly end, with any sort of message - see fmtmk */ +#define debug_END(s) { \ + void error_exit (const char *); \ + fputs(Cap_clr_scr, stdout); \ + error_exit(s); \ + } + + /* A poor man's breakpoint, if he's too lazy to learn gdb */ +#define its_YOUR_fault { *((char *)0) = '!'; } + + +/*###### Some Display Support *Data* ###################################*/ +/* ( see module top_nls.c for the nls translatable data ) */ + + /* Configuration files support */ +#define SYS_RCRESTRICT "/etc/toprc" +#define SYS_RCDEFAULTS "/etc/topdefaultrc" +#define RCF_EYECATCHER "Config File (Linux processes with windows)\n" +#define RCF_PLUS_H "\\]^_`abcdefghij" +#define RCF_PLUS_J "klmnopqrstuvwxyz" + // this next guy must never, ever change + // ( transitioned from 'char' to 'int' ) +#define RCF_XFORMED_ID 'k' + // this next guy is incremented when columns change + // ( to prevent older top versions from accessing ) +#define RCF_VERSION_ID 'k' + +#define FLD_OFFSET ( (int)'%' ) +#define FLD_ROWMAX 20 + + /* The default fields displayed and their order, + if nothing is specified by the loser, oops user. */ +#ifdef ORIG_TOPDEFS +#define DEF_FORMER "¥¨³´»½ÀÄ·º¹Å&')*+,-./012568<>?ABCFGHIJKLMNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#else +#define DEF_FORMER "¥&K¨³´»½@·º¹56ÄFÅ')*+,-./0128<>?ABCGHIJLMNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#endif + /* Pre-configured windows/field groups */ +#define JOB_FORMER "¥¦¹·º(³´Ä»½@<§Å)*+,-./012568>?ABCFGHIJKLMNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#define MEM_FORMER "¥º»<½¾¿ÀÁMBNÃD34·Å&'()*+,-./0125689FGHIJKLOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#define USR_FORMER "¥¦§¨ª°¹·ºÄÅ)+,-./1234568;<=>?@ABCFGHIJKLMNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J + // old top fields ( 'a'-'z' ) in positions 0-25 + // other suse old top fields ( '{|' ) in positions 26-27 +#define CVT_FORMER "%&*'(-0346789:;<=>?@ACDEFGML)+,./125BHIJKNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#define CVT_FLDMAX 28 + +#ifdef ORIG_TOPDEFS +#define DEF_FIELDS { \ + 75, 81, 103, 105, 119, 123, 129, 137, 111, 117, 115, 139, 76, 78, 82, 84, 86, 88, 90, 92, \ + 94, 96, 98, 100, 106, 108, 112, 120, 124, 126, 130, 132, 134, 140, 142, 144, 146, 148, 150, 152, \ + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } +#else +#define DEF_FIELDS { \ + 75, 76, 150, 81, 103, 105, 119, 123, 128, 111, 117, 115, 106, 108, 137, 140, 139, 78, 82, 84, \ + 86, 88, 90, 92, 94, 96, 98, 100, 112, 120, 124, 126, 130, 132, 134, 142, 144, 146, 148, 152, \ + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } +#endif +#define JOB_FIELDS { \ + 75, 77, 115, 111, 117, 80, 103, 105, 137, 119, 123, 128, 120, 79, 139, 82, 84, 86, 88, 90, \ + 92, 94, 96, 98, 100, 106, 108, 112, 124, 126, 130, 132, 134, 140, 142, 144, 146, 148, 150, 152, \ + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } +#define MEM_FIELDS { \ + 75, 117, 119, 120, 123, 125, 127, 129, 131, 154, 132, 156, 135, 136, 102, 104, 111, 139, 76, 78, \ + 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 106, 108, 112, 114, 140, 142, 144, 146, 148, \ + 150, 152, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } +#define USR_FIELDS { \ + 75, 77, 79, 81, 85, 97, 115, 111, 117, 137, 139, 82, 86, 88, 90, 92, 94, 98, 100, 102, \ + 104, 106, 108, 112, 118, 120, 122, 124, 126, 128, 130, 132, 134, 140, 142, 144, 146, 148, 150, 152, \ + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } + + /* The default values for the local config file */ +#define DEF_RCFILE { \ + RCF_VERSION_ID, 0, 1, DEF_DELAY, 0, { \ + { EU_CPU, DEF_WINFLGS, 0, DEF_GRAPHS2, 1, 0, 0, \ + COLOR_RED, COLOR_RED, COLOR_YELLOW, COLOR_RED, \ + "Def", DEF_FIELDS }, \ + { EU_PID, ALT_WINFLGS, 0, ALT_GRAPHS2, 0, 0, 0, \ + COLOR_CYAN, COLOR_CYAN, COLOR_WHITE, COLOR_CYAN, \ + "Job", JOB_FIELDS }, \ + { EU_MEM, ALT_WINFLGS, 0, ALT_GRAPHS2, 0, 0, 0, \ + COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLUE, COLOR_MAGENTA, \ + "Mem", MEM_FIELDS }, \ + { EU_UEN, ALT_WINFLGS, 0, ALT_GRAPHS2, 0, 0, 0, \ + COLOR_YELLOW, COLOR_YELLOW, COLOR_GREEN, COLOR_YELLOW, \ + "Usr", USR_FIELDS } \ + }, 0, DEF_SCALES2, 0, 0 } + + /* Summary Lines specially formatted string(s) -- + see 'show_special' for syntax details + other cautions. */ +#define LOADAV_line "%s -%s\n" +#define LOADAV_line_alt "%s~6 -%s\n" + +/*###### For Piece of mind #############################################*/ + + /* just sanity check(s)... */ +#if defined(RECALL_FIXED) && defined(TERMIOS_ONLY) +# error 'RECALL_FIXED' conflicts with 'TERMIOS_ONLY' +#endif +#if (LRGBUFSIZ < SCREENMAX) +# error 'LRGBUFSIZ' must NOT be less than 'SCREENMAX' +#endif +#if defined(TERMIOS_ONLY) +# warning 'TERMIOS_ONLY' disables input recall and makes man doc incorrect +#endif +#if defined(MEMGRAPH_OLD) +# warning 'MEMGRAPH_OLD' will make the man document Section 2c. misleading +#endif +#if defined(SCALE_FORMER) && defined(SCALE_POSTFX) +# warning 'SCALE_POSTFX' is ignored when 'SCALE_FORMER' is active +#endif +#if defined(USE_X_COLHDR) +# warning 'USE_X_COLHDR' makes parts of man page misleading (4e, 5d & 5e) +#endif +#if defined(TOG4_SEP_STD) && defined(TOG4_SEP_OFF) +# warning 'TOG4_SEP_STD' has no effect when 'TOG4_SEP_OFF' is active +#endif + +/*###### Some Prototypes (ha!) #########################################*/ + + /* These 'prototypes' are here exclusively for documentation purposes. */ + /* ( see the find_string routine for the one true required prototype ) */ +/*------ Tiny useful routine(s) ----------------------------------------*/ +//atic const char *fmtmk (const char *fmts, ...); +//atic inline int mlen (const int *mem); +//atic inline int *msch (const int *mem, int obj, int max); +//atic inline char *scat (char *dst, const char *src); +//atic const char *tg2 (int x, int y); +/*------ Exit/Interrupt routines ---------------------------------------*/ +//atic void at_eoj (void); +//atic void bye_bye (const char *str); +//atic void error_exit (const char *str); +//atic void sig_abexit (int sig); +//atic void sig_endpgm (int dont_care_sig); +//atic void sig_paused (int dont_care_sig); +//atic void sig_resize (int dont_care_sig); +/*------ Special UTF-8 Multi-Byte support ------------------------------*/ +/*atic char UTF8_tab[] = { ... } */ +//atic inline int utf8_cols (const unsigned char *p, int n); +//atic int utf8_delta (const char *str); +//atic int utf8_embody (const char *str, int width); +//atic const char *utf8_justify (const char *str, int width, int justr); +//atic int utf8_proper_col (const char *str, int col, int tophysical); +/*------ Misc Color/Display support ------------------------------------*/ +//atic void capsmk (WIN_t *q); +//atic void show_msg (const char *str); +//atic int show_pmt (const char *str); +//atic void show_scroll (void); +//atic void show_special (int interact, const char *glob); +/*------ Low Level Memory/Keyboard/File I/O support --------------------*/ +//atic void *alloc_c (size_t num); +//atic void *alloc_r (void *ptr, size_t num); +//atic char *alloc_s (const char *str); +//atic inline int ioa (struct timespec *ts); +//atic int ioch (int ech, char *buf, unsigned cnt); +//atic int iokey (int action); +//atic char *ioline (const char *prompt); +//atic int mkfloat (const char *str, float *num, int whole); +//atic int readfile (FILE *fp, char **baddr, size_t *bsize, size_t *bread); +/*------ Small Utility routines ----------------------------------------*/ +//atic float get_float (const char *prompt); +//atic int get_int (const char *prompt); +//atic inline const char *hex_make (long num, int noz); +//atic const char *user_certify (WIN_t *q, const char *str, char typ); +/*------ Basic Formatting support --------------------------------------*/ +//atic inline const char *justify_pad (const char *str, int width, int justr); +//atic inline const char *make_chr (const char ch, int width, int justr); +//atic inline const char *make_num (long num, int width, int justr, int col, int noz); +//atic inline const char *make_str (const char *str, int width, int justr, int col); +//atic inline const char *make_str_utf8 (const char *str, int width, int justr, int col); +//atic const char *scale_mem (int target, float num, int width, int justr); +//atic const char *scale_num (float num, int width, int justr); +//atic const char *scale_pcnt (float num, int width, int justr, int xtra); +//atic const char *scale_tics (TIC_t tics, int width, int justr, int target); +/*------ Fields Management support -------------------------------------*/ +/*atic struct Fieldstab[] = { ... } */ +//atic void adj_geometry (void); +//atic void build_headers (void); +//atic void calibrate_fields (void); +//atic void display_fields (int focus, int extend); +//atic void fields_utility (void); +//atic inline void widths_resize (void); +//atic void zap_fieldstab (void); +/*------ Library Interface (as separate threads) -----------------------*/ +//atic void *cpus_refresh (void *unused); +//atic void *memory_refresh (void *unused); +//atic void *tasks_refresh (void *unused); +/*------ Inspect Other Output ------------------------------------------*/ +//atic void insp_cnt_nl (void); +#ifndef INSP_OFFDEMO +//atic void insp_do_demo (char *fmts, int pid); +#endif +//atic void insp_do_file (char *fmts, int pid); +//atic void insp_do_pipe (char *fmts, int pid); +//atic inline int insp_find_ofs (int col, int row); +//atic void insp_find_str (int ch, int *col, int *row); +//atic void insp_mkrow_raw (int col, int row); +//atic void insp_mkrow_utf8 (int col, int row); +//atic void insp_show_pgs (int col, int row, int max); +//atic int insp_view_choice (struct pids_stack *p); +//atic void inspection_utility (int pid); +/*------ Other Filtering ------------------------------------------------*/ +//atic const char *osel_add (WIN_t *q, int ch, char *glob, int push); +//atic void osel_clear (WIN_t *q); +//atic inline int osel_matched (const WIN_t *q, FLG_t enu, const char *str); +/*------ Startup routines ----------------------------------------------*/ +//atic void before (char *me); +//atic int cfg_xform (WIN_t *q, char *flds, const char *defs); +//atic int config_insp (FILE *fp, char *buf, size_t size); +//atic int config_osel (FILE *fp, char *buf, size_t size); +//atic const char *configs_file (FILE *fp, const char *name, float *delay); +//atic int configs_path (const char *const fmts, ...); +//atic void configs_reads (void); +//atic void parse_args (int argc, char **argv); +//atic void signals_set (void); +//atic void whack_terminal (void); +/*------ Windows/Field Groups support ----------------------------------*/ +//atic void win_names (WIN_t *q, const char *name); +//atic void win_reset (WIN_t *q); +//atic WIN_t *win_select (int ch); +//atic int win_warn (int what); +//atic void wins_clrhlp (WIN_t *q, int save); +//atic void wins_colors (void); +//atic void wins_reflag (int what, int flg); +//atic void wins_stage_1 (void); +//atic void wins_stage_2 (void); +//atic inline int wins_usrselect (const WIN_t *q, int idx); +/*------ Forest View support -------------------------------------------*/ +//atic void forest_adds (const int self, int level); +//atic void forest_begin (WIN_t *q); +//atic void forest_config (WIN_t *q); +//atic inline const char *forest_display (const WIN_t *q, int idx); +/*------ Special Separate Bottom Window support ------------------------*/ +//atic void bot_do (const char *str, int focus); +//atic int bot_focus_str (const char *hdr, const char *str); +//atic int bot_focus_strv (const char *hdr, const char **strv); +//atic void *bot_item_hlp (struct pids_stack *p); +//atic void bot_item_show (void); +//atic void bot_item_toggle (int what, const char *head, char sep); +/*------ Interactive Input Tertiary support ----------------------------*/ +//atic inline int find_ofs (const WIN_t *q, const char *buf); +//atic void find_string (int ch); +//atic void help_view (void); +//atic void other_filters (int ch); +//atic void write_rcfile (void); +/*------ Interactive Input Secondary support (do_key helpers) ----------*/ +//atic void keys_global (int ch); +//atic void keys_summary (int ch); +//atic void keys_task (int ch); +//atic void keys_window (int ch); +//atic void keys_xtra (int ch); +/*------ Tertiary summary display support (summary_show helpers) -------*/ +//atic struct rx_st *sum_rx (struct graph_parms *these); +//atic inline int sum_see (const char *str, int nobuf); +//atic int sum_tics (struct stat_stack *this, const char *pfx, int nobuf); +//atic int sum_unify (struct stat_stack *this, int nobuf); +/*------ Secondary summary display support (summary_show helpers) ------*/ +//atic void do_cpus (void); +//atic void do_memory (void); +/*------ Main Screen routines ------------------------------------------*/ +//atic void do_key (int ch); +//atic void summary_show (void); +//atic const char *task_show (const WIN_t *q, int idx); +//atic void window_hlp (void); +//atic int window_show (WIN_t *q, int wmax); +/*------ Entry point plus two ------------------------------------------*/ +//atic void frame_hlp (int wix, int max); +//atic void frame_make (void); +// int main (int argc, char *argv[]); + +#endif /* _Itop */ + diff --git a/src/top/top_nls.c b/src/top/top_nls.c new file mode 100644 index 0000000..d7ab8dc --- /dev/null +++ b/src/top/top_nls.c @@ -0,0 +1,867 @@ +/* top_nls.c - provide the basis for future nls translations */ +/* + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net + * + * This file may be used subject to the terms and conditions of the + * GNU Library General Public License Version 2, or any later version + * at your option, as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + */ +/* For contributions to this program, the author wishes to thank: + * Craig Small, <csmall@dropbear.xyz> + * Sami Kerola, <kerolasa@iki.fi> + */ + +#include <locale.h> +#include <stdio.h> +#include <string.h> + +#include "nls.h" + +#include "top.h" +#include "top_nls.h" + +#ifdef NLS_VALIDATE +#include <stdlib.h> +#endif + + + /* + * The provision excluding some strings is intended to be + * used very sparingly. It exists in case we collide with + * some translation project person in a position to delay + * a future release over his or her personal preferences. + * + * If it's ever enabled, it will produce a fatal compiler + * error as our only option since those gettext tools are + * far too primitive to be influenced with a conditional. + * They always ignore a '_X()' macro no matter its state. */ + +#ifndef NLS_INCLUDED +# define _X(str) (str) +#else +# define _X(str) +# error instead of this #define, restore the true gettext macro(s) +#endif + + // Programmer Note(s): + // Preparation --------------------------------------------- + // Unless you have *something* following the gettext macro, + // xgettext will refuse to see any TRANSLATORS comments. + // Thus empty strings have been added for potential future + // comment additions. + // + // Also, by omitting the argument for the --add-comments + // XGETTEXT_OPTION in po/Makevars, *any* preceding c style + // comment will be propagated to the .pot file, providing + // that the gettext macro isn't empty as discussed above. + // However, this is far too aggressive so we have chosen + // the word 'Translation' to denote xgettext comments. + // + // /* Need Not Say 'TRANSLATORS': ... + // snprintf(buf, sizeof(buf), "%s", _( // unseen comment + // + // /* Translation Hint: ... + // snprintf(buf, sizeof(buf), "%s", _("" // now it's seen! + // + // Translation, from po/ directory after make -------------- + // ( this is the procedure used before any translations were ) + // ( available in the po/ directory, which contained only the ) + // ( procps-ng.pot, this domain's template file. ) + // + // ( below: ll_CC = language/country as in 'zh_CN' or 'en_AU' ) + // + // msginit --locale=ll_CC --no-wrap + // . creates a ll_CC.po file from the template procps-ng.pot + // . may also duplicate msgid as msgstr if languages similar + // msgen --no-wrap ll_CC.po --output-file=ll_CC.po + // . duplicates every msgid literal as msgstr value + // . this is the file that's edited + // . replace "Content-Type: ... charset=ASCII\n" + // with "... charset=UTF-8\n" + // . translate msgstr values, leaving msgid unchanged + // msgfmt ll_CC.po --strict --output-file=procps-ng.mo + // . after which ensure, chmod 644 + // . then copy + // to /usr/share/locale-langpack/ll_CC/LC_MESSAGES/ + // or /usr/share/locale/ll_CC/LC_MESSAGES/ + // Testing ------------------------------------------------- + // export LC_ALL= && export LANGUAGE=ll_CC + // run some capable program like top + // + + /* + * These are our string tables with the following contents: + * Head : column headings with varying size limits + * Desc : fields descriptions not to exceed 20 screen positions + * Norm : regular text possibly also containing c-format specifiers + * Uniq : show_special specially formatted strings + * + * The latter table presents the greatest translation challenge ! + * + * We go to the trouble of creating the nls string tables to achieve + * these objectives: + * + the overhead of repeated runtime calls to gettext() + * will be avoided + * + the order of the strings in the template (.pot) file + * can be completely controlled + * + none of the important translator only comments will + * clutter and obscure the main program + */ +const char *Head_nlstab[EU_MAXPFLGS]; +const char *Desc_nlstab[EU_MAXPFLGS]; +const char *Norm_nlstab[norm_MAX]; +const char *Uniq_nlstab[uniq_MAX]; + + + /* + * This routine builds the nls table containing plain text only + * used as the field descriptions. Each translated line MUST be + * kept to a maximum of 20 characters or less! */ +static void build_two_nlstabs (void) { + +/* Translation Notes ------------------------------------------------ + . It is strongly recommend that the --no-wrap command line option + . be used with all supporting translation tools, when available. + . + . The following line pairs contain only plain text and consist of: + . 1) a field name/column header - mostly upper case + . 2) the related description - both upper and lower case + . + . To avoid truncation on the main top display, each column header + . is noted with its maximum size, while a few are 'variable' width. + . Names for the latter should probably be 10 or fewer characters. + . + . Those fields shown with a '+' are also eligible for user resizing + . using the 'X' command. That means the default width might produce + . truncation but need not if widened (see the man page 'X' command). + . + . All headers are subject to a maximum of 8 on the Fields Management + . screen where truncation is entirely acceptable. + . + . The associated descriptions are always limited to 20 characters, + . and are used only on the Fields Management screen. + . + . In all cases, fewer characters are just fine. + . */ + +/* Translation Hint: maximum 'PID' = 5 */ + Head_nlstab[EU_PID] = _("PID"); + Desc_nlstab[EU_PID] = _("Process Id"); +/* Translation Hint: maximum 'PPID' = 5 */ + Head_nlstab[EU_PPD] = _("PPID"); + Desc_nlstab[EU_PPD] = _("Parent Process pid"); +/* Translation Hint: maximum 'UID' = 5 + */ + Head_nlstab[EU_UED] = _("UID"); + Desc_nlstab[EU_UED] = _("Effective User Id"); +/* Translation Hint: maximum 'USER' = 8 + */ + Head_nlstab[EU_UEN] = _("USER"); + Desc_nlstab[EU_UEN] = _("Effective User Name"); +/* Translation Hint: maximum 'RUID' = 5 + */ + Head_nlstab[EU_URD] = _("RUID"); + Desc_nlstab[EU_URD] = _("Real User Id"); +/* Translation Hint: maximum 'RUSER' = 8 + */ + Head_nlstab[EU_URN] = _("RUSER"); + Desc_nlstab[EU_URN] = _("Real User Name"); +/* Translation Hint: maximum 'SUID' = 5 + */ + Head_nlstab[EU_USD] = _("SUID"); + Desc_nlstab[EU_USD] = _("Saved User Id"); +/* Translation Hint: maximum 'SUSER' = 8 + */ + Head_nlstab[EU_USN] = _("SUSER"); + Desc_nlstab[EU_USN] = _("Saved User Name"); +/* Translation Hint: maximum 'GID' = 5 + */ + Head_nlstab[EU_GID] = _("GID"); + Desc_nlstab[EU_GID] = _("Group Id"); +/* Translation Hint: maximum 'GROUP' = 8 + */ + Head_nlstab[EU_GRP] = _("GROUP"); + Desc_nlstab[EU_GRP] = _("Group Name"); +/* Translation Hint: maximum 'PGRP' = 5 */ + Head_nlstab[EU_PGD] = _("PGRP"); + Desc_nlstab[EU_PGD] = _("Process Group Id"); +/* Translation Hint: maximum 'TTY' = 8 + */ + Head_nlstab[EU_TTY] = _("TTY"); + Desc_nlstab[EU_TTY] = _("Controlling Tty"); +/* Translation Hint: maximum 'TPGID' = 5 */ + Head_nlstab[EU_TPG] = _("TPGID"); + Desc_nlstab[EU_TPG] = _("Tty Process Grp Id"); +/* Translation Hint: maximum 'SID' = 5 */ + Head_nlstab[EU_SID] = _("SID"); + Desc_nlstab[EU_SID] = _("Session Id"); +/* Translation Hint: maximum 'PR' = 3 */ + Head_nlstab[EU_PRI] = _("PR"); + Desc_nlstab[EU_PRI] = _("Priority"); +/* Translation Hint: maximum 'NI' = 3 */ + Head_nlstab[EU_NCE] = _("NI"); + Desc_nlstab[EU_NCE] = _("Nice Value"); +/* Translation Hint: maximum 'nTH' = 3 */ + Head_nlstab[EU_THD] = _("nTH"); + Desc_nlstab[EU_THD] = _("Number of Threads"); +/* Translation Hint: maximum 'P' = 1 */ + Head_nlstab[EU_CPN] = _("P"); + Desc_nlstab[EU_CPN] = _("Last Used Cpu (SMP)"); +/* Translation Hint: maximum '%CPU' = 4 */ + Head_nlstab[EU_CPU] = _("%CPU"); + Desc_nlstab[EU_CPU] = _("CPU Usage"); +/* Translation Hint: maximum '' = 6 */ + Head_nlstab[EU_TME] = _("TIME"); + Desc_nlstab[EU_TME] = _("CPU Time"); +/* Translation Hint: maximum 'TIME+' = 9 */ + Head_nlstab[EU_TM2] = _("TIME+"); + Desc_nlstab[EU_TM2] = _("CPU Time, hundredths"); +/* Translation Hint: maximum '%MEM' = 4 */ + Head_nlstab[EU_MEM] = _("%MEM"); + Desc_nlstab[EU_MEM] = _("Memory Usage (RES)"); +/* Translation Hint: maximum 'VIRT' = 7 */ + Head_nlstab[EU_VRT] = _("VIRT"); + Desc_nlstab[EU_VRT] = _("Virtual Image (KiB)"); +/* Translation Hint: maximum 'SWAP' = 6 */ + Head_nlstab[EU_SWP] = _("SWAP"); + Desc_nlstab[EU_SWP] = _("Swapped Size (KiB)"); +/* Translation Hint: maximum 'RES' = 6 */ + Head_nlstab[EU_RES] = _("RES"); + Desc_nlstab[EU_RES] = _("Resident Size (KiB)"); +/* Translation Hint: maximum 'CODE' = 4 */ + Head_nlstab[EU_COD] = _("CODE"); + Desc_nlstab[EU_COD] = _("Code Size (KiB)"); +/* Translation Hint: maximum 'DATA' = 7 */ + Head_nlstab[EU_DAT] = _("DATA"); + Desc_nlstab[EU_DAT] = _("Data+Stack (KiB)"); +/* Translation Hint: maximum 'SHR' = 6 */ + Head_nlstab[EU_SHR] = _("SHR"); + Desc_nlstab[EU_SHR] = _("Shared Memory (KiB)"); +/* Translation Hint: maximum 'nMaj' = 4 */ + Head_nlstab[EU_FL1] = _("nMaj"); + Desc_nlstab[EU_FL1] = _("Major Page Faults"); +/* Translation Hint: maximum 'nMin' = 4 */ + Head_nlstab[EU_FL2] = _("nMin"); + Desc_nlstab[EU_FL2] = _("Minor Page Faults"); +/* Translation Hint: maximum 'nDRT' = 4 */ + Head_nlstab[EU_DRT] = _("nDRT"); + Desc_nlstab[EU_DRT] = _("Dirty Pages Count"); +/* Translation Hint: maximum 'S' = 1 */ + Head_nlstab[EU_STA] = _("S"); + Desc_nlstab[EU_STA] = _("Process Status"); +/* Translation Hint: maximum 'COMMAND' = variable */ + Head_nlstab[EU_CMD] = _("COMMAND"); + Desc_nlstab[EU_CMD] = _("Command Name/Line"); +/* Translation Hint: maximum 'WCHAN' = 10 + */ + Head_nlstab[EU_WCH] = _("WCHAN"); + Desc_nlstab[EU_WCH] = _("Sleeping in Function"); +/* Translation Hint: maximum 'Flags' = 8 */ + Head_nlstab[EU_FLG] = _("Flags"); + Desc_nlstab[EU_FLG] = _("Task Flags <sched.h>"); +/* Translation Hint: maximum 'CGROUPS' = variable */ + Head_nlstab[EU_CGR] = _("CGROUPS"); + Desc_nlstab[EU_CGR] = _("Control Groups"); +/* Translation Hint: maximum 'SUPGIDS' = variable */ + Head_nlstab[EU_SGD] = _("SUPGIDS"); + Desc_nlstab[EU_SGD] = _("Supp Groups IDs"); +/* Translation Hint: maximum 'SUPGRPS' = variable */ + Head_nlstab[EU_SGN] = _("SUPGRPS"); + Desc_nlstab[EU_SGN] = _("Supp Groups Names"); +/* Translation Hint: maximum 'TGID' = 5 */ + Head_nlstab[EU_TGD] = _("TGID"); + Desc_nlstab[EU_TGD] = _("Thread Group Id"); +/* Translation Hint: maximum 'OOMa' = 5 */ + Head_nlstab[EU_OOA] = _("OOMa"); + Desc_nlstab[EU_OOA] = _("OOMEM Adjustment"); +/* Translation Hint: maximum 'OOMs' = 4 */ + Head_nlstab[EU_OOM] = _("OOMs"); + Desc_nlstab[EU_OOM] = _("OOMEM Score current"); +/* Translation Hint: maximum 'ENVIRON' = variable */ + Head_nlstab[EU_ENV] = _("ENVIRON"); +/* Translation Hint: the abbreviation 'vars' below is shorthand for + 'variables' */ + Desc_nlstab[EU_ENV] = _("Environment vars"); +/* Translation Hint: maximum 'vMj' = 3 */ + Head_nlstab[EU_FV1] = _("vMj"); + Desc_nlstab[EU_FV1] = _("Major Faults delta"); +/* Translation Hint: maximum 'vMn' = 3 */ + Head_nlstab[EU_FV2] = _("vMn"); + Desc_nlstab[EU_FV2] = _("Minor Faults delta"); +/* Translation Hint: maximum 'USED' = 6 */ + Head_nlstab[EU_USE] = _("USED"); + Desc_nlstab[EU_USE] = _("Res+Swap Size (KiB)"); +/* Translation Hint: maximum 'nsIPC' = 10 + */ + Head_nlstab[EU_NS1] = _("nsIPC"); + Desc_nlstab[EU_NS1] = _("IPC namespace Inode"); +/* Translation Hint: maximum 'nsMNT' = 10 + */ + Head_nlstab[EU_NS2] = _("nsMNT"); + Desc_nlstab[EU_NS2] = _("MNT namespace Inode"); +/* Translation Hint: maximum 'nsNET' = 10 + */ + Head_nlstab[EU_NS3] = _("nsNET"); + Desc_nlstab[EU_NS3] = _("NET namespace Inode"); +/* Translation Hint: maximum 'nsPID' = 10 + */ + Head_nlstab[EU_NS4] = _("nsPID"); + Desc_nlstab[EU_NS4] = _("PID namespace Inode"); +/* Translation Hint: maximum 'nsUSER' = 10 + */ + Head_nlstab[EU_NS5] = _("nsUSER"); + Desc_nlstab[EU_NS5] = _("USER namespace Inode"); +/* Translation Hint: maximum 'nsUTS' = 10 + */ + Head_nlstab[EU_NS6] = _("nsUTS"); + Desc_nlstab[EU_NS6] = _("UTS namespace Inode"); +/* Translation Hint: maximum 'LXC' = 8 + */ + Head_nlstab[EU_LXC] = _("LXC"); + Desc_nlstab[EU_LXC] = _("LXC container name"); +/* Translation Hint: maximum 'RSan' = 6 */ + Head_nlstab[EU_RZA] = _("RSan"); + Desc_nlstab[EU_RZA] = _("RES Anonymous (KiB)"); +/* Translation Hint: maximum 'RSfd' = 6 */ + Head_nlstab[EU_RZF] = _("RSfd"); + Desc_nlstab[EU_RZF] = _("RES File-based (KiB)"); +/* Translation Hint: maximum 'RSlk' = 6 */ + Head_nlstab[EU_RZL] = _("RSlk"); + Desc_nlstab[EU_RZL] = _("RES Locked (KiB)"); +/* Translation Hint: maximum 'RSsh' = 6 */ + Head_nlstab[EU_RZS] = _("RSsh"); + Desc_nlstab[EU_RZS] = _("RES Shared (KiB)"); +/* Translation Hint: maximum 'CGNAME' = variable */ + Head_nlstab[EU_CGN] = _("CGNAME"); + Desc_nlstab[EU_CGN] = _("Control Group name"); +/* Translation Hint: maximum 'NU' = 2 */ + Head_nlstab[EU_NMA] = _("NU"); + Desc_nlstab[EU_NMA] = _("Last Used NUMA node"); +/* Translation Hint: maximum 'LOGID' = 5 + */ + Head_nlstab[EU_LID] = _("LOGID"); + Desc_nlstab[EU_LID] = _("Login User Id"); +/* Translation Hint: maximum 'EXE' = variable */ + Head_nlstab[EU_EXE] = _("EXE"); + Desc_nlstab[EU_EXE] = _("Executable Path"); +/* Translation Hint: With the next 5 fields, notice how an extra space + has been added ahead of one 'KiB' so that they all + align. You need not preserve such alignment. */ +/* Translation Hint: maximum 'RSS' = 6 */ + Head_nlstab[EU_RSS] = _("RSS"); + Desc_nlstab[EU_RSS] = _("Res Mem (smaps), KiB"); +/* Translation Hint: maximum 'PSS' = 6 */ + Head_nlstab[EU_PSS] = _("PSS"); + Desc_nlstab[EU_PSS] = _("Proportion RSS, KiB"); +/* Translation Hint: maximum 'PSan' = 6 */ + Head_nlstab[EU_PZA] = _("PSan"); + Desc_nlstab[EU_PZA] = _("Proportion Anon, KiB"); +/* Translation Hint: maximum 'PSfd' = 6 */ + Head_nlstab[EU_PZF] = _("PSfd"); + Desc_nlstab[EU_PZF] = _("Proportion File, KiB"); +/* Translation Hint: maximum 'PSsh' = 6 */ + Head_nlstab[EU_PZS] = _("PSsh"); + Desc_nlstab[EU_PZS] = _("Proportion Shrd, KiB"); +/* Translation Hint: maximum 'USS' = 6 */ + Head_nlstab[EU_USS] = _("USS"); + Desc_nlstab[EU_USS] = _("Unique RSS, KiB"); +/* Translation Hint: maximum 'ioR' = 6 */ + Head_nlstab[EU_IRB] = _("ioR"); + Desc_nlstab[EU_IRB] = _("I/O Bytes Read"); +/* Translation Hint: maximum 'ioRop' = 5 */ + Head_nlstab[EU_IRO] = _("ioRop"); + Desc_nlstab[EU_IRO] = _("I/O Read Operations"); +/* Translation Hint: maximum 'ioW' = 6 */ + Head_nlstab[EU_IWB] = _("ioW"); + Desc_nlstab[EU_IWB] = _("I/O Bytes Written"); +/* Translation Hint: maximum 'ioWop' = 5 */ + Head_nlstab[EU_IWO] = _("ioWop"); + Desc_nlstab[EU_IWO] = _("I/O Write Operations"); +/* Translation Hint: maximum 'AGID' = 5 */ + Head_nlstab[EU_AGI] = _("AGID"); + Desc_nlstab[EU_AGI] = _("Autogroup Identifier"); +/* Translation Hint: maximum 'AGNI' = 4 */ + Head_nlstab[EU_AGN] = _("AGNI"); + Desc_nlstab[EU_AGN] = _("Autogroup Nice Value"); +/* Translation Hint: maximum 'STARTED' = 7 */ + Head_nlstab[EU_TM3] = _("STARTED"); + Desc_nlstab[EU_TM3] = _("Start Time from boot"); +/* Translation Hint: maximum 'ELAPSED' = 7 */ + Head_nlstab[EU_TM4] = _("ELAPSED"); + Desc_nlstab[EU_TM4] = _("Elapsed Running Time"); +/* Translation Hint: maximum '%CUU' = 6 */ + Head_nlstab[EU_CUU] = _("%CUU"); + Desc_nlstab[EU_CUU] = _("CPU Utilization"); +/* Translation Hint: maximum '%CUC' = 7 */ + Head_nlstab[EU_CUC] = _("%CUC"); + Desc_nlstab[EU_CUC] = _("Utilization + child"); +/* Translation Hint: maximum 'nsCGROUP' = 10 + */ + Head_nlstab[EU_NS7] = _("nsCGROUP"); + Desc_nlstab[EU_NS7] = _("CGRP namespace Inode"); +/* Translation Hint: maximum 'nsTIME' = 10 + */ + Head_nlstab[EU_NS8] = _("nsTIME"); + Desc_nlstab[EU_NS8] = _("TIME namespace Inode"); +} + + + /* + * This routine builds the nls table containing both plain text + * and regular c-format strings. */ +static void build_norm_nlstab (void) { + +/* Translation Notes ------------------------------------------------ + . It is strongly recommend that the --no-wrap command line option + . be used with all supporting translation tools, when available. + . + . This group of lines contains both plain text and c-format strings. + . + . Some strings reflect switches used to affect the running program + . and should not be translated without also making corresponding + . c-code logic changes. + . */ + + Norm_nlstab[EXIT_signals_fmt] = _("" + "\tsignal %d (%s) was caught by %s, please\n" + "\tsend bug reports to <procps@freelists.org>\n"); + Norm_nlstab[HELP_cmdline_fmt] = _X("\n" + "Usage:\n" + " %s [options]\n" + "\n" + "Options:\n" + " -b, --batch-mode run in non-interactive batch mode\n" + " -c, --cmdline-toggle reverse last remembered 'c' state\n" + " -d, --delay =SECS [.TENTHS] iterative delay as SECS [.TENTHS]\n" + " -E, --scale-summary-mem =SCALE set mem as: k,m,g,t,p,e for SCALE\n" + " -e, --scale-task-mem =SCALE set mem with: k,m,g,t,p for SCALE\n" + " -H, --threads-show show tasks plus all their threads\n" + " -i, --idle-toggle reverse last remembered 'i' state\n" + " -n, --iterations =NUMBER exit on maximum iterations NUMBER\n" + " -O, --list-fields output all field names, then exit\n" + " -o, --sort-override =FIELD force sorting on this named FIELD\n" + " -p, --pid =PIDLIST monitor only the tasks in PIDLIST\n" + " -S, --accum-time-toggle reverse last remembered 'S' state\n" + " -s, --secure-mode run with secure mode restrictions\n" + " -U, --filter-any-user =USER show only processes owned by USER\n" + " -u, --filter-only-euser =USER show only processes owned by USER\n" + " -w, --width [=COLUMNS] change print width [,use COLUMNS]\n" + " -1, --single-cpu-toggle reverse last remembered '1' state\n" + "\n" + " -h, --help display this help text, then exit\n" + " -V, --version output version information & exit\n" + "\n" + "For more details see top(1)."); + Norm_nlstab[BAD_delayint_fmt] = _("bad delay interval '%s'"); + Norm_nlstab[BAD_niterate_fmt] = _("bad iterations argument '%s'"); + Norm_nlstab[LIMIT_exceed_fmt] = _("pid limit (%d) exceeded"); + Norm_nlstab[BAD_mon_pids_fmt] = _("bad pid '%s'"); + Norm_nlstab[MISSING_args_fmt] = _("-%c argument missing"); + Norm_nlstab[BAD_widtharg_fmt] = _("bad width arg '%s'"); + Norm_nlstab[UNKNOWN_opts_fmt] = _("unknown option '%s'"); + Norm_nlstab[DELAY_secure_txt] = _("-d disallowed in \"secure\" mode"); + Norm_nlstab[DELAY_badarg_txt] = _("-d requires positive argument"); + Norm_nlstab[ON_word_only_txt] = _("On"); + Norm_nlstab[OFF_one_word_txt] = _("Off"); + Norm_nlstab[VERSION_opts_fmt] = _("%s from %s"); + Norm_nlstab[FOREST_modes_fmt] = _("Forest mode %s"); + Norm_nlstab[FAIL_tty_get_txt] = _("failed tty get"); + Norm_nlstab[FAIL_tty_set_fmt] = _("failed tty set: %s"); + Norm_nlstab[CHOOSE_group_txt] = _("Choose field group (1 - 4)"); + Norm_nlstab[DISABLED_cmd_txt] = _("Command disabled, 'A' mode required"); + Norm_nlstab[DISABLED_win_fmt] = _("Command disabled, activate %s with '-' or '_'"); + Norm_nlstab[COLORS_nomap_txt] = _("No colors to map!"); + Norm_nlstab[FAIL_rc_open_fmt] = _("Failed '%s' open: %s"); + Norm_nlstab[WRITE_rcfile_fmt] = _("Wrote configuration to '%s'"); + Norm_nlstab[DELAY_change_fmt] = _("Change delay from %.1f to"); + Norm_nlstab[THREADS_show_fmt] = _("Show threads %s"); + Norm_nlstab[IRIX_curmode_fmt] = _("Irix mode %s"); + Norm_nlstab[GET_pid2kill_fmt] = _("PID to signal/kill [default pid = %d]"); + Norm_nlstab[GET_sigs_num_fmt] = _("Send pid %d signal [%d/sigterm]"); + Norm_nlstab[FAIL_signals_fmt] = _("Failed signal pid '%d' with '%d': %s"); + Norm_nlstab[BAD_signalid_txt] = _("Invalid signal"); + Norm_nlstab[GET_pid2nice_fmt] = _("PID to renice [default pid = %d]"); + Norm_nlstab[GET_nice_num_fmt] = _("Renice PID %d to value"); + Norm_nlstab[FAIL_re_nice_fmt] = _("Failed renice of PID %d to %d: %s"); + Norm_nlstab[NAME_windows_fmt] = _("Rename window '%s' to (1-3 chars)"); + Norm_nlstab[TIME_accumed_fmt] = _("Cumulative time %s"); + Norm_nlstab[GET_max_task_fmt] = _("Maximum tasks = %d, change to (0 is unlimited)"); + Norm_nlstab[BAD_max_task_txt] = _("Invalid maximum"); + Norm_nlstab[GET_user_ids_txt] = _("Which user (blank for all)"); + Norm_nlstab[UNKNOWN_cmds_txt] = _("Unknown command - try 'h' for help"); + Norm_nlstab[SCROLL_coord_fmt] = _("scroll coordinates: y = %d/%d (tasks), x = %d/%d (fields)"); + Norm_nlstab[FAIL_alloc_c_txt] = _("failed memory allocate"); + Norm_nlstab[FAIL_alloc_r_txt] = _("failed memory re-allocate"); + Norm_nlstab[BAD_numfloat_txt] = _("Unacceptable floating point"); + Norm_nlstab[BAD_username_txt] = _("Invalid user"); + Norm_nlstab[FOREST_views_txt] = _("forest view"); + Norm_nlstab[FAIL_widepid_txt] = _("failed pid maximum size test"); + Norm_nlstab[FAIL_widecpu_txt] = _("failed number of cpus test"); + Norm_nlstab[RC_bad_files_fmt] = _("incompatible rcfile, you should delete '%s'"); + Norm_nlstab[RC_bad_entry_fmt] = _("window entry #%d corrupt, please delete '%s'"); + Norm_nlstab[NOT_onsecure_txt] = _("Unavailable in secure mode"); + Norm_nlstab[NOT_smp_cpus_txt] = _("Only 1 cpu detected"); + Norm_nlstab[BAD_integers_txt] = _("Unacceptable integer"); + Norm_nlstab[SELECT_clash_txt] = _("conflicting process selections (U/p/u)"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . kibibytes (1024 bytes) */ + Norm_nlstab[AMT_kilobyte_txt] = _("KiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . mebibytes (1,048,576 bytes) */ + Norm_nlstab[AMT_megabyte_txt] = _("MiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . gibibytes (1,073,741,824 bytes) */ + Norm_nlstab[AMT_gigabyte_txt] = _("GiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . tebibytes (1,099,511,627,776 bytes) */ + Norm_nlstab[AMT_terabyte_txt] = _("TiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . pebibytes (1,024 tebibytes) */ + Norm_nlstab[AMT_petabyte_txt] = _("PiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . exbibytes (1,024 pebibytes) */ + Norm_nlstab[AMT_exxabyte_txt] = _("EiB"); + Norm_nlstab[WORD_threads_txt] = _("Threads"); + Norm_nlstab[WORD_process_txt] = _("Tasks"); +/* Translation Hint: The following "word" is meant to represent either a single + . cpu or all of the processors in a multi-processor computer + . (should be exactly 6 characters, excluding leading % & colon) */ + Norm_nlstab[WORD_allcpus_txt] = _("%Cpu(s):"); +/* Translation Hint: The following "word" is meant to represent a single processor + and 'the Cp' prefix will be combined with a core id: 'p', 'e' or 'u' + . (if 'Cp' is translated, it must be exactly 2 characters long) */ + Norm_nlstab[WORD_eachcpu_fmt] = _("%%Cp%c%-3d:"); +/* Translation Hint: The following word "another" must have 1 trailing space */ + Norm_nlstab[WORD_another_txt] = _("another "); + Norm_nlstab[FIND_no_next_txt] = _("Locate next inactive, use \"L\""); + Norm_nlstab[GET_find_str_txt] = _("Locate string"); + Norm_nlstab[FIND_no_find_fmt] = _("%s\"%s\" not found"); + Norm_nlstab[XTRA_fixwide_fmt] = _("width incr is %d, change to (0 default, -1 auto)"); + Norm_nlstab[XTRA_warncfg_txt] = _("rcfile has inspect/other-filter error(s), save anyway?"); + Norm_nlstab[XTRA_badflds_fmt] = _("unrecognized field name '%s'"); + Norm_nlstab[XTRA_winsize_txt] = _("even using field names only, window is now too small"); + Norm_nlstab[YINSP_demo01_txt] = _("Open Files"); + Norm_nlstab[YINSP_demo02_txt] = _("NUMA Info"); + Norm_nlstab[YINSP_demo03_txt] = _("Log"); + Norm_nlstab[YINSP_deqfmt_txt] = _("the '=' key will eventually show the actual file read or command(s) executed ..."); + Norm_nlstab[YINSP_deqtyp_txt] = _("demo"); + Norm_nlstab[YINSP_dstory_txt] = _("" + "This is simulated output representing the contents of some file or the output\n" + "from some command. Exactly which commands and/or files are solely up to you.\n" + "\n" + "Although this text is for information purposes only, it can still be scrolled\n" + "and searched like real output will be. You are encouraged to experiment with\n" + "those features as explained in the prologue above.\n" + "\n" + "To enable real Inspect functionality, entries must be added to the end of the\n" + "top personal personal configuration file. You could use your favorite editor\n" + "to accomplish this, taking care not to disturb existing entries.\n" + "\n" + "Another way to add entries is illustrated below, but it risks overwriting the\n" + "rcfile. Redirected echoes must not replace (>) but append (>>) to that file.\n" + "\n" + " /bin/echo -e \"pipe\\tOpen Files\\tlsof -P -p %d 2>&1\" >> ~/.toprc\n" + " /bin/echo -e \"file\\tNUMA Info\\t/proc/%d/numa_maps\" >> ~/.toprc\n" + " /bin/echo -e \"pipe\\tLog\\ttail -n200 /var/log/syslog | sort -Mr\" >> ~/.toprc\n" + "\n" + "If you don't know the location or name of the top rcfile, use the 'W' command\n" + "and note those details. After backing up the current rcfile, try issuing the\n" + "above echoes exactly as shown, replacing '.toprc' as appropriate. The safest\n" + "approach would be to use copy then paste to avoid any typing mistakes.\n" + "\n" + "Finally, restart top to reveal what actual Inspect entries combined with this\n" + "new command can offer. The possibilities are endless, especially considering\n" + "that 'pipe' type entries can include shell scripts too!\n" + "\n" + "For additional important information, please consult the top(1) man document.\n" + "Then, enhance top with your very own customized 'file' and/or 'pipe' entries.\n" + "\n" + "Enjoy!\n"); + Norm_nlstab[YINSP_noent1_txt] = _("to enable 'Y' press <Enter> then type 'W' and restart top"); + Norm_nlstab[YINSP_noent2_txt] = _("to enable 'Y' please consult the top man page (press Enter)"); + Norm_nlstab[YINSP_failed_fmt] = _("Selection failed with: %s\n"); + Norm_nlstab[YINSP_pidbad_fmt] = _("unable to inspect, pid %d not found"); + Norm_nlstab[YINSP_pidsee_fmt] = _("inspect at PID [default pid = %d]"); + Norm_nlstab[YINSP_status_fmt] = _("%s: %*d-%-*d lines, %*d-%*d columns, %lu bytes read"); + Norm_nlstab[YINSP_waitin_txt] = _("patience please, working ..."); + Norm_nlstab[YINSP_workin_txt] = _("working, use Ctrl-C to end ..."); +/* Translation Hint: Below are 2 abbreviations which can be as long as needed: + . FLD = FIELD, VAL = VALUE */ + Norm_nlstab[OSEL_prompts_fmt] = _("add filter #%d (%s) as: [!]FLD?VAL"); + Norm_nlstab[OSEL_casenot_txt] = _("ignoring case"); + Norm_nlstab[OSEL_caseyes_txt] = _("case sensitive"); + Norm_nlstab[OSEL_errdups_txt] = _("duplicate filter was ignored"); + Norm_nlstab[OSEL_errdelm_fmt] = _("'%s' filter delimiter is missing"); + Norm_nlstab[OSEL_errvalu_fmt] = _("'%s' filter value is missing"); + Norm_nlstab[WORD_include_txt] = _("include"); + Norm_nlstab[WORD_exclude_txt] = _("exclude"); + Norm_nlstab[OSEL_statlin_fmt] = _("<Enter> to resume, filters: %s"); + Norm_nlstab[WORD_noneone_txt] = _("none"); +/* Translation Hint: The following word 'Node' should be exactly + 4 characters, excluding leading %%, fmt chars & colon) */ + Norm_nlstab[NUMA_nodenam_fmt] = _("%%Node%-2d:"); + Norm_nlstab[NUMA_nodeget_fmt] = _("expand which numa node (0-%d)"); + Norm_nlstab[NUMA_nodebad_txt] = _("invalid numa node"); + Norm_nlstab[NUMA_nodenot_txt] = _("sorry, NUMA extensions unavailable"); +/* Translation Hint: 'Mem ' is an abbreviation for physical memory/ram + . 'Swap' represents the linux swap file -- + . please make both translations exactly 4 characters, + . padding with extra spaces as necessary */ + Norm_nlstab[WORD_abv_mem_txt] = _("Mem "); + Norm_nlstab[WORD_abv_swp_txt] = _("Swap"); + Norm_nlstab[LIB_errormem_fmt] = _("library failed memory statistics, at %d: %s"); + Norm_nlstab[LIB_errorcpu_fmt] = _("library failed cpu statistics, at %d: %s"); + Norm_nlstab[LIB_errorpid_fmt] = _("library failed pids statistics, at %d: %s"); + Norm_nlstab[BAD_memscale_fmt] = _("bad memory scaling arg '%s'"); + Norm_nlstab[XTRA_vforest_fmt] = _("PID to collapse/expand [default pid = %d]"); + Norm_nlstab[XTRA_modebad_txt] = _("wrong mode, command inactive"); + Norm_nlstab[XTRA_warnold_txt] = _("saving prevents older top from reading, save anyway?"); + Norm_nlstab[X_SEMAPHORES_fmt] = _("failed sem_init() at %d: %s"); + Norm_nlstab[X_THREADINGS_fmt] = _("failed pthread_create() at %d: %s"); + Norm_nlstab[X_RESTRICTED_txt] = _("sorry, restricted namespace with reduced functionality"); + Norm_nlstab[AGNI_valueof_fmt] = _("set pid %d AGNI value to"); + Norm_nlstab[AGNI_invalid_txt] = _("valid AGNI range is -20 to +19"); + Norm_nlstab[AGNI_notopen_fmt] = _("autogroup open failed, %s"); + Norm_nlstab[AGNI_nowrite_fmt] = _("autogroup write failed, %s"); + Norm_nlstab[X_BOT_cmdlin_fmt] = _("command line for pid %d, %s"); + Norm_nlstab[X_BOT_ctlgrp_fmt] = _("control groups for pid %d, %s"); + Norm_nlstab[X_BOT_envirn_fmt] = _("environment for pid %d, %s"); + Norm_nlstab[X_BOT_namesp_fmt] = _("namespaces for pid %d, %s"); + Norm_nlstab[X_BOT_nodata_txt] = _("n/a"); + Norm_nlstab[X_BOT_supgrp_fmt] = _("supplementary groups for pid %d, %s"); + Norm_nlstab[X_BOT_msglog_txt] = _("message log, last 10 messages:"); +} + + + /* + * This routine builds the nls table containing specially + * formatted strings designed to fit within an 80x24 terminal. */ +static void build_uniq_nlstab (void) { + +/* Translation Notes ------------------------------------------------ + . It is strongly recommend that the --no-wrap command line option + . be used with all supporting translation tools, when available. + . + . The next several text groups contain special escape sequences + . representing values used to index a table at run-time. + . + . Each such sequence consists of a tilde (~) followed by an ascii + . number in the range of '1' - '8'. Examples are '~2', '~8', etc. + . These escape sequences must never themselves be translated but + . could be deleted. + . + . If you remove these escape sequences (both tilde and number) it + . would make translation easier. However, the ability to display + . colors and bold text at run-time will have been lost. + . + . Additionally, each of these text groups was designed to display + . in a 80x24 terminal window. Hopefully, any translations will + . adhere to that goal lest the translated text be truncated. + . + . If you would like additional information regarding these strings, + . please see the prologue to the show_special function in the top.c + . source file. + . + . Caution: + . The next three items represent pages for interacting with a user. + . In all cases, the last lines of text must be treated with care. + . + . They must not end with a newline character so that at runtime the + . cursor will remain on that final text line. + . + . Also, the special sequences (tilde+number) must not appear on the + . last line (the one without the newline). So please avoid any line + . wraps that could place them there. + . */ + + Uniq_nlstab[KEYS_helpbas_fmt] = _("" + "Help for Interactive Commands~2 - %s\n" + "Window ~1%s~6: ~1Cumulative mode ~3%s~2. ~1System~6: ~1Delay ~3%.1f secs~2; ~1Secure mode ~3%s~2.\n" + "\n" + " Z~5,~1B~5,E,e Global: '~1Z~2' colors; '~1B~2' bold; '~1E~2'/'~1e~2' summary/task memory scale\n" + " l,t,m,I,0 Toggle: '~1l~2' load avg; '~1t~2' task/cpu; '~1m~2' memory; '~1I~2' Irix; '~10~2' zeros\n" + " 1,2,3,4,5 Toggle: '~11~2/~12~2/~13~2' cpu/numa views; '~14~2' cpus abreast; '~15~2' P/E-cores\n" + " f,X Fields: '~1f~2' add/remove/order/sort; '~1X~2' increase fixed-width fields\n" + "\n" + " L,&,<,> . Locate: '~1L~2'/'~1&~2' find/again; Move sort column: '~1<~2'/'~1>~2' left/right\n" + " R,H,J,C . Toggle: '~1R~2' Sort; '~1H~2' Threads; '~1J~2' Num justify; '~1C~2' Coordinates\n" + " c,i,S,j . Toggle: '~1c~2' Cmd name/line; '~1i~2' Idle; '~1S~2' Time; '~1j~2' Str justify\n" + " x~5,~1y~5 . Toggle highlights: '~1x~2' sort field; '~1y~2' running tasks\n" + " z~5,~1b~5 . Toggle: '~1z~2' color/mono; '~1b~2' bold/reverse (only if 'x' or 'y')\n" + " u,U,o,O . Filter by: '~1u~2'/'~1U~2' effective/any user; '~1o~2'/'~1O~2' other criteria\n" + " n,#,^O . Set: '~1n~2'/'~1#~2' max tasks displayed; Show: ~1Ctrl~2+'~1O~2' other filter(s)\n" + " V,v,F . Toggle: '~1V~2' forest view; '~1v~2' hide/show children; '~1F~2' keep focused\n" + "\n" + "%s" + " ^G,K,N,U View: ctl groups ~1^G~2; cmdline ~1^K~2; environment ~1^N~2; supp groups ~1^U~2\n" + " Y,!,^E,P Inspect '~1Y~2'; Combine Cpus '~1!~2'; Scale time ~1^E~2; View namespaces ~1^P~2\n" + " W,q Write config file '~1W~2'; Quit '~1q~2'\n" + " ( commands shown with '.' require a ~1visible~2 task display ~1window~2 ) \n" + "Press '~1h~2' or '~1?~2' for help with ~1Windows~2,\n" + "Type 'q' or <Esc> to continue "); + + Uniq_nlstab[WINDOWS_help_fmt] = _("" + "Help for Windows / Field Groups~2 - \"Current Window\" = ~1 %s ~6\n" + "\n" + ". Use multiple ~1windows~2, each with separate config opts (color,fields,sort,etc)\n" + ". The 'current' window controls the ~1Summary Area~2 and responds to your ~1Commands~2\n" + " . that window's ~1task display~2 can be turned ~1Off~2 & ~1On~2, growing/shrinking others\n" + " . with ~1NO~2 task display, some commands will be ~1disabled~2 ('i','R','n','c', etc)\n" + " until a ~1different window~2 has been activated, making it the 'current' window\n" + ". You ~1change~2 the 'current' window by: ~1 1~2) cycling forward/backward;~1 2~2) choosing\n" + " a specific field group; or~1 3~2) exiting the color mapping or fields screens\n" + ". Commands ~1available anytime -------------~2\n" + " A . Alternate display mode toggle, show ~1Single~2 / ~1Multiple~2 windows\n" + " g . Choose another field group and make it 'current', or change now\n" + " by selecting a number from: ~1 1~2 =%s;~1 2~2 =%s;~1 3~2 =%s; or~1 4~2 =%s\n" + ". Commands ~1requiring~2 '~1A~2' mode~1 -------------~2\n" + " G . Change the ~1Name~5 of the 'current' window/field group\n" + " ~1*~4 a , w . Cycle through all four windows: '~1a~5' Forward; '~1w~5' Backward\n" + " ~1*~4 - , _ . Show/Hide: '~1-~5' ~1Current~2 window; '~1_~5' all ~1Visible~2/~1Invisible~2\n" + " The screen will be divided evenly between task displays. But you can make\n" + " some ~1larger~2 or ~1smaller~2, using '~1n~2' and '~1i~2' commands. Then later you could:\n" + " ~1*~4 = , + . Rebalance tasks: '~1=~5' ~1Current~2 window; '~1+~5' ~1Every~2 window\n" + " (this also forces the ~1current~2 or ~1every~2 window to become visible)\n" + "\n" + "In '~1A~2' mode, '~1*~4' keys are your ~1essential~2 commands. Please try the '~1a~2' and '~1w~2'\n" + "commands plus the 'g' sub-commands NOW. Press <Enter> to make 'Current' "); + +/* Translation Notes ------------------------------------------------ + . The following 'Help for color mapping' simulated screen should + . probably NOT be translated. It is terribly hard to follow in + . this form and any translation could produce unpleasing results + . that are unlikely to parallel the running top program. + . + . If you decide to proceed with translation, please take care + . to not disturb the spaces and the tilde + number delimiters. + . */ + Uniq_nlstab[COLOR_custom_fmt] = _("" + "Help for color mapping~2 - \"Current Window\" = ~1 %s ~6\n" + "\n" + " color - 04:25:44 up 8 days, 50 min, 7 users, load average:\n" + " Tasks:~3 64 ~2total,~3 2 ~3running,~3 62 ~2sleeping,~3 0 ~2stopped,~3\n" + " %%Cpu(s):~3 76.5 ~2user,~3 11.2 ~2system,~3 0.0 ~2nice,~3 12.3 ~2idle~3\n" + " ~1 Nasty Message! ~4 -or- ~1Input Prompt~5\n" + " ~1 PID TTY PR NI %%CPU TIME+ VIRT SWAP S COMMAND ~6\n" + " 17284 ~8pts/2 ~7 8 0 0.0 0:00.75 1380 0 S /bin/bash ~8\n" + " ~1 8601 pts/1 7 -10 0.4 0:00.03 916 0 R color -b -z~7\n" + " 11005 ~8? ~7 9 0 0.0 0:02.50 2852 1008 S amor -sessi~8\n" + " available toggles: ~1B~2 =disable bold globally (~1%s~2),\n" + " ~1z~2 =color/mono (~1%s~2), ~1b~2 =tasks \"bold\"/reverse (~1%s~2)\n" + "\n" + "1) Select a ~1target~2 as an upper case letter, ~1current target~2 is ~1 %c ~4:\n" + " S~2 = Summary Data,~1 M~2 = Messages/Prompts,\n" + " H~2 = Column Heads,~1 T~2 = Task Information\n" + "2) Select a ~1color~2 as a number or use the up/down arrow keys\n" + " to raise/lower the %d colors value, ~1current color~2 is ~1 %d ~4:\n" + " 0~2 = black,~1 1~2 = red, ~1 2~2 = green,~1 3~2 = yellow,\n" + " 4~2 = blue, ~1 5~2 = magenta,~1 6~2 = cyan, ~1 7~2 = white\n" + "\n" + "3) Then use these keys when finished:\n" + " 'q' or <Esc> to abort changes to window '~1%s~2'\n" + " 'a' or 'w' to commit & change another, <Enter> to commit and end "); + +/* Translation Hint: As is true for the text above, the "keys" shown to the left and + . also embedded in the translatable text (along with escape seqs) + . should never themselves be translated. */ + Uniq_nlstab[KEYS_helpext_fmt] = _("" + " d,k,r,^R '~1d~2' set delay; '~1k~2' kill; '~1r~2' renice; ~1Ctrl~2+'~1R~2' renice autogroup\n"); + +/* Translation Hint: + . This Fields Management header should be 3 lines long so as + . to allow 1 blank line before the fields & descriptions. + . If absolutely necessary, 4 lines could be used (but never more). + . */ + Uniq_nlstab[FIELD_header_fmt] = _("" + "Fields Management~2 for window ~1%s~6, whose current sort field is ~1%s~2\n" + " Navigate with Up/Dn, Right selects for move then <Enter> or Left commits,\n" + " 'd' or <Space> toggles display, 's' sets sort. Use 'q' or <Esc> to end!\n"); + +/* Translation Hint: + . The next 5 items must each be translated as a single line. + . */ + Uniq_nlstab[STATE_line_1_fmt] = _("%s:~3" + " %3u ~2total,~3 %3u ~2running,~3 %3u ~2sleeping,~3 %3u ~2stopped,~3 %3u ~2zombie~3\n"); + +/* Translation Hint: Only the following abbreviations need be translated + . us = user, sy = system, ni = nice, id = idle, wa = wait, + . hi hardware interrupt, si = software interrupt */ + Uniq_nlstab[STATE_lin2x6_fmt] = _("%s~3" + " %#5.1f ~2us,~3 %#5.1f ~2sy,~3 %#5.1f ~2ni,~3 %#5.1f ~2id,~3 %#5.1f ~2wa,~3 %#5.1f ~2hi,~3 %#5.1f ~2si~3 ~1"); + +/* Translation Hint: Only the following abbreviations need be translated + . us = user, sy = system, ni = nice, id = idle, wa = wait, + . hi hardware interrupt, si = software interrupt, st = steal time */ + Uniq_nlstab[STATE_lin2x7_fmt] = _("%s~3" + "%#5.1f ~2us,~3%#5.1f ~2sy,~3%#5.1f ~2ni,~3%#5.1f ~2id,~3%#5.1f ~2wa,~3%#5.1f ~2hi,~3%#5.1f ~2si,~3%#5.1f ~2st~3 ~1"); + +/* Translation Hint: next 2 must be treated together, with WORDS above & below aligned */ + Uniq_nlstab[MEMORY_line1_fmt] = _("" + "%s %s:~3 %9.9s~2total,~3 %9.9s~2free,~3 %9.9s~2used,~3 %9.9s~2buff/cache~3 ~1 "); + Uniq_nlstab[MEMORY_line2_fmt] = _("" + "%s %s:~3 %9.9s~2total,~3 %9.9s~2free,~3 %9.9s~2used.~3 %9.9s~2avail %s~3"); + +/* Translation Hint: + . The next 2 headers for 'Inspection' must each be 3 lines or less + . */ + Uniq_nlstab[YINSP_hdsels_fmt] = _("" + "Inspection~2 Pause at: pid ~1%d~6, running ~1%s~6\n" + "Use~2: left/right then <Enter> to ~1select~5 an option; 'q' or <Esc> to ~1end~5 !\n" + "Options~2: ~1%s\n"); + + Uniq_nlstab[YINSP_hdview_fmt] = _("" + "Inspection~2 View at pid: ~1%s~3, running ~1%s~3. Locating: ~1%s~6\n" + "Use~2: left/right/up/down/etc to ~1navigate~5 the output; 'L'/'&' to ~1locate~5/~1next~5.\n" + "Or~2: <Enter> to ~1select another~5; 'q' or <Esc> to ~1end~5 !\n"); +} + + + /* + * This function must be called very early at startup, before + * any other function call, and especially before any changes + * have been made to the terminal if NLS_VALIDATE is defined! + * + * The gettext documentation suggests that alone among locale + * variables LANGUAGE=ll_CC may be abbreviated as LANGUAGE=ll + * to denote the language's main dialect. Unfortunately this + * does not appear to be true. One must specify the complete + * ll_CC. Optionally, a '.UTF-8' or '.uft8' suffix, as shown + * in the following examples, may also be included: + * export LANGUAGE=ll_CC # minimal requirement + * export LANGUAGE=ll_CC.UTF-8 # optional convention + * export LANGUAGE=ll_CC.utf8 # ok, too + * + * Additionally, as suggested in the gettext documentation, a + * user will also have to export an empty LC_ALL= to actually + * enable any translations. + */ +void initialize_nls (void) { +#ifdef NLS_VALIDATE + static const char *nls_err ="\t%s_nlstab[%d] == NULL\n"; + int i; + + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + memset(Head_nlstab, 0, sizeof(Head_nlstab)); + memset(Desc_nlstab, 0, sizeof(Desc_nlstab)); + build_two_nlstabs(); + for (i = 0; i < EU_MAXPFLGS; i++) { + if (!Head_nlstab[i]) { + fprintf(stderr, nls_err, "Head", i); + exit(1); + } + if (!Desc_nlstab[i]) { + fprintf(stderr, nls_err, "Desc", i); + exit(1); + } + } + memset(Norm_nlstab, 0, sizeof(Norm_nlstab)); + build_norm_nlstab(); + for (i = 0; i < norm_MAX; i++) + if (!Norm_nlstab[i]) { + fprintf(stderr, nls_err, "Norm", i); + exit(1); + } + memset(Uniq_nlstab, 0, sizeof(Uniq_nlstab)); + build_uniq_nlstab(); + for (i = 0; i < uniq_MAX; i++) + if (!Uniq_nlstab[i]) { + fprintf(stderr, nls_err, "Uniq", i); + exit(1); + } +#else + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + build_two_nlstabs(); + build_norm_nlstab(); + build_uniq_nlstab(); +#endif +} diff --git a/src/top/top_nls.h b/src/top/top_nls.h new file mode 100644 index 0000000..ed6bad6 --- /dev/null +++ b/src/top/top_nls.h @@ -0,0 +1,108 @@ +/* top_nls.h - provide the basis for future nls translations */ +/* + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net + * + * This file may be used subject to the terms and conditions of the + * GNU Library General Public License Version 2, or any later version + * at your option, as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + */ +/* For contributions to this program, the author wishes to thank: + * Craig Small, <csmall@dropbear.xyz> + * Sami Kerola, <kerolasa@iki.fi> + */ +#ifndef _Itop_nls +#define _Itop_nls + + /* + * These are our string tables with the following contents: + * Head : column headings with varying size limits + * Desc : field descriptions not to exceed 20 screen positions + * Norm : regular text possibly also containing c-format specifiers + * Uniq : show_special specially formatted strings + * + * The latter table presents the greatest translation challenge ! + * + * We go to the trouble of creating the nls string tables to achieve + * these objectives: + * + the overhead of repeated runtime calls to gettext() + * will be avoided + * + the order of the strings in the template (.pot) file + * can be completely controlled + * + none of the important translator only comments will + * clutter and obscure the main program + */ +extern const char *Head_nlstab[]; +extern const char *Desc_nlstab[]; +extern const char *Norm_nlstab[]; +extern const char *Uniq_nlstab[]; + + /* + * Simple optional macros to ease table access. + * The N_txt and N_fmt macros are interchangeable but + * highlight the two types of strings found in Norm_nlstable. + */ +#define N_col(e) Head_nlstab[e] +#define N_fld(e) Desc_nlstab[e] +#define N_txt(e) Norm_nlstab[e] +#define N_fmt(e) Norm_nlstab[e] +#define N_unq(e) Uniq_nlstab[e] + + /* + * These enums are the means to access two of our four tables. + * The Head_nlstab and Desc_nlstab are accessed with standard + * top pflag enums. + * + * The norm_nls enums carry a suffix distinguishing plain text + * from any text also containiing c-format specifiers. + */ +enum norm_nls { + AGNI_invalid_txt, AGNI_notopen_fmt, AGNI_nowrite_fmt, AGNI_valueof_fmt, + AMT_exxabyte_txt, AMT_gigabyte_txt, AMT_kilobyte_txt, AMT_megabyte_txt, + AMT_petabyte_txt, AMT_terabyte_txt, BAD_delayint_fmt, BAD_integers_txt, + BAD_max_task_txt, BAD_memscale_fmt, BAD_mon_pids_fmt, BAD_niterate_fmt, + BAD_numfloat_txt, BAD_signalid_txt, BAD_username_txt, BAD_widtharg_fmt, + CHOOSE_group_txt, COLORS_nomap_txt, DELAY_badarg_txt, DELAY_change_fmt, + DELAY_secure_txt, DISABLED_cmd_txt, DISABLED_win_fmt, EXIT_signals_fmt, + FAIL_alloc_c_txt, FAIL_alloc_r_txt, FAIL_rc_open_fmt, FAIL_re_nice_fmt, + FAIL_signals_fmt, FAIL_tty_get_txt, FAIL_tty_set_fmt, FAIL_widecpu_txt, + FAIL_widepid_txt, FIND_no_find_fmt, FIND_no_next_txt, FOREST_modes_fmt, + FOREST_views_txt, GET_find_str_txt, GET_max_task_fmt, GET_nice_num_fmt, + GET_pid2kill_fmt, GET_pid2nice_fmt, GET_sigs_num_fmt, GET_user_ids_txt, + HELP_cmdline_fmt, IRIX_curmode_fmt, LIB_errorcpu_fmt, LIB_errormem_fmt, + LIB_errorpid_fmt, LIMIT_exceed_fmt, MISSING_args_fmt, NAME_windows_fmt, + NOT_onsecure_txt, NOT_smp_cpus_txt, NUMA_nodebad_txt, NUMA_nodeget_fmt, + NUMA_nodenam_fmt, NUMA_nodenot_txt, OFF_one_word_txt, ON_word_only_txt, + OSEL_casenot_txt, OSEL_caseyes_txt, OSEL_errdelm_fmt, OSEL_errdups_txt, + OSEL_errvalu_fmt, OSEL_prompts_fmt, OSEL_statlin_fmt, RC_bad_entry_fmt, + RC_bad_files_fmt, SCROLL_coord_fmt, SELECT_clash_txt, THREADS_show_fmt, + TIME_accumed_fmt, UNKNOWN_cmds_txt, UNKNOWN_opts_fmt, VERSION_opts_fmt, + WORD_abv_mem_txt, WORD_abv_swp_txt, WORD_allcpus_txt, WORD_another_txt, + WORD_eachcpu_fmt, WORD_exclude_txt, WORD_include_txt, WORD_noneone_txt, + WORD_process_txt, WORD_threads_txt, WRITE_rcfile_fmt, + XTRA_badflds_fmt, XTRA_fixwide_fmt, XTRA_modebad_txt, XTRA_vforest_fmt, + XTRA_warncfg_txt, XTRA_warnold_txt, XTRA_winsize_txt, + X_BOT_cmdlin_fmt, X_BOT_ctlgrp_fmt, X_BOT_envirn_fmt, X_BOT_msglog_txt, + X_BOT_namesp_fmt, X_BOT_nodata_txt, X_BOT_supgrp_fmt, X_RESTRICTED_txt, + X_SEMAPHORES_fmt, X_THREADINGS_fmt, + YINSP_demo01_txt, YINSP_demo02_txt, YINSP_demo03_txt, YINSP_deqfmt_txt, + YINSP_deqtyp_txt, YINSP_dstory_txt, + YINSP_failed_fmt, YINSP_noent1_txt, YINSP_noent2_txt, YINSP_pidbad_fmt, + YINSP_pidsee_fmt, YINSP_status_fmt, YINSP_waitin_txt, YINSP_workin_txt, + norm_MAX +}; + +enum uniq_nls { + COLOR_custom_fmt, FIELD_header_fmt, KEYS_helpbas_fmt, KEYS_helpext_fmt, + MEMORY_line1_fmt, MEMORY_line2_fmt, STATE_lin2x6_fmt, STATE_lin2x7_fmt, + STATE_line_1_fmt, WINDOWS_help_fmt, YINSP_hdsels_fmt, YINSP_hdview_fmt, + uniq_MAX +}; + +void initialize_nls (void); + +#endif /* _Itop_nls */ + diff --git a/src/uptime.c b/src/uptime.c new file mode 100644 index 0000000..91e89c7 --- /dev/null +++ b/src/uptime.c @@ -0,0 +1,127 @@ +/* + * uptime.c - display system uptime + * + * Copyright © 2002-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2020-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <sys/time.h> + +#include "c.h" +#include "fileutils.h" +#include "nls.h" + +#include "misc.h" + +static void print_uptime_since() +{ + double now, uptime_secs, idle_secs; + time_t up_since_secs; + struct tm *up_since; + struct timeval tim; + + /* Get the current time and convert it to a double */ + if (gettimeofday(&tim, NULL) != 0) + xerr(EXIT_FAILURE, "gettimeofday"); + now = (tim.tv_sec * 1000000.0) + tim.tv_usec; + + /* Get the uptime and calculate when that was */ + if (procps_uptime(&uptime_secs, &idle_secs) < 0) + xerr(EXIT_FAILURE, _("Cannot get system uptime")); + up_since_secs = (time_t) ((now/1000000.0) - uptime_secs); + + /* Show this */ + if ((up_since = localtime(&up_since_secs)) == NULL) + xerrx(EXIT_FAILURE, "localtime"); + printf("%04d-%02d-%02d %02d:%02d:%02d\n", + up_since->tm_year + 1900, up_since->tm_mon + 1, up_since->tm_mday, + up_since->tm_hour, up_since->tm_min, up_since->tm_sec); +} + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -p, --pretty show uptime in pretty format\n"), out); + fputs(USAGE_HELP, out); + fputs(_(" -s, --since system up since\n"), out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("uptime(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int c, p = 0; + char *uptime_str; + + static const struct option longopts[] = { + {"pretty", no_argument, NULL, 'p'}, + {"help", no_argument, NULL, 'h'}, + {"since", no_argument, NULL, 's'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "phsV", longopts, NULL)) != -1) + switch (c) { + case 'p': + p = 1; + break; + case 'h': + usage(stdout); + case 's': + print_uptime_since(); + return EXIT_SUCCESS; + case 'V': + printf(PROCPS_NG_VERSION); + return EXIT_SUCCESS; + default: + usage(stderr); + } + + if (optind != argc) + usage(stderr); + + if (p) + uptime_str = procps_uptime_sprint_short(); + else + uptime_str = procps_uptime_sprint(); + + if (!uptime_str || uptime_str[0] == '\0') + xerr(EXIT_FAILURE, _("Cannot get system uptime")); + + printf("%s\n", uptime_str); + return EXIT_SUCCESS; +} diff --git a/src/vmstat.c b/src/vmstat.c new file mode 100644 index 0000000..c77ee25 --- /dev/null +++ b/src/vmstat.c @@ -0,0 +1,1092 @@ +/* + * vmstat - report memory statistics + * + * Copyright © 2011-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2012-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2010 Jan Görig <jgorig@redhat.com> + * Copyright © 2003 Fabian Frederick + * Copyright © 1998-2002 Albert Cahalan + * Copyright © 1994 Henry Ware <al172@yfn.ysu.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <assert.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> +#include <time.h> + +#include "c.h" +#include "fileutils.h" +#include "nls.h" +#include "strutils.h" + +#include "diskstats.h" +#include "meminfo.h" +#include "misc.h" +#include "slabinfo.h" +#include "stat.h" +#include "vmstat.h" + +#define UNIT_B 1 +#define UNIT_k 1000 +#define UNIT_K 1024 +#define UNIT_m 1000000 +#define UNIT_M 1048576 + +static unsigned long dataUnit = UNIT_K; +static char szDataUnit[3] = "K"; + +#define VMSTAT 0 +#define DISKSTAT 0x00000001 +#define VMSUMSTAT 0x00000002 +#define SLABSTAT 0x00000004 +#define PARTITIONSTAT 0x00000008 +#define DISKSUMSTAT 0x00000010 + +static int statMode = VMSTAT; + +/* "-a" means "show active/inactive" */ +static int a_option; + +/* "-w" means "wide output" */ +static int w_option; + +/* "-y" means "skip first output" */ +static int y_option; + +/* "-t" means "show timestamp" */ +static int t_option; + +static unsigned sleep_time = 1; +static int infinite_updates = 0; +static unsigned long num_updates =1; +/* window height */ +static unsigned int height; +static unsigned int moreheaders = TRUE; + +static enum stat_item First_stat_items[] = { + STAT_SYS_PROC_RUNNING, + STAT_SYS_PROC_BLOCKED, + STAT_SYS_INTERRUPTS, + STAT_SYS_CTX_SWITCHES, + STAT_TIC_USER, + STAT_TIC_NICE, + STAT_TIC_SYSTEM, + STAT_TIC_IRQ, + STAT_TIC_SOFTIRQ, + STAT_TIC_IDLE, + STAT_TIC_IOWAIT, + STAT_TIC_STOLEN, + STAT_TIC_GUEST, + STAT_TIC_GUEST_NICE +}; +static enum stat_item Loop_stat_items[] = { + STAT_SYS_PROC_RUNNING, + STAT_SYS_PROC_BLOCKED, + STAT_SYS_DELTA_INTERRUPTS, + STAT_SYS_DELTA_CTX_SWITCHES, + STAT_TIC_DELTA_USER, + STAT_TIC_DELTA_NICE, + STAT_TIC_DELTA_SYSTEM, + STAT_TIC_DELTA_IRQ, + STAT_TIC_DELTA_SOFTIRQ, + STAT_TIC_DELTA_IDLE, + STAT_TIC_DELTA_IOWAIT, + STAT_TIC_DELTA_STOLEN, + STAT_TIC_DELTA_GUEST, + STAT_TIC_DELTA_GUEST_NICE +}; +enum Rel_statitems { + stat_PRU, stat_PBL, stat_INT, stat_CTX, + stat_USR, stat_NIC, stat_SYS, stat_IRQ, stat_SRQ, + stat_IDL, stat_IOW, stat_STO, stat_GST, stat_GNI, + MAX_stat +}; + +static enum meminfo_item Mem_items[] = { + MEMINFO_SWAP_USED, + MEMINFO_MEM_FREE, + MEMINFO_MEM_ACTIVE, + MEMINFO_MEM_INACTIVE, + MEMINFO_MEM_BUFFERS, + MEMINFO_MEM_CACHED_ALL +}; +enum Rel_memitems { + mem_SUS, mem_FREE, mem_ACT, mem_INA, mem_BUF, mem_CAC, MAX_mem +}; + +static enum diskstats_item Disk_items[] = { + DISKSTATS_TYPE, + DISKSTATS_NAME, + DISKSTATS_READS, + DISKSTATS_READS_MERGED, + DISKSTATS_READ_SECTORS, + DISKSTATS_READ_TIME, + DISKSTATS_WRITES, + DISKSTATS_WRITES_MERGED, + DISKSTATS_WRITE_SECTORS, + DISKSTATS_WRITE_TIME, + DISKSTATS_IO_INPROGRESS, + DISKSTATS_IO_TIME, + DISKSTATS_WEIGHTED_TIME +}; +enum Rel_diskitems { + disk_TYPE, disk_NAME, + disk_READ, disk_READ_MERGE, disk_READ_SECT, disk_READ_TIME, + disk_WRITE, disk_WRITE_MERGE, disk_WRITE_SECT, disk_WRITE_TIME, + disk_IO, disk_IO_TIME, disk_IO_WTIME, MAX_disk +}; + +static enum diskstats_item Part_items[] = { + DISKSTATS_READS, + DISKSTATS_READ_SECTORS, + DISKSTATS_WRITES, + DISKSTATS_WRITE_SECTORS +}; +enum Rel_partitems { + part_READ, part_READ_SECT, part_WRITE, part_WRITE_SECT, MAX_part +}; + +static enum stat_item Sum_stat_items[] = { + STAT_TIC_USER, + STAT_TIC_NICE, + STAT_TIC_SYSTEM, + STAT_TIC_IDLE, + STAT_TIC_IOWAIT, + STAT_TIC_IRQ, + STAT_TIC_SOFTIRQ, + STAT_TIC_STOLEN, + STAT_TIC_GUEST, + STAT_TIC_GUEST_NICE, + STAT_SYS_CTX_SWITCHES, + STAT_SYS_INTERRUPTS, + STAT_SYS_TIME_OF_BOOT, + STAT_SYS_PROC_CREATED +}; +enum Rel_sumstatitems { + sstat_USR, sstat_NIC, sstat_SYS, sstat_IDL, sstat_IOW, sstat_IRQ, + sstat_SRQ, sstat_STO, sstat_GST, sstat_GNI, sstat_CTX, sstat_INT, + sstat_TOB, sstat_PCR +}; + +static enum meminfo_item Sum_mem_items[] = { + MEMINFO_MEM_TOTAL, + MEMINFO_MEM_USED, + MEMINFO_MEM_ACTIVE, + MEMINFO_MEM_INACTIVE, + MEMINFO_MEM_FREE, + MEMINFO_MEM_BUFFERS, + MEMINFO_MEM_CACHED_ALL, + MEMINFO_SWAP_TOTAL, + MEMINFO_SWAP_USED, + MEMINFO_SWAP_FREE, +}; +enum Rel_summemitems { + smem_MTOT, smem_MUSE, smem_MACT, smem_MIAC, smem_MFRE, + smem_MBUF, smem_MCAC, smem_STOT, smem_SUSE, smem_SFRE +}; + + +static void __attribute__ ((__noreturn__)) + usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] [delay [count]]\n"), + program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --active active/inactive memory\n"), out); + fputs(_(" -f, --forks number of forks since boot\n"), out); + fputs(_(" -m, --slabs slabinfo\n"), out); + fputs(_(" -n, --one-header do not redisplay header\n"), out); + fputs(_(" -s, --stats event counter statistics\n"), out); + fputs(_(" -d, --disk disk statistics\n"), out); + fputs(_(" -D, --disk-sum summarize disk statistics\n"), out); + fputs(_(" -p, --partition <dev> partition specific statistics\n"), out); + fputs(_(" -S, --unit <char> define display unit\n"), out); + fputs(_(" -w, --wide wide output\n"), out); + fputs(_(" -t, --timestamp show timestamp\n"), out); + fputs(_(" -y, --no-first skips first line of output\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("vmstat(8)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static void new_header(void) +{ + struct tm *tm_ptr; + time_t the_time; + char timebuf[32]; + + /* Translation Hint: Translating folloging header & fields + * that follow (marked with max x chars) might not work, + * unless manual page is translated as well. */ + const char *header = + _("procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------"); + const char *wide_header = + _("--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- ----------cpu----------"); + const char *timestamp_header = _(" -----timestamp-----"); + + const char format[] = + "%2s %2s %6s %6s %6s %6s %4s %4s %5s %5s %4s %4s %2s %2s %2s %2s %2s %2s"; + const char wide_format[] = + "%4s %4s %12s %12s %12s %12s %4s %4s %5s %5s %4s %4s %3s %3s %3s %3s %3s %3s"; + + + printf("%s", w_option ? wide_header : header); + + if (t_option) { + printf("%s", timestamp_header); + } + + printf("\n"); + + printf( + w_option ? wide_format : format, + /* Translation Hint: max 2 chars */ + _("r"), + /* Translation Hint: max 2 chars */ + _("b"), + /* Translation Hint: max 6 chars */ + _("swpd"), + /* Translation Hint: max 6 chars */ + _("free"), + /* Translation Hint: max 6 chars */ + a_option ? _("inact") : + /* Translation Hint: max 6 chars */ + _("buff"), + /* Translation Hint: max 6 chars */ + a_option ? _("active") : + /* Translation Hint: max 6 chars */ + _("cache"), + /* Translation Hint: max 4 chars */ + _("si"), + /* Translation Hint: max 4 chars */ + _("so"), + /* Translation Hint: max 5 chars */ + _("bi"), + /* Translation Hint: max 5 chars */ + _("bo"), + /* Translation Hint: max 4 chars */ + _("in"), + /* Translation Hint: max 4 chars */ + _("cs"), + /* Translation Hint: max 2 chars */ + _("us"), + /* Translation Hint: max 2 chars */ + _("sy"), + /* Translation Hint: max 2 chars */ + _("id"), + /* Translation Hint: max 2 chars */ + _("wa"), + /* Translation Hint: max 2 chars */ + _("st"), + /* Translation Hint: max 2 chars */ + _("gu")); + + if (t_option) { + (void) time( &the_time ); + tm_ptr = localtime( &the_time ); + if (tm_ptr && strftime(timebuf, sizeof(timebuf), "%Z", tm_ptr)) { + const size_t len = strlen(timestamp_header); + if (len >= 1 && len - 1 < sizeof(timebuf)) { + timebuf[len - 1] = '\0'; + } + } else { + timebuf[0] = '\0'; + } + printf(" %*s", (int)(strlen(timestamp_header) - 1), timebuf); + } + + printf("\n"); +} + + +static unsigned long unitConvert(unsigned long size) +{ + double cvSize; + cvSize = (double)size / dataUnit * ((statMode == SLABSTAT) ? 1 : 1024); + return ((unsigned long)cvSize); +} + +static void new_format(void) +{ +#define TICv(E) STAT_VAL(E, ull_int, stat_stack, stat_info) +#define DTICv(E) STAT_VAL(E, sl_int, stat_stack, stat_info) +#define SYSv(E) STAT_VAL(E, ul_int, stat_stack, stat_info) +#define MEMv(E) MEMINFO_VAL(E, ul_int, mem_stack, mem_info) +#define DSYSv(E) STAT_VAL(E, s_int, stat_stack, stat_info) + const char format[] = + "%2lu %2lu %6lu %6lu %6lu %6lu %4u %4u %5u %5u %4u %4u %2u %2u %2u %2u %2u %2u"; + const char wide_format[] = + "%4lu %4lu %12lu %12lu %12lu %12lu %4u %4u %5u %5u %4u %4u %3u %3u %3u %3u %3u %3u"; + + unsigned int tog = 0; /* toggle switch for cleaner code */ + unsigned int i; + long long cpu_use, cpu_sys, cpu_idl, cpu_iow, cpu_sto, cpu_gue; + long long Div, divo2; + unsigned long pgpgin[2], pgpgout[2], pswpin[2] = {0,0}, pswpout[2]; + unsigned int sleep_half; + unsigned long kb_per_page = sysconf(_SC_PAGESIZE) / 1024ul; + int debt = 0; /* handle idle ticks running backwards */ + struct tm *tm_ptr; + time_t the_time; + char timebuf[32]; + double uptime; + struct vmstat_info *vm_info = NULL; + struct stat_info *stat_info = NULL; + struct stat_stack *stat_stack; + struct meminfo_info *mem_info = NULL; + struct meminfo_stack *mem_stack; + + sleep_half = (sleep_time / 2); + // long hz = procps_hertz_get(); + + if (procps_vmstat_new(&vm_info) < 0) + xerrx(EXIT_FAILURE, _("Unable to create vmstat structure")); + if (procps_stat_new(&stat_info) < 0) + xerrx(EXIT_FAILURE, _("Unable to create system stat structure")); + if (procps_meminfo_new(&mem_info) < 0) + xerrx(EXIT_FAILURE, _("Unable to create meminfo structure")); + if (procps_uptime(&uptime, NULL) < 0) + xerr(EXIT_FAILURE, _("Unable to get uptime")); + if (0.0 == uptime) + uptime = 1.0; + new_header(); + + pgpgin[tog] = VMSTAT_GET(vm_info, VMSTAT_PGPGIN, ul_int); + pgpgout[tog] = VMSTAT_GET(vm_info, VMSTAT_PGPGOUT, ul_int); + pswpin[tog] = VMSTAT_GET(vm_info, VMSTAT_PSWPIN, ul_int); + pswpout[tog] = VMSTAT_GET(vm_info, VMSTAT_PSWPOUT, ul_int); + + if (!(mem_stack = procps_meminfo_select(mem_info, Mem_items, MAX_mem))) + xerrx(EXIT_FAILURE, _("Unable to select memory information")); + + if (y_option == 0) { + if (t_option) { + (void) time( &the_time ); + tm_ptr = localtime( &the_time ); + if (tm_ptr && strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm_ptr)) { + ; + } else { + timebuf[0] = '\0'; + } + } + /* Do the initial fill */ + if (!(stat_stack = procps_stat_select(stat_info, First_stat_items, MAX_stat))) + xerrx(EXIT_FAILURE, _("Unable to select stat information")); + cpu_use = TICv(stat_USR) + TICv(stat_NIC); + cpu_sys = TICv(stat_SYS) + TICv(stat_IRQ) + TICv(stat_SRQ); + cpu_idl = TICv(stat_IDL); + cpu_iow = TICv(stat_IOW); + cpu_sto = TICv(stat_STO); + cpu_gue = TICv(stat_GST) + TICv(stat_GNI); + + Div = cpu_use + cpu_sys + cpu_idl + cpu_iow + cpu_sto; + if (!Div) { + Div = 1; + cpu_idl = 1; + } + divo2 = Div / 2UL; + cpu_use = (cpu_use >= cpu_gue)? cpu_use - cpu_gue : 0; + + printf(w_option ? wide_format : format, + SYSv(stat_PRU), + SYSv(stat_PBL), + unitConvert(MEMv(mem_SUS)), + unitConvert(MEMv(mem_FREE)), + unitConvert((a_option?MEMv(mem_INA):MEMv(mem_BUF))), + unitConvert((a_option?MEMv(mem_ACT):MEMv(mem_CAC))), + (unsigned)( unitConvert(VMSTAT_GET(vm_info, VMSTAT_PSWPIN, ul_int) * kb_per_page) / uptime ), + (unsigned)( unitConvert(VMSTAT_GET(vm_info, VMSTAT_PSWPOUT, ul_int) * kb_per_page) / uptime ), + (unsigned)( VMSTAT_GET(vm_info, VMSTAT_PGPGIN, ul_int) / uptime ), + (unsigned)( VMSTAT_GET(vm_info, VMSTAT_PGPGOUT, ul_int) / uptime ), + (unsigned)( SYSv(stat_INT) / uptime ), + (unsigned)( SYSv(stat_CTX) / Div ), + (unsigned)( (100*cpu_use + divo2) / Div ), + (unsigned)( (100*cpu_sys + divo2) / Div ), + (unsigned)( (100*cpu_idl + divo2) / Div ), + (unsigned)( (100*cpu_iow + divo2) / Div ), + (unsigned)( (100*cpu_sto + divo2) / Div ), + (unsigned)( (100*cpu_gue + divo2) / Div ) + ); + + if (t_option) { + printf(" %s", timebuf); + } + + printf("\n"); + } else + num_updates++; + + /* main loop */ + for (i = 1; infinite_updates || i < num_updates; i++) { + sleep(sleep_time); + if (moreheaders && ((i % height) == 0)) + new_header(); + tog = !tog; + + if (!(stat_stack = procps_stat_select(stat_info, Loop_stat_items, MAX_stat))) + xerrx(EXIT_FAILURE, _("Unable to select stat information")); + + cpu_use = DTICv(stat_USR) + DTICv(stat_NIC); + cpu_sys = DTICv(stat_SYS) + DTICv(stat_IRQ) + DTICv(stat_SRQ); + cpu_idl = DTICv(stat_IDL); + cpu_iow = DTICv(stat_IOW); + cpu_sto = DTICv(stat_STO); + cpu_gue = TICv(stat_GST) + TICv(stat_GNI); + pgpgin[tog] = VMSTAT_GET(vm_info, VMSTAT_PGPGIN, ul_int); + pgpgout[tog] = VMSTAT_GET(vm_info, VMSTAT_PGPGOUT, ul_int); + pswpin[tog] = VMSTAT_GET(vm_info, VMSTAT_PSWPIN, ul_int); + pswpout[tog] = VMSTAT_GET(vm_info, VMSTAT_PSWPOUT, ul_int); + + if (!(mem_stack = procps_meminfo_select(mem_info, Mem_items, MAX_mem))) + xerrx(EXIT_FAILURE, _("Unable to select memory information")); + + if (t_option) { + (void) time( &the_time ); + tm_ptr = localtime( &the_time ); + if (tm_ptr && strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm_ptr)) { + ; + } else { + timebuf[0] = '\0'; + } + } + + /* idle can run backwards for a moment -- kernel "feature" */ + if (debt) { + cpu_idl = (int)cpu_idl + debt; + debt = 0; + } + if ((int)cpu_idl < 0) { + debt = (int)cpu_idl; + cpu_idl = 0; + } + + Div = cpu_use + cpu_sys + cpu_idl + cpu_iow + cpu_sto; + if (!Div) Div = 1, cpu_idl = 1; + divo2 = Div / 2UL; + + /* guest time is also in user time, we need to subtract. Due to timing + * effects guest could be larger than user. We use 0 that case */ + if (cpu_use >= cpu_gue) { + cpu_use -= cpu_gue; + } else { + cpu_use = 0; + } + + printf(w_option ? wide_format : format, + SYSv(stat_PRU), + SYSv(stat_PBL), + unitConvert(MEMv(mem_SUS)), + unitConvert(MEMv(mem_FREE)), + unitConvert((a_option?MEMv(mem_INA):MEMv(mem_BUF))), + unitConvert((a_option?MEMv(mem_ACT):MEMv(mem_CAC))), + /*si */ + (unsigned)( ( unitConvert((pswpin [tog] - pswpin [!tog])*kb_per_page)+sleep_half )/sleep_time ), + /* so */ + (unsigned)( ( unitConvert((pswpout[tog] - pswpout[!tog])*kb_per_page)+sleep_half )/sleep_time ), + /* bi */ + (unsigned)( ( pgpgin [tog] - pgpgin [!tog] +sleep_half )/sleep_time ), + /* bo */ + (unsigned)( ( pgpgout[tog] - pgpgout[!tog] +sleep_half )/sleep_time ), + /* in */ + (unsigned)( ( DSYSv(stat_INT) +sleep_half )/sleep_time ), + /* cs */ + (unsigned)( ( DSYSv(stat_CTX) +sleep_half )/sleep_time ), + /* us */ + (unsigned)( (100*cpu_use+divo2)/Div ), + /* sy */ + (unsigned)( (100*cpu_sys+divo2)/Div ), + /* id */ + (unsigned)( (100*cpu_idl+divo2)/Div ), + /* wa */ + (unsigned)( (100*cpu_iow+divo2)/Div ), + /* st */ + (unsigned)( (100*cpu_sto+divo2)/Div ), + /* gu */ + (unsigned)( (100*cpu_gue+divo2)/Div ) + ); + + if (t_option) { + printf(" %s", timebuf); + } + + printf("\n"); + } + /* Cleanup */ + procps_stat_unref(&stat_info); + procps_vmstat_unref(&vm_info); + procps_meminfo_unref(&mem_info); +#undef TICv +#undef DTICv +#undef SYSv +#undef DSYSv +#undef MEMv +} + +static void diskpartition_header(const char *partition_name) +{ + printf("%-10s %10s %16s %10s %16s\n", + partition_name, + + /* Translation Hint: Translating folloging disk partition + * header fields that follow (marked with max x chars) might + * not work, unless manual page is translated as well. */ + /* Translation Hint: max 10 chars */ + _("reads"), + /* Translation Hint: max 16 chars */ + _("read sectors"), + /* Translation Hint: max 10 chars */ + _("writes"), + /* Translation Hint: max 16 chars */ + _("requested writes")); +} + +static void diskpartition_format(const char *partition_name) +{ + #define partVAL(x) DISKSTATS_VAL(x, ul_int, stack, disk_stat) + struct diskstats_info *disk_stat = NULL; + struct diskstats_stack *stack; + struct diskstats_result *got; + const char format[] = "%21lu %16lu %10lu %16lu\n"; + int i; + + if (procps_diskstats_new(&disk_stat) < 0) + xerrx(EXIT_FAILURE, _("Unable to create diskstat structure")); + + if (!(got = procps_diskstats_get(disk_stat, partition_name, DISKSTATS_TYPE))) + xerrx(EXIT_FAILURE, _("Disk/Partition %s not found"), partition_name); + + diskpartition_header(partition_name); + + for (i = 0; infinite_updates || i < num_updates ; i++) { + if (!(stack = procps_diskstats_select(disk_stat, partition_name, Part_items, MAX_part))) + xerrx(EXIT_FAILURE, _("Disk/Partition %s not found"), partition_name); + printf(format, + partVAL(part_READ), + partVAL(part_READ_SECT), + partVAL(part_WRITE), + partVAL(part_WRITE_SECT)); + if (infinite_updates || i+1 < num_updates) + sleep(sleep_time); + } + procps_diskstats_unref(&disk_stat); + #undef partVAL +} + +static void diskheader(void) +{ + struct tm *tm_ptr; + time_t the_time; + char timebuf[32]; + + /* Translation Hint: Translating folloging header & fields + * that follow (marked with max x chars) might not work, + * unless manual page is translated as well. */ + const char *header = + _("disk- ------------reads------------ ------------writes----------- -----IO------"); + const char *wide_header = + _("disk- -------------------reads------------------- -------------------writes------------------ ------IO-------"); + const char *timestamp_header = _(" -----timestamp-----"); + + const char format[] = + "%5s %6s %6s %7s %7s %6s %6s %7s %7s %6s %6s"; + const char wide_format[] = + "%5s %9s %9s %11s %11s %9s %9s %11s %11s %7s %7s"; + + printf("%s", w_option ? wide_header : header); + + if (t_option) { + printf("%s", timestamp_header); + } + + printf("\n"); + + printf(w_option ? wide_format : format, + " ", + /* Translation Hint: max 6 chars */ + _("total"), + /* Translation Hint: max 6 chars */ + _("merged"), + /* Translation Hint: max 7 chars */ + _("sectors"), + /* Translation Hint: max 7 chars */ + _("ms"), + /* Translation Hint: max 6 chars */ + _("total"), + /* Translation Hint: max 6 chars */ + _("merged"), + /* Translation Hint: max 7 chars */ + _("sectors"), + /* Translation Hint: max 7 chars */ + _("ms"), + /* Translation Hint: max 6 chars */ + _("cur"), + /* Translation Hint: max 6 chars */ + _("sec")); + + if (t_option) { + (void) time( &the_time ); + tm_ptr = localtime( &the_time ); + if (tm_ptr && strftime(timebuf, sizeof(timebuf), "%Z", tm_ptr)) { + const size_t len = strlen(timestamp_header); + if (len >= 1 && len - 1 < sizeof(timebuf)) { + timebuf[len - 1] = '\0'; + } + } else { + timebuf[0] = '\0'; + } + printf(" %*s", (int)(strlen(timestamp_header) - 1), timebuf); + } + + printf("\n"); +} + +static void diskformat(void) +{ +#define diskVAL(e,t) DISKSTATS_VAL(e, t, reap->stacks[j], disk_stat) + struct diskstats_info *disk_stat = NULL; + struct diskstats_reaped *reap; + int i, j; + time_t the_time; + struct tm *tm_ptr; + char timebuf[32]; + const char format[] = "%-5s %6lu %6lu %7lu %7lu %6lu %6lu %7lu %7lu %6d %6lu"; + const char wide_format[] = "%-5s %9lu %9lu %11lu %11lu %9lu %9lu %11lu %11lu %7d %7lu"; + + if (procps_diskstats_new(&disk_stat) < 0) + xerrx(EXIT_FAILURE, _("Unable to create diskstat structure")); + + if (!moreheaders) + diskheader(); + + for (i=0; infinite_updates || i < num_updates ; i++) { + if (!(reap = procps_diskstats_reap(disk_stat, Disk_items, MAX_disk))) + xerrx(EXIT_FAILURE, _("Unable to retrieve disk statistics")); + if (t_option) { + (void) time( &the_time ); + tm_ptr = localtime( &the_time ); + if (tm_ptr && strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm_ptr)) { + ; + } else { + timebuf[0] = '\0'; + } + } + for (j = 0; j < reap->total; j++) { + if (diskVAL(disk_TYPE, s_int) != DISKSTATS_TYPE_DISK) + continue; /* not a disk */ + if (moreheaders && ((j % height) == 0)) + diskheader(); + printf(w_option ? wide_format : format, + diskVAL(disk_NAME, str), + diskVAL(disk_READ, ul_int), + diskVAL(disk_READ_MERGE, ul_int), + diskVAL(disk_READ_SECT, ul_int), + diskVAL(disk_READ_TIME, ul_int), + diskVAL(disk_WRITE, ul_int), + diskVAL(disk_WRITE_MERGE, ul_int), + diskVAL(disk_WRITE_SECT, ul_int), + diskVAL(disk_WRITE_TIME, ul_int), + diskVAL(disk_IO, s_int) / 1000, + diskVAL(disk_IO_TIME, ul_int) / 1000); + if (t_option) + printf(" %s\n", timebuf); + else + printf("\n"); + fflush(stdout); + } + if (infinite_updates || i+1 < num_updates) + sleep(sleep_time); + } +#undef diskVAL + procps_diskstats_unref(&disk_stat); +} + +static void slabheader(void) +{ + printf("%-24s %6s %6s %6s %6s\n", + /* Translation Hint: Translating folloging slab fields that + * follow (marked with max x chars) might not work, unless + * manual page is translated as well. */ + /* Translation Hint: max 24 chars */ + _("Cache"), + /* Translation Hint: max 6 chars */ + _("Num"), + /* Translation Hint: max 6 chars */ + _("Total"), + /* Translation Hint: max 6 chars */ + _("Size"), + /* Translation Hint: max 6 chars */ + _("Pages")); +} + +static void slabformat (void) +{ + #define MAX_ITEMS (int)(sizeof(node_items) / sizeof(node_items[0])) + #define slabVAL(e,t) SLABINFO_VAL(e, t, p, slab_info) + struct slabinfo_info *slab_info = NULL; + struct slabinfo_reaped *reaped; + int i, j; + enum slabinfo_item node_items[] = { + SLAB_ACTIVE_OBJS, SLAB_NUM_OBJS, + SLAB_OBJ_SIZE, SLAB_OBJ_PER_SLAB, + SLAB_NAME }; + enum rel_enums { + slab_AOBJS, slab_OBJS, slab_OSIZE, slab_OPS, slab_NAME }; + + if (procps_slabinfo_new(&slab_info) < 0) + xerr(EXIT_FAILURE, _("Unable to create slabinfo structure")); + + if (!moreheaders) + slabheader(); + + for (i = 0; infinite_updates || i < num_updates; i++) { + if (!(reaped = procps_slabinfo_reap(slab_info, node_items, MAX_ITEMS))) + xerrx(EXIT_FAILURE, _("Unable to get slabinfo node data")); + if (!(procps_slabinfo_sort(slab_info, reaped->stacks, reaped->total, SLAB_NAME, SLABINFO_SORT_ASCEND))) + xerrx(EXIT_FAILURE, _("Unable to sort slab nodes")); + + for (j = 0; j < reaped->total; j++) { + struct slabinfo_stack *p = reaped->stacks[j]; + if (moreheaders && ((j % height) == 0)) + slabheader(); + printf("%-24.24s %6u %6u %6u %6u\n", + slabVAL(slab_NAME, str), + slabVAL(slab_AOBJS, u_int), + slabVAL(slab_OBJS, u_int), + slabVAL(slab_OSIZE, u_int), + slabVAL(slab_OPS, u_int)); + } + if (infinite_updates || i+1 < num_updates) + sleep(sleep_time); + } + procps_slabinfo_unref(&slab_info); + #undef MAX_ITEMS + #undef slabVAL +} + +static void disksum_format(void) +{ +#define diskVAL(e,t) DISKSTATS_VAL(e, t, reap->stacks[j], disk_stat) + struct diskstats_info *disk_stat = NULL; + struct diskstats_reaped *reap; + int j, disk_count, part_count; + unsigned long reads, merged_reads, read_sectors, milli_reading, writes, + merged_writes, written_sectors, milli_writing, inprogress_IO, + milli_spent_IO, weighted_milli_spent_IO; + + reads = merged_reads = read_sectors = milli_reading = writes = + merged_writes = written_sectors = milli_writing = inprogress_IO = + milli_spent_IO = weighted_milli_spent_IO = 0; + disk_count = part_count = 0; + + if (procps_diskstats_new(&disk_stat) < 0) + xerrx(EXIT_FAILURE, _("Unable to create diskstat structure")); + if (!(reap = procps_diskstats_reap(disk_stat, Disk_items, MAX_disk))) + xerrx(EXIT_FAILURE, _("Unable to retrieve disk statistics")); + + for (j = 0; j < reap->total; j++) { + if (diskVAL(disk_TYPE, s_int) != DISKSTATS_TYPE_DISK) { + part_count++; + continue; /* not a disk */ + } + disk_count++; + + reads += diskVAL(disk_READ, ul_int); + merged_reads += diskVAL(disk_READ_MERGE, ul_int); + read_sectors += diskVAL(disk_READ_SECT, ul_int); + milli_reading += diskVAL(disk_READ_TIME, ul_int); + writes += diskVAL(disk_WRITE, ul_int); + merged_writes += diskVAL(disk_WRITE_MERGE, ul_int); + written_sectors += diskVAL(disk_WRITE_SECT, ul_int); + milli_writing += diskVAL(disk_WRITE_TIME, ul_int); + inprogress_IO += diskVAL(disk_IO, s_int) / 1000; + milli_spent_IO += diskVAL(disk_IO_TIME, ul_int) / 1000; + weighted_milli_spent_IO += diskVAL(disk_IO_WTIME, ul_int) / 1000; + } + printf(_("%13d disks\n"), disk_count); // <== old vmstat had a trailing space here + printf(_("%13d partitions\n"), part_count); // <== old vmstat had a trailing space here too + printf(_("%13lu total reads\n"), reads); + printf(_("%13lu merged reads\n"), merged_reads); + printf(_("%13lu read sectors\n"), read_sectors); + printf(_("%13lu milli reading\n"), milli_reading); + printf(_("%13lu writes\n"), writes); + printf(_("%13lu merged writes\n"), merged_writes); + printf(_("%13lu written sectors\n"), written_sectors); + printf(_("%13lu milli writing\n"), milli_writing); + printf(_("%13lu inprogress IO\n"), inprogress_IO); + printf(_("%13lu milli spent IO\n"), milli_spent_IO); + printf(_("%13lu milli weighted IO\n"), weighted_milli_spent_IO); + + procps_diskstats_unref(&disk_stat); +#undef diskVAL +} + +static void sum_format(void) +{ +#define TICv(E) STAT_VAL(E, ull_int, stat_stack, stat_info) +#define SYSv(E) STAT_VAL(E, ul_int, stat_stack, stat_info) +#define MEMv(E) unitConvert(MEMINFO_VAL(E, ul_int, mem_stack, mem_info)) + struct stat_info *stat_info = NULL; + struct vmstat_info *vm_info = NULL; + struct meminfo_info *mem_info = NULL; + struct stat_stack *stat_stack; + struct meminfo_stack *mem_stack; + + if (procps_stat_new(&stat_info) < 0) + xerrx(EXIT_FAILURE, _("Unable to create system stat structure")); + if (!(stat_stack = procps_stat_select(stat_info, Sum_stat_items, 14))) + xerrx(EXIT_FAILURE, _("Unable to select stat information")); + if (procps_vmstat_new(&vm_info) < 0) + xerrx(EXIT_FAILURE, _("Unable to create vmstat structure")); + if (procps_meminfo_new(&mem_info) < 0) + xerrx(EXIT_FAILURE, _("Unable to create meminfo structure")); + if (!(mem_stack = procps_meminfo_select(mem_info, Sum_mem_items, 10))) + xerrx(EXIT_FAILURE, _("Unable to select memory information")); + + printf(_("%13lu %s total memory\n"), MEMv(smem_MTOT), szDataUnit); + printf(_("%13lu %s used memory\n"), MEMv(smem_MUSE), szDataUnit); + printf(_("%13lu %s active memory\n"), MEMv(smem_MACT), szDataUnit); + printf(_("%13lu %s inactive memory\n"), MEMv(smem_MIAC), szDataUnit); + printf(_("%13lu %s free memory\n"), MEMv(smem_MFRE), szDataUnit); + printf(_("%13lu %s buffer memory\n"), MEMv(smem_MBUF), szDataUnit); + printf(_("%13lu %s swap cache\n"), MEMv(smem_MCAC), szDataUnit); + printf(_("%13lu %s total swap\n"), MEMv(smem_STOT), szDataUnit); + printf(_("%13lu %s used swap\n"), MEMv(smem_SUSE), szDataUnit); + printf(_("%13lu %s free swap\n"), MEMv(smem_SFRE), szDataUnit); + printf(_("%13lld non-nice user cpu ticks\n"), TICv(sstat_USR)); + printf(_("%13lld nice user cpu ticks\n"), TICv(sstat_NIC)); + printf(_("%13lld system cpu ticks\n"), TICv(sstat_SYS)); + printf(_("%13lld idle cpu ticks\n"), TICv(sstat_IDL)); + printf(_("%13lld IO-wait cpu ticks\n"), TICv(sstat_IOW)); + printf(_("%13lld IRQ cpu ticks\n"), TICv(sstat_IRQ)); + printf(_("%13lld softirq cpu ticks\n"), TICv(sstat_SRQ)); + printf(_("%13lld stolen cpu ticks\n"), TICv(sstat_STO)); + printf(_("%13lld non-nice guest cpu ticks\n"), TICv(sstat_GST)); + printf(_("%13lld nice guest cpu ticks\n"), TICv(sstat_GNI)); + printf(_("%13lu K paged in\n"), VMSTAT_GET(vm_info, VMSTAT_PGPGIN, ul_int)); + printf(_("%13lu K paged out\n"), VMSTAT_GET(vm_info, VMSTAT_PGPGOUT, ul_int)); + printf(_("%13lu pages swapped in\n"), VMSTAT_GET(vm_info, VMSTAT_PSWPIN, ul_int)); + printf(_("%13lu pages swapped out\n"), VMSTAT_GET(vm_info, VMSTAT_PSWPOUT, ul_int)); + printf(_("%13lu interrupts\n"), SYSv(sstat_INT)); + printf(_("%13lu CPU context switches\n"), SYSv(sstat_CTX)); + printf(_("%13lu boot time\n"), SYSv(sstat_TOB)); + printf(_("%13lu forks\n"), SYSv(sstat_PCR)); + + /* Cleanup */ + procps_stat_unref(&stat_info); + procps_vmstat_unref(&vm_info); + procps_meminfo_unref(&mem_info); +#undef TICv +#undef SYSv +#undef MEMv +} + +static void fork_format(void) +{ + struct stat_info *stat_info = NULL; + + if (procps_stat_new(&stat_info) < 0) + xerrx(EXIT_FAILURE, _("Unable to create system stat structure")); + + printf(_("%13lu forks\n"), STAT_GET(stat_info, STAT_SYS_PROC_CREATED, ul_int)); + /* Cleanup */ + procps_stat_unref(&stat_info); +} + +static int winhi(void) +{ + struct winsize win; + int rows = 24; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 && 0 < win.ws_row) + rows = win.ws_row; + + return rows; +} + +int main(int argc, char *argv[]) +{ + char *partition = NULL; + int c; + long tmp; + + static const struct option longopts[] = { + {"active", no_argument, NULL, 'a'}, + {"forks", no_argument, NULL, 'f'}, + {"slabs", no_argument, NULL, 'm'}, + {"one-header", no_argument, NULL, 'n'}, + {"stats", no_argument, NULL, 's'}, + {"disk", no_argument, NULL, 'd'}, + {"disk-sum", no_argument, NULL, 'D'}, + {"partition", required_argument, NULL, 'p'}, + {"unit", required_argument, NULL, 'S'}, + {"wide", no_argument, NULL, 'w'}, + {"timestamp", no_argument, NULL, 't'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"no-first", no_argument, NULL, 'y'}, + {NULL, 0, NULL, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = + getopt_long(argc, argv, "afmnsdDp:S:wthVy", longopts, NULL)) != -1) + switch (c) { + case 'V': + printf(PROCPS_NG_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + case 'd': + statMode |= DISKSTAT; + break; + case 'a': + /* active/inactive mode */ + a_option = 1; + break; + case 'f': + /* FIXME: check for conflicting args */ + fork_format(); + exit(0); + case 'm': + statMode |= SLABSTAT; + break; + case 'D': + statMode |= DISKSUMSTAT; + break; + case 'n': + /* print only one header */ + moreheaders = FALSE; + break; + case 'p': + statMode |= PARTITIONSTAT; + partition = optarg; + if (strncmp(partition, "/dev/", 5) == 0) + partition += 5; + break; + case 'S': + switch (optarg[0]) { + case 'b': + case 'B': + dataUnit = UNIT_B; + break; + case 'k': + dataUnit = UNIT_k; + break; + case 'K': + dataUnit = UNIT_K; + break; + case 'm': + dataUnit = UNIT_m; + break; + case 'M': + dataUnit = UNIT_M; + break; + default: + /* Translation Hint: do not change argument characters */ + xerrx(EXIT_FAILURE, _("-S requires k, K, m or M (default is KiB)")); + } + szDataUnit[0] = optarg[0]; + break; + case 's': + statMode |= VMSUMSTAT; + break; + case 'w': + w_option = 1; + break; + case 't': + t_option = 1; + break; + case 'y': + /* Don't display stats since system restart */ + y_option = 1; + break; + default: + /* no other aguments defined yet. */ + usage(stderr); + } + + if (optind < argc) { + tmp = strtol_or_err(argv[optind++], _("failed to parse argument")); + if (tmp < 1) + xerrx(EXIT_FAILURE, _("delay must be positive integer")); + else if (UINT_MAX < tmp) + xerrx(EXIT_FAILURE, _("too large delay value")); + sleep_time = tmp; + infinite_updates = 1; + } + num_updates = 1; + if (optind < argc) { + num_updates = strtol_or_err(argv[optind++], _("failed to parse argument")); + infinite_updates = 0; + } + if (optind < argc) + usage(stderr); + + if (moreheaders) { + int wheight = winhi() - 3; + height = ((wheight > 0) ? wheight : 22); + } + setlinebuf(stdout); + switch (statMode) { + case (VMSTAT): + new_format(); + break; + case (VMSUMSTAT): + sum_format(); + break; + case (DISKSTAT): + diskformat(); + break; + case (PARTITIONSTAT): + diskpartition_format(partition); + break; + case (SLABSTAT): + slabformat(); + break; + case (DISKSUMSTAT): + disksum_format(); + break; + default: + usage(stderr); + break; + } + return 0; +} @@ -0,0 +1,896 @@ +/* + * w - show what logged in users are doing. + * + * Copyright © 2009-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2002-2006 Albert Cahalan + * Copyright © 1996 Charles Blake + * + * Rewritten, older version: + * Copyright © 1993 Larry Greenfield + * with some fixes by Michael K. Johnson. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <locale.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> +#ifdef HAVE_UTMPX_H +#include <utmpx.h> +#ifndef HAVE_UT_HOSTSIZE_IN_UTMPX +#include <utmp.h> +#endif +#else +# include <utmp.h> +#endif +#include <arpa/inet.h> +#ifdef WITH_SYSTEMD +# include <systemd/sd-login.h> +# include <systemd/sd-daemon.h> +#endif +#ifdef WITH_ELOGIND +# include <elogind/sd-login.h> +# include <elogind/sd-daemon.h> +#endif + +#include "c.h" +#include "fileutils.h" +#include "nls.h" + +#include "misc.h" +#include "pids.h" + +static int ignoreuser = 0; /* for '-u' */ +static int oldstyle = 0; /* for '-o' */ + +#ifdef HAVE_UTMPX_H +typedef struct utmpx utmp_t; +#else +typedef struct utmp utmp_t; +#endif + +#ifdef __GLIBC__ +#if !defined(UT_HOSTSIZE) || defined(__UT_HOSTSIZE) +# define UT_HOSTSIZE __UT_HOSTSIZE +# define UT_LINESIZE __UT_LINESIZE +# define UT_NAMESIZE __UT_NAMESIZE +#endif +#endif + +#ifdef W_SHOWFROM +# define FROM_STRING "on" +#else +# define FROM_STRING "off" +#endif + +#define MAX_CMD_WIDTH 512 +#define MIN_CMD_WIDTH 7 + +/* + * This routine is careful since some programs leave utmp strings + * unprintable. Always outputs at least 16 chars padded with + * spaces on the right if necessary. + */ +static void print_host(const char *restrict host, int len, const int fromlen) +{ + const char *last; + int width = 0; + + if (len > fromlen) + len = fromlen; + last = host + len; + for (; host < last; host++) { + if (*host == '\0') break; + if (isprint(*host) && *host != ' ') { + fputc(*host, stdout); + ++width; + } else { + fputc('-', stdout); + ++width; + break; + } + } + + /* + * space-fill, and a '-' too if needed to ensure the + * column exists + */ + if (!width) { + fputc('-', stdout); + ++width; + } + while (width++ < fromlen) + fputc(' ', stdout); +} + + +/* This routine prints the display part of the host or IPv6 link address interface */ +static void print_display_or_interface(const char *restrict host, int len, int restlen) +{ + const char *const end = host + (len > 0 ? len : 0); + const char *disp, *tmp; + + if (restlen <= 0) return; /* not enough space for printing anything */ + + /* search for a collon (might be a display) */ + disp = host; + while ( (disp < end) && (*disp != ':') && isprint(*disp) ) disp++; + + /* colon found */ + if (disp < end && *disp == ':') { + /* detect multiple colons -> IPv6 in the host (not a display) */ + tmp = disp+1; + while ( (tmp < end) && (*tmp != ':') && isprint(*tmp) ) tmp++; + + if (tmp >= end || *tmp != ':') { /* multiple colons not found - it's a display */ + + /* number of chars till the end of the input field */ + len -= (disp - host); + + /* if it is still longer than the rest of the output field, then cut it */ + if (len > restlen) len = restlen; + + /* print the display */ + while ((len > 0) && isprint(*disp) && (*disp != ' ')) { + len--; restlen--; + fputc(*disp, stdout); + disp++; + } + + if ((len > 0) && (*disp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */ + restlen--; + fputc('-', stdout); + } + } else { /* multiple colons found - it's an IPv6 address */ + + /* search for % (interface separator in case of IPv6 link address) */ + while ( (tmp < end) && (*tmp != '%') && isprint(*tmp) ) tmp++; + + if (tmp < end && *tmp == '%') { /* interface separator found */ + + /* number of chars till the end of the input field */ + len -= (tmp - host); + + /* if it is still longer than the rest of the output field, then cut it */ + if (len > restlen) len = restlen; + + /* print the interface */ + while ((len > 0) && isprint(*tmp) && (*tmp != ' ')) { + len--; restlen--; + fputc(*tmp, stdout); + tmp++; + } + if ((len > 0) && (*tmp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */ + restlen--; + fputc('-', stdout); + } + } + } + } + + /* padding with spaces */ + while (restlen > 0) { + fputc(' ', stdout); + restlen--; + } +} + + +/* This routine prints either the hostname or the IP address of the remote */ +static void print_from( +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + const char *session, +#endif + const utmp_t *restrict const u, const int ip_addresses, const int fromlen) { +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + if (session) { + char *host = NULL; + int r; + + r = sd_session_get_remote_host(session, &host); + if (r < 0 || host == NULL) + print_host("", 0, fromlen); + else { + print_host(host, strlen(host), fromlen); + free(host); + } + } else { +#endif + char buf[fromlen + 1]; + char buf_ipv6[INET6_ADDRSTRLEN]; + int len; +#ifndef __CYGWIN__ + int32_t ut_addr_v6[4]; /* IP address of the remote host */ + + if (ip_addresses) { /* -i switch used */ + memcpy(&ut_addr_v6, &u->ut_addr_v6, sizeof(ut_addr_v6)); + if (IN6_IS_ADDR_V4MAPPED(&ut_addr_v6)) { + /* map back */ + ut_addr_v6[0] = ut_addr_v6[3]; + ut_addr_v6[1] = 0; + ut_addr_v6[2] = 0; + ut_addr_v6[3] = 0; + } + if (ut_addr_v6[1] || ut_addr_v6[2] || ut_addr_v6[3]) { + /* IPv6 */ + if (!inet_ntop(AF_INET6, &ut_addr_v6, buf_ipv6, sizeof(buf_ipv6))) { + strcpy(buf, ""); /* invalid address, clean the buffer */ + } else { + strncpy(buf, buf_ipv6, fromlen); /* address valid, copy to buffer */ + } + } else { + /* IPv4 */ + if (!(ut_addr_v6[0] && inet_ntop(AF_INET, &ut_addr_v6[0], buf, sizeof(buf)))) { + strcpy(buf, ""); /* invalid address, clean the buffer */ + } + } + buf[fromlen] = '\0'; + + len = strlen(buf); + if (len) { /* IP address is non-empty, print it (and concatenate with display, if present) */ + fputs(buf, stdout); + /* show the display part of the host or IPv6 link addr. interface, if present */ + print_display_or_interface(u->ut_host, UT_HOSTSIZE, fromlen - len); + } else { /* IP address is empty, print the host instead */ + print_host(u->ut_host, UT_HOSTSIZE, fromlen); + } + } else { /* -i switch NOT used */ + print_host(u->ut_host, UT_HOSTSIZE, fromlen); + } +#else + print_host(u->ut_host, UT_HOSTSIZE, fromlen); +#endif +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + } +#endif +} + + +/* compact 7 char format for time intervals (belongs in libproc?) */ +static void print_time_ival7(time_t t, int centi_sec, FILE * fout) +{ + if ((long)t < (long)0) { + /* system clock changed? */ + printf(" ? "); + return; + } + if (oldstyle) { + if (t >= 48 * 60 * 60) + /* > 2 days */ + fprintf(fout, _(" %2lludays"), (unsigned long long)t / (24 * 60 * 60)); + else if (t >= 60 * 60) + /* > 1 hour */ + /* Translation Hint: Hours:Minutes */ + fprintf(fout, " %2llu:%02u ", (unsigned long long)t / (60 * 60), + (unsigned)((t / 60) % 60)); + else if (t > 60) + /* > 1 minute */ + /* Translation Hint: Minutes:Seconds */ + fprintf(fout, _(" %2llu:%02um"), (unsigned long long)t / 60, (unsigned)t % 60); + else + fprintf(fout, " "); + } else { + if (t >= 48 * 60 * 60) + /* 2 days or more */ + fprintf(fout, _(" %2lludays"), (unsigned long long)t / (24 * 60 * 60)); + else if (t >= 60 * 60) + /* 1 hour or more */ + /* Translation Hint: Hours:Minutes */ + fprintf(fout, _(" %2llu:%02um"), (unsigned long long)t / (60 * 60), + (unsigned)((t / 60) % 60)); + else if (t > 60) + /* 1 minute or more */ + /* Translation Hint: Minutes:Seconds */ + fprintf(fout, " %2llu:%02u ", (unsigned long long)t / 60, (unsigned)t % 60); + else + /* Translation Hint: Seconds:Centiseconds */ + fprintf(fout, _(" %2llu.%02us"), (unsigned long long)t, centi_sec); + } +} + +/* stat the device file to get an idle time */ +static time_t idletime(const char *restrict const tty) +{ + struct stat sbuf; + if (stat(tty, &sbuf) != 0) + return 0; + return time(NULL) - sbuf.st_atime; +} + +/* 7 character formatted login time */ + +static void print_logintime(time_t logt, FILE * fout) +{ + + /* Abbreviated of weekday can be longer than 3 characters, + * see for instance hu_HU. Using 16 is few bytes more than + * enough. */ + char time_str[16]; + time_t curt; + struct tm *logtm, *curtm; + int today; + + curt = time(NULL); + curtm = localtime(&curt); + /* localtime returns a pointer to static memory */ + today = curtm->tm_yday; + logtm = localtime(&logt); + if (curt - logt > 12 * 60 * 60 && logtm->tm_yday != today) { + if (curt - logt > 6 * 24 * 60 * 60) { + strftime(time_str, sizeof(time_str), "%b", logtm); + fprintf(fout, " %02d%3s%02d", logtm->tm_mday, + time_str, logtm->tm_year % 100); + } else { + strftime(time_str, sizeof(time_str), "%a", logtm); + fprintf(fout, " %3s%02d ", time_str, + logtm->tm_hour); + } + } else { + fprintf(fout, " %02d:%02d ", logtm->tm_hour, logtm->tm_min); + } +} + +/* + * Get the Device ID of the given TTY + */ +static int get_tty_device(const char *restrict const name) +{ + struct stat st; + static char buf[32]; + char *dev_paths[] = { "/dev/%s", "/dev/tty%s", "/dev/pts/%s", NULL}; + int i; + + if (name[0] == '/' && stat(name, &st) == 0) + return st.st_rdev; + + for (i=0; dev_paths[i] != NULL; i++) { + snprintf(buf, 32, dev_paths[i], name); + if (stat(buf, &st) == 0 && (st.st_mode & S_IFMT) == S_IFCHR) + return st.st_rdev; + } + return -1; +} + +/* + * This function scans the process table accumulating total cpu + * times for any processes "associated" with this login session. + * It also searches for the "best" process to report as "(w)hat" + * the user for that login session is doing currently. This the + * essential core of 'w'. + */ +static int find_best_proc( +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + const char *session, +#endif + const utmp_t * restrict const u, + const char *restrict const tty, + unsigned long long *restrict const jcpu, + unsigned long long *restrict const pcpu, + char *cmdline, + pid_t *pid) +{ +#define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, reap->stacks[i], info) +#define PIDS_GETUNT(e) PIDS_VAL(EU_ ## e, u_int, reap->stacks[i], info) +#define PIDS_GETULL(e) PIDS_VAL(EU_ ## e, ull_int, reap->stacks[i], info) +#define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, reap->stacks[i], info) + unsigned uid = ~0U; + pid_t ut_pid = -1; + int found_utpid = 0; + int i, total_procs, line; + unsigned long long best_time = 0; + unsigned long long secondbest_time = 0; + + struct pids_info *info=NULL; + struct pids_fetch *reap; + enum pids_item items[] = { + PIDS_ID_PID, + PIDS_ID_TGID, + PIDS_TICS_BEGAN, + PIDS_ID_EUID, + PIDS_ID_RUID, + PIDS_ID_TPGID, + PIDS_ID_PGRP, + PIDS_TTY, + PIDS_TICS_ALL, + PIDS_CMDLINE}; + enum rel_items { + EU_PID, EU_TGID, EU_START, EU_EUID, EU_RUID, EU_TPGID, EU_PGRP, EU_TTY, + EU_TICS_ALL, EU_CMDLINE}; + + *jcpu = 0; + *pcpu = 0; + if (!ignoreuser) { +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + if (session) { + if (sd_session_get_uid(session, &uid) < 0) + return 0; + } else { +#endif + char buf[UT_NAMESIZE + 1]; + struct passwd *passwd_data; + strncpy(buf, u->ut_user, UT_NAMESIZE); + buf[UT_NAMESIZE] = '\0'; + if ((passwd_data = getpwnam(buf)) == NULL) + return 0; + uid = passwd_data->pw_uid; + /* OK to have passwd_data go out of scope here */ +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + } +#endif + } + + line = get_tty_device(tty); + + if (procps_pids_new(&info, items, 10) < 0) + xerrx(EXIT_FAILURE, + _("Unable to create pid info structure")); + if ((reap = procps_pids_reap(info, PIDS_FETCH_TASKS_ONLY)) == NULL) + xerrx(EXIT_FAILURE, + _("Unable to load process information")); + total_procs = reap->counts->total; + + if (u) + ut_pid = u->ut_pid; +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + else + sd_session_get_leader(session, &ut_pid); +#endif + + for (i=0; i < total_procs; i++) { + /* is this the login process? */ + if (PIDS_GETINT(TGID) == ut_pid) { + found_utpid = 1; + if (!best_time) { + best_time = PIDS_GETULL(START); + strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH); + *pid = PIDS_GETULL(PID); + *pcpu = PIDS_GETULL(TICS_ALL); + } + + } + if (PIDS_GETINT(TTY) != line) + continue; + (*jcpu) += PIDS_VAL(EU_TICS_ALL, ull_int, reap->stacks[i], info); + if (!(secondbest_time && PIDS_GETULL(START) <= secondbest_time)) { + secondbest_time = PIDS_GETULL(START); + if (cmdline[0] == '-' && cmdline[1] == '\0') { + strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH); + *pid = PIDS_GETULL(PID); + *pcpu = PIDS_GETULL(TICS_ALL); + } + } + if ( + (!ignoreuser && uid != PIDS_GETUNT(EUID) + && uid != PIDS_GETUNT(RUID)) + || (PIDS_GETINT(PGRP) != PIDS_GETINT(TPGID)) + || (PIDS_GETULL(START) <= best_time) + ) + continue; + best_time = PIDS_GETULL(START); + strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH); + *pid = PIDS_GETULL(PID); + *pcpu = PIDS_GETULL(TICS_ALL); + } + procps_pids_unref(&info); + return found_utpid; +#undef PIDS_GETINT +#undef PIDS_GETUNT +#undef PIDS_GETULL +#undef PIDS_GETSTR +} + +static void showinfo( +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + const char *session, const char *name, +#endif + utmp_t * u, int formtype, int maxcmd, int from, + const int userlen, const int fromlen, const int ip_addresses, + const int pids) +{ + unsigned long long jcpu, pcpu; + unsigned i; + char uname[UT_NAMESIZE + 1] = "", tty[5 + UT_LINESIZE + 1] = "/dev/"; + long hertz; + char cmdline[MAX_CMD_WIDTH + 1]; + pid_t best_pid = -1; + int pids_length = 0; + + strcpy(cmdline, "-"); + + hertz = procps_hertz_get(); + +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + if (session) { + char *sd_tty; + + if (sd_session_get_tty(session, &sd_tty) >= 0) { + for (i = 0; i < strlen (sd_tty); i++) + /* clean up tty if garbled */ + if (isalnum(sd_tty[i]) || (sd_tty[i] == '/')) + tty[i + 5] = sd_tty[i]; + else + tty[i + 5] = '\0'; + free(sd_tty); + } + } else { +#endif + for (i = 0; i < UT_LINESIZE; i++) + /* clean up tty if garbled */ + if (isalnum(u->ut_line[i]) || (u->ut_line[i] == '/')) + tty[i + 5] = u->ut_line[i]; + else + tty[i + 5] = '\0'; +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + } +#endif + + if (find_best_proc( +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + session, +#endif + u, tty + 5, &jcpu, &pcpu, cmdline, &best_pid) == 0) + /* + * just skip if stale utmp entry (i.e. login proc doesn't + * exist). If there is a desire a cmdline flag could be + * added to optionally show it with a prefix of (stale) + * in front of cmd or something like that. + */ + return; + +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + if (name) + strncpy(uname, name, UT_NAMESIZE); + else +#endif + strncpy(uname, u->ut_user, UT_NAMESIZE); + /* force NUL term for printf */ + uname[UT_NAMESIZE] = '\0'; + + if (formtype) { + printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, tty + 5); +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + if (session) { + uint64_t ltime; + + if (from) + print_from(session, NULL, ip_addresses, fromlen); + + sd_session_get_start_time(session, <ime); + print_logintime(ltime/((uint64_t) 1000000ULL), stdout); + } else { +#endif + if (from) + print_from( +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + NULL, +#endif + u, ip_addresses, fromlen); + +#ifdef HAVE_UTMPX_H + print_logintime(u->ut_tv.tv_sec, stdout); +#else + print_logintime(u->ut_time, stdout); +#endif +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + } +#endif + if (u && *u->ut_line == ':') + /* idle unknown for xdm logins */ + printf(" ?xdm? "); + else + print_time_ival7(idletime(tty), 0, stdout); + print_time_ival7(jcpu / hertz, (jcpu % hertz) * (100. / hertz), + stdout); + if (pcpu > 0) + print_time_ival7(pcpu / hertz, + (pcpu % hertz) * (100. / hertz), + stdout); + else + printf(" ? "); + } else { + printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, tty + 5); + if (from) + print_from( +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + NULL, +#endif + u, ip_addresses, fromlen); + if (u && *u->ut_line == ':') + /* idle unknown for xdm logins */ + printf(" ?xdm? "); + else + print_time_ival7(idletime(tty), 0, stdout); + } + if (pids) { + pid_t ut_pid = -1; + if (u) + ut_pid = u->ut_pid; +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + else + sd_session_get_leader(session, &ut_pid); +#endif + pids_length = printf(" %d/%d", ut_pid, best_pid); + if (pids_length > maxcmd) { + maxcmd = 0; + } else if (pids_length > 0) { + maxcmd -= pids_length; + } + } + printf(" %.*s\n", maxcmd, cmdline); +} + +static void __attribute__ ((__noreturn__)) + usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] [user]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -h, --no-header do not print header\n"),out); + fputs(_(" -u, --no-current ignore current process username\n"),out); + fputs(_(" -s, --short short format\n"),out); + fputs(_(" -f, --from show remote hostname field\n"),out); + fputs(_(" -o, --old-style old style output\n"),out); + fputs(_(" -i, --ip-addr display IP address instead of hostname (if possible)\n"), out); + fputs(_(" -p, --pids show the PID(s) of processes in WHAT\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(_(" --help display this help and exit\n"), out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("w(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + char *user = NULL, *p; + utmp_t *u; + struct winsize win; + int ch; + int maxcmd = 80; + int userlen = 8; + int fromlen = 16; + char *env_var; + + /* switches (defaults) */ + int header = 1; + int longform = 1; + int from = 1; + int ip_addresses = 0; + int pids = 0; + + enum { + HELP_OPTION = CHAR_MAX + 1 + }; + + static const struct option longopts[] = { + {"no-header", no_argument, NULL, 'h'}, + {"no-current", no_argument, NULL, 'u'}, + {"short", no_argument, NULL, 's'}, + {"from", no_argument, NULL, 'f'}, + {"old-style", no_argument, NULL, 'o'}, + {"ip-addr", no_argument, NULL, 'i'}, + {"pids", no_argument, NULL, 'p'}, + {"help", no_argument, NULL, HELP_OPTION}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + +#ifndef W_SHOWFROM + from = 0; +#endif + + while ((ch = + getopt_long(argc, argv, "husfoVip", longopts, NULL)) != -1) + switch (ch) { + case 'h': + header = 0; + break; + case 's': + longform = 0; + break; + case 'f': + from = !from; + break; + case 'V': + printf(PROCPS_NG_VERSION); + exit(0); + case 'u': + ignoreuser = 1; + break; + case 'o': + oldstyle = 1; + break; + case 'i': + ip_addresses = 1; + from = 1; + break; + case 'p': + pids = 1; + break; + case HELP_OPTION: + usage(stdout); + default: + usage(stderr); + } + + if ((argv[optind])) + user = (argv[optind]); + + /* Get user field length from environment */ + if ((env_var = getenv("PROCPS_USERLEN")) != NULL) { + int ut_namesize = UT_NAMESIZE; + userlen = atoi(env_var); + if (userlen < 8 || ut_namesize < userlen) { + xwarnx + (_("User length environment PROCPS_USERLEN must be between 8 and %i, ignoring.\n"), + ut_namesize); + userlen = 8; + } + } + /* Get from field length from environment */ + if ((env_var = getenv("PROCPS_FROMLEN")) != NULL) { + fromlen = atoi(env_var); + if (fromlen < 8 || UT_HOSTSIZE < fromlen) { + xwarnx + (_("from length environment PROCPS_FROMLEN must be between 8 and %d, ignoring\n"), + UT_HOSTSIZE); + fromlen = 16; + } + } + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 && win.ws_col > 0) + maxcmd = win.ws_col; + else if ((p = getenv("COLUMNS"))) + maxcmd = atoi(p); + else + maxcmd = MAX_CMD_WIDTH; +#define CLAMP_CMD_WIDTH(cw) do { \ + if ((cw) < MIN_CMD_WIDTH) (cw) = MIN_CMD_WIDTH; \ + if ((cw) > MAX_CMD_WIDTH) (cw) = MAX_CMD_WIDTH; \ +} while (0) + CLAMP_CMD_WIDTH(maxcmd); + maxcmd -= 21 + userlen + (from ? fromlen : 0) + (longform ? 20 : 0); + CLAMP_CMD_WIDTH(maxcmd); +#undef CLAMP_CMD_WIDTH + + + if (header) { + /* print uptime and headers */ + printf("%s\n", procps_uptime_sprint()); + /* Translation Hint: Following five uppercase messages are + * headers. Try to keep alignment intact. */ + printf(_("%-*s TTY "), userlen, _("USER")); + if (from) + printf("%-*s", fromlen - 1, _("FROM")); + if (longform) + printf(_(" LOGIN@ IDLE JCPU PCPU WHAT\n")); + else + printf(_(" IDLE WHAT\n")); + } +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + if (sd_booted() > 0) { + char **sessions_list; + int sessions; + int i; + + sessions = sd_get_sessions (&sessions_list); + if (sessions < 0 && sessions != -ENOENT) + error(EXIT_FAILURE, -sessions, _("error getting sessions")); + + if (sessions >= 0) { + for (int i = 0; i < sessions; i++) { + char *name; + int r; + + if ((r = sd_session_get_username(sessions_list[i], &name)) < 0) + error(EXIT_FAILURE, -r, _("get user name failed")); + + if (user) { + if (!strcmp(name, user)) + showinfo(sessions_list[i], name, NULL, longform, + maxcmd, from, userlen, fromlen, + ip_addresses, pids); + } else { + showinfo(sessions_list[i], name, NULL, longform, maxcmd, + from, userlen, fromlen, ip_addresses, pids); + } + free(name); + free(sessions_list[i]); + } + free(sessions_list); + } + } else { +#endif +#ifdef HAVE_UTMPX_H + setutxent(); +#else + utmpname(UTMP_FILE); + setutent(); +#endif + if (user) { + for (;;) { +#ifdef HAVE_UTMPX_H + u = getutxent(); +#else + u = getutent(); +#endif + if (!u) + break; + if (u->ut_type != USER_PROCESS) + continue; + if (!strncmp(u->ut_user, user, UT_NAMESIZE)) + showinfo( +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + NULL, NULL, +#endif + u, longform, maxcmd, from, userlen, + fromlen, ip_addresses, pids); + } + } else { + for (;;) { +#ifdef HAVE_UTMPX_H + u = getutxent(); +#else + u = getutent(); +#endif + if (!u) + break; + if (u->ut_type != USER_PROCESS) + continue; + if (*u->ut_user) + showinfo( +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + NULL, NULL, +#endif + u, longform, maxcmd, from, userlen, + fromlen, ip_addresses, pids); + } + } +#ifdef HAVE_UTMPX_H + endutxent(); +#else + endutent(); +#endif +#if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) + } +#endif + + return EXIT_SUCCESS; +} diff --git a/src/watch.c b/src/watch.c new file mode 100644 index 0000000..5c159a9 --- /dev/null +++ b/src/watch.c @@ -0,0 +1,1045 @@ +/* + * watch - execute a program repeatedly, displaying output fullscreen + * + * Copyright © 2010-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> + * Copyright © 2002-2007 Albert Cahalan + * Copyright © 1999 Mike Coleman <mkc@acm.org>. + * + * Based on the original 1991 'watch' by Tony Rems <rembo@unisoft.com> + * (with mods and corrections by Francois Pinard). + * + * stderr handling, exec, and beep option added by Morty Abzug, 2008 + * Unicode Support added by Jarrod Lowe <procps@rrod.net> in 2009. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "c.h" +#include "config.h" +#include "fileutils.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <locale.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> +#ifdef WITH_WATCH8BIT +# define _XOPEN_SOURCE_EXTENDED 1 +# include <wchar.h> +# include <wctype.h> +# include <ncursesw/ncurses.h> +#else +# include <ncurses.h> +#endif /* WITH_WATCH8BIT */ + +#ifdef FORCE_8BIT +# undef isprint +# define isprint(x) ( (x>=' '&&x<='~') || (x>=0xa0) ) +#endif + +/* Boolean command line options */ +static int flags; +#define WATCH_DIFF (1 << 1) +#define WATCH_CUMUL (1 << 2) +#define WATCH_EXEC (1 << 3) +#define WATCH_BEEP (1 << 4) +#define WATCH_COLOR (1 << 5) +#define WATCH_ERREXIT (1 << 6) +#define WATCH_CHGEXIT (1 << 7) +#define WATCH_EQUEXIT (1 << 8) +#define WATCH_NORERUN (1 << 9) + +static int curses_started = 0; +static long height = 24, width = 80; +static int screen_size_changed = 0; +static int first_screen = 1; +static int show_title = 2; /* number of lines used, 2 or 0 */ +static int precise_timekeeping = 0; +static int line_wrap = 1; + +#define min(x,y) ((x) > (y) ? (y) : (x)) +#define MAX_ANSIBUF 100 + +static void __attribute__ ((__noreturn__)) + usage(FILE * out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] command\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -b, --beep beep if command has a non-zero exit\n"), out); + fputs(_(" -c, --color interpret ANSI color and style sequences\n"), out); + fputs(_(" -C, --no-color do not interpret ANSI color and style sequences\n"), out); + fputs(_(" -d, --differences[=<permanent>]\n" + " highlight changes between updates\n"), out); + fputs(_(" -e, --errexit exit if command has a non-zero exit\n"), out); + fputs(_(" -g, --chgexit exit when output from command changes\n"), out); + fputs(_(" -q, --equexit <cycles>\n" + " exit when output from command does not change\n"), out); + fputs(_(" -n, --interval <secs> seconds to wait between updates\n"), out); + fputs(_(" -p, --precise attempt run command in precise intervals\n"), out); + fputs(_(" -r, --no-rerun do not rerun program on window resize\n"), out); + fputs(_(" -t, --no-title turn off header\n"), out); + fputs(_(" -w, --no-wrap turn off line wrapping\n"), out); + fputs(_(" -x, --exec pass command to exec instead of \"sh -c\"\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(_(" -v, --version output version information and exit\n"), out); + fprintf(out, USAGE_MAN_TAIL("watch(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static int nr_of_colors; +static int attributes; +static int fg_col; +static int bg_col; +static int more_colors; + + +static void reset_ansi(void) +{ + attributes = A_NORMAL; + fg_col = 0; + bg_col = 0; +} + +static void init_ansi_colors(void) +{ + short ncurses_colors[] = { + -1, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, + COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE + }; + nr_of_colors = sizeof(ncurses_colors) / sizeof(short); + + more_colors = (COLORS >= 16) && (COLOR_PAIRS >= 16 * 16); + + // Initialize ncurses colors. -1 is terminal default + // 0-7 are auto created standard colors initialized by ncurses + if (more_colors) { + // Initialize using ANSI SGR 8-bit specified colors + // 8-15 are bright colors + init_color(8, 333, 333, 333); // Bright black + init_color(9, 1000, 333, 333); // Bright red + init_color(10, 333, 1000, 333); // Bright green + init_color(11, 1000, 1000, 333); // Bright yellow + init_color(12, 333, 333, 1000); // Bright blue + init_color(13, 1000, 333, 1000); // Bright magenta + init_color(14, 333, 1000, 1000); // Bright cyan + // Often ncurses is built with only 256 colors, so lets + // stop here - so we can support the -1 terminal default + //init_color(15, 1000, 1000, 1000); // Bright white + nr_of_colors += 7; + } + + // Initialize all color pairs with ncurses + for (bg_col = 0; bg_col < nr_of_colors; bg_col++) + for (fg_col = 0; fg_col < nr_of_colors; fg_col++) + init_pair(bg_col * nr_of_colors + fg_col + 1, fg_col - 1, bg_col - 1); + + reset_ansi(); +} + + +static int process_ansi_color_escape_sequence(char** escape_sequence) { + // process SGR ANSI color escape sequence + // Eg 8-bit + // 38;5;⟨n⟩ (set fg color to n) + // 48;5;⟨n⟩ (set bg color to n) + // + // Eg 24-bit (not yet implemented) + // ESC[ 38;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB foreground color + // ESC[ 48;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB background color + int num; + + if (!escape_sequence) + return 0; /* avoid NULLPTR dereference, return "not understood" */ + + if ((*escape_sequence)[0] != ';') + return 0; /* not understood */ + + if ((*escape_sequence)[1] == '5') { + // 8 bit! ANSI specifies a predefined set of 256 colors here. + if ((*escape_sequence)[2] != ';') + return 0; /* not understood */ + num = strtol((*escape_sequence) + 3, escape_sequence, 10); + if (num >= 0 && num <= 7) { + // 0-7 are standard colors same as SGR 30-37 + return num + 1; + } + if (num >= 8 && num <= 15) { + // 8-15 are standard colors same as SGR 90-97 + return more_colors ? num + 1 : num - 8 + 1; + } + + // Remainder aren't yet implemented + // 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) + // 232-255: grayscale from black to white in 24 steps + } + + return 0; /* not understood */ +} + + +static int set_ansi_attribute(const int attrib, char** escape_sequence) +{ + switch (attrib) { + case -1: /* restore last settings */ + break; + case 0: /* restore default settings */ + reset_ansi(); + break; + case 1: /* set bold / increased intensity */ + attributes |= A_BOLD; + break; + case 2: /* set decreased intensity (if supported) */ + attributes |= A_DIM; + break; +#ifdef A_ITALIC + case 3: /* set italic (if supported) */ + attributes |= A_ITALIC; + break; +#endif + case 4: /* set underline */ + attributes |= A_UNDERLINE; + break; + case 5: /* set blinking */ + attributes |= A_BLINK; + break; + case 7: /* set inversed */ + attributes |= A_REVERSE; + break; + case 21: /* unset bold / increased intensity */ + attributes &= ~A_BOLD; + break; + case 22: /* unset bold / any intensity modifier */ + attributes &= ~(A_BOLD | A_DIM); + break; +#ifdef A_ITALIC + case 23: /* unset italic */ + attributes &= ~A_ITALIC; + break; +#endif + case 24: /* unset underline */ + attributes &= ~A_UNDERLINE; + break; + case 25: /* unset blinking */ + attributes &= ~A_BLINK; + break; + case 27: /* unset inversed */ + attributes &= ~A_REVERSE; + break; + case 38: + fg_col = process_ansi_color_escape_sequence(escape_sequence); + if (fg_col == 0) { + return 0; /* not understood */ + } + break; + case 39: + fg_col = 0; + break; + case 48: + bg_col = process_ansi_color_escape_sequence(escape_sequence); + if (bg_col == 0) { + return 0; /* not understood */ + } + break; + case 49: + bg_col = 0; + break; + default: + if (attrib >= 30 && attrib <= 37) { /* set foreground color */ + fg_col = attrib - 30 + 1; + } else if (attrib >= 40 && attrib <= 47) { /* set background color */ + bg_col = attrib - 40 + 1; + } else if (attrib >= 90 && attrib <= 97) { /* set bright fg color */ + fg_col = more_colors ? attrib - 90 + 9 : attrib - 90 + 1; + } else if (attrib >= 100 && attrib <= 107) { /* set bright bg color */ + bg_col = more_colors ? attrib - 100 + 9 : attrib - 100 + 1; + } else { + return 0; /* Not understood */ + } + } + attr_set(attributes, bg_col * nr_of_colors + fg_col + 1, NULL); + return 1; +} + +static void process_ansi(FILE * fp) +{ + int i, c; + char buf[MAX_ANSIBUF]; + char *numstart, *endptr = buf; + int ansi_attribute; + + c = getc(fp); + + if (c == '(') { + c = getc(fp); + c = getc(fp); + } + if (c != '[') { + ungetc(c, fp); + return; + } + for (i = 0; i < MAX_ANSIBUF; i++) { + c = getc(fp); + /* COLOUR SEQUENCE ENDS in 'm' */ + if (c == 'm') { + buf[i] = '\0'; + break; + } + if ((c < '0' || c > '9') && c != ';') { + return; + } + buf[i] = (char)c; + } + /* + * buf now contains a semicolon-separated list of decimal integers, + * each indicating an attribute to apply. + * For example, buf might contain "0;1;31", derived from the color + * escape sequence "<ESC>[0;1;31m". There can be 1 or more + * attributes to apply, but typically there are between 1 and 3. + */ + + /* Special case of <ESC>[m */ + if (buf[0] == '\0') + set_ansi_attribute(0, NULL); + + for (endptr = numstart = buf; *endptr != '\0'; numstart = endptr + 1) { + ansi_attribute = strtol(numstart, &endptr, 10); + if (!set_ansi_attribute(ansi_attribute, &endptr)) + break; + if (numstart == endptr) + set_ansi_attribute(0, NULL); /* [m treated as [0m */ + } +} + +static void __attribute__ ((__noreturn__)) do_exit(int status) +{ + if (curses_started) + endwin(); + exit(status); +} + +/* signal handler */ +static void die(int notused __attribute__ ((__unused__))) +{ + do_exit(EXIT_SUCCESS); +} + +static void winch_handler(int notused __attribute__ ((__unused__))) +{ + screen_size_changed = 1; +} + +static char env_col_buf[24]; +static char env_row_buf[24]; +static int incoming_cols; +static int incoming_rows; + +static void get_terminal_size(void) +{ + struct winsize w; + if (!incoming_cols) { + /* have we checked COLUMNS? */ + const char *s = getenv("COLUMNS"); + incoming_cols = -1; + if (s && *s) { + long t; + char *endptr; + t = strtol(s, &endptr, 0); + if (!*endptr && 0 < t) + incoming_cols = t; + width = incoming_cols; + snprintf(env_col_buf, sizeof env_col_buf, "COLUMNS=%ld", + width); + putenv(env_col_buf); + } + } + if (!incoming_rows) { + /* have we checked LINES? */ + const char *s = getenv("LINES"); + incoming_rows = -1; + if (s && *s) { + long t; + char *endptr; + t = strtol(s, &endptr, 0); + if (!*endptr && 0 < t) + incoming_rows = t; + height = incoming_rows; + snprintf(env_row_buf, sizeof env_row_buf, "LINES=%ld", + height); + putenv(env_row_buf); + } + } + if (ioctl(STDERR_FILENO, TIOCGWINSZ, &w) == 0) { + if (incoming_cols < 0 || incoming_rows < 0) { + if (incoming_rows < 0 && w.ws_row > 0) { + height = w.ws_row; + snprintf(env_row_buf, sizeof env_row_buf, + "LINES=%ld", height); + putenv(env_row_buf); + } + if (incoming_cols < 0 && w.ws_col > 0) { + width = w.ws_col; + snprintf(env_col_buf, sizeof env_col_buf, + "COLUMNS=%ld", width); + putenv(env_col_buf); + } + } + } +} + +/* get current time in usec */ +typedef unsigned long long watch_usec_t; +#define USECS_PER_SEC (1000000ull) +static watch_usec_t get_time_usec() +{ + struct timeval now; + gettimeofday(&now, NULL); + return USECS_PER_SEC * now.tv_sec + now.tv_usec; +} + +#ifdef WITH_WATCH8BIT +/* read a wide character from a popen'd stream */ +#define MAX_ENC_BYTES 16 +wint_t my_getwc(FILE * s); +wint_t my_getwc(FILE * s) +{ + /* assuming no encoding ever consumes more than 16 bytes */ + char i[MAX_ENC_BYTES]; + int byte = 0; + int convert; + wchar_t rval; + while (1) { + i[byte] = getc(s); + if (i[byte] == EOF) { + return WEOF; + } + byte++; + errno = 0; + mbtowc(NULL, NULL, 0); + convert = mbtowc(&rval, i, byte); + if (convert > 0) { + /* legal conversion */ + return rval; + } + if (byte == MAX_ENC_BYTES) { + while (byte > 1) { + /* at least *try* to fix up */ + ungetc(i[--byte], s); + } + errno = -EILSEQ; + return WEOF; + } + } +} +#endif /* WITH_WATCH8BIT */ + +#ifdef WITH_WATCH8BIT +static void output_header(wchar_t *restrict wcommand, int wcommand_characters, double interval) +#else +static void output_header(char *restrict command, double interval) +#endif /* WITH_WATCH8BIT */ +{ + time_t t = time(NULL); + char *ts = ctime(&t); + char *header; + char *right_header; + int max_host_name_len = (int) sysconf(_SC_HOST_NAME_MAX); + char hostname[max_host_name_len + 1]; + int command_columns = 0; /* not including final \0 */ + + gethostname(hostname, sizeof(hostname)); + + /* + * left justify interval and command, right justify hostname and time, + * clipping all to fit window width + */ + int hlen = asprintf(&header, _("Every %.1fs: "), interval); + int rhlen = asprintf(&right_header, _("%s: %s"), hostname, ts); + + /* + * the rules: + * width < rhlen : print nothing + * width < rhlen + hlen + 1: print hostname, ts + * width = rhlen + hlen + 1: print header, hostname, ts + * width < rhlen + hlen + 4: print header, ..., hostname, ts + * width < rhlen + hlen + wcommand_columns: print header, + * truncated wcommand, ..., hostname, ts + * width > "": print header, wcomand, hostname, ts + * this is slightly different from how it used to be + */ + if (width < rhlen) { + free(header); + free(right_header); + return; + } + if (rhlen + hlen + 1 <= width) { + mvaddstr(0, 0, header); + if (rhlen + hlen + 2 <= width) { + if (width < rhlen + hlen + 4) { + mvaddstr(0, width - rhlen - 4, "... "); + } else { +#ifdef WITH_WATCH8BIT + command_columns = wcswidth(wcommand, -1); + if (width < rhlen + hlen + command_columns) { + /* print truncated */ + int available = width - rhlen - hlen; + int in_use = command_columns; + int wcomm_len = wcommand_characters; + while (available - 4 < in_use) { + wcomm_len--; + in_use = wcswidth(wcommand, wcomm_len); + } + mvaddnwstr(0, hlen, wcommand, wcomm_len); + mvaddstr(0, width - rhlen - 4, "... "); + } else { + mvaddwstr(0, hlen, wcommand); + } +#else + command_columns = strlen(command); + if (width < rhlen + hlen + command_columns) { + /* print truncated */ + mvaddnstr(0, hlen, command, width - rhlen - hlen - 4); + mvaddstr(0, width - rhlen - 4, "... "); + } else { + mvaddnstr(0, hlen, command, width - rhlen - hlen); + } +#endif /* WITH_WATCH8BIT */ + } + } + } + mvaddstr(0, width - rhlen + 1, right_header); + free(header); + free(right_header); + return; +} + +static void find_eol(FILE *p) +{ + int c; +#ifdef WITH_WATCH8BIT + do { + c = my_getwc(p); + } while (c != WEOF + && c!= L'\n'); +#else + do { + c = getc(p); + } while (c != EOF + && c != '\n'); +#endif /* WITH_WATCH8BIT */ +} + +static int run_command(char *restrict command, char **restrict command_argv) +{ + FILE *p; + int x, y; + int oldeolseen = 1; + int pipefd[2]; + pid_t child; + int exit_early = 0; + int buffer_size = 0; + int unchanged_buffer = 0; + int status; + + /* allocate pipes */ + if (pipe(pipefd) < 0) + xerr(7, _("unable to create IPC pipes")); + + /* flush stdout and stderr, since we're about to do fd stuff */ + fflush(stdout); + fflush(stderr); + + /* fork to prepare to run command */ + child = fork(); + + if (child < 0) { /* fork error */ + xerr(2, _("unable to fork process")); + } else if (child == 0) { /* in child */ + close(pipefd[0]); /* child doesn't need read side of pipe */ + close(1); /* prepare to replace stdout with pipe */ + if (dup2(pipefd[1], 1) < 0) { /* replace stdout with write side of pipe */ + xerr(3, _("dup2 failed")); + } + close(pipefd[1]); /* once duped, the write fd isn't needed */ + dup2(1, 2); /* stderr should default to stdout */ + + if (flags & WATCH_EXEC) { /* pass command to exec instead of system */ + if (execvp(command_argv[0], command_argv) == -1) { + xerr(4, _("unable to execute '%s'"), + command_argv[0]); + } + } else { + status = system(command); /* watch manpage promises sh quoting */ + /* propagate command exit status as child exit status */ + if (!WIFEXITED(status)) { /* child exits nonzero if command does */ + exit(EXIT_FAILURE); + } else { + exit(WEXITSTATUS(status)); + } + } + } + + /* otherwise, we're in parent */ + close(pipefd[1]); /* close write side of pipe */ + if ((p = fdopen(pipefd[0], "r")) == NULL) + xerr(5, _("fdopen")); + + reset_ansi(); + for (y = show_title; y < height; y++) { + int eolseen = 0, tabpending = 0, tabwaspending = 0; + if (flags & WATCH_COLOR) + set_ansi_attribute(-1, NULL); +#ifdef WITH_WATCH8BIT + wint_t carry = WEOF; +#endif + for (x = 0; x < width; x++) { +#ifdef WITH_WATCH8BIT + wint_t c = ' '; +#else + int c = ' '; +#endif + int attr = 0; + + if (tabwaspending && (flags & WATCH_COLOR)) + set_ansi_attribute(-1, NULL); + tabwaspending = 0; + + if (!eolseen) { + /* if there is a tab pending, just + * spit spaces until the next stop + * instead of reading characters */ + if (!tabpending) +#ifdef WITH_WATCH8BIT + do { + if (carry == WEOF) { + c = my_getwc(p); + } else { + c = carry; + carry = WEOF; + } + } while (c != WEOF && !iswprint(c) + && c < 128 + && wcwidth(c) == 0 + && c != L'\a' + && c != L'\n' + && c != L'\t' + && (c != L'\033' + || !(flags & WATCH_COLOR))); +#else + do + c = getc(p); + while (c != EOF && !isprint(c) + && c != '\a' + && c != '\n' + && c != '\t' + && (c != L'\033' + || !(flags & WATCH_COLOR))); +#endif + if (c == L'\033' && (flags & WATCH_COLOR)) { + x--; + process_ansi(p); + continue; + } + if (c == L'\n') + if (!oldeolseen && x == 0) { + x = -1; + continue; + } else + eolseen = 1; + else if (c == L'\t') + tabpending = 1; + else if (c == L'\a') { + beep(); + continue; + } +#ifdef WITH_WATCH8BIT + if (x == width - 1 && wcwidth(c) == 2) { + y++; + x = -1; /* process this double-width */ + carry = c; /* character on the next line */ + continue; /* because it won't fit here */ + } + if (c == WEOF || c == L'\n' || c == L'\t') { + c = L' '; + if (flags & WATCH_COLOR) + attrset(A_NORMAL); + } +#else + if (c == EOF || c == '\n' || c == '\t') { + c = ' '; + if (flags & WATCH_COLOR) + attrset(A_NORMAL); + } +#endif + if (tabpending && (((x + 1) % 8) == 0)) { + tabpending = 0; + tabwaspending = 1; + } + } + move(y, x); + + if (!first_screen && !exit_early && (flags & WATCH_CHGEXIT)) { +#ifdef WITH_WATCH8BIT + cchar_t oldc; + in_wch(&oldc); + exit_early = (wchar_t) c != oldc.chars[0]; +#else + chtype oldch = inch(); + unsigned char oldc = oldch & A_CHARTEXT; + exit_early = (unsigned char)c != oldc; +#endif + } + if (!first_screen && !exit_early && (flags & WATCH_EQUEXIT)) { + buffer_size++; +#ifdef WITH_WATCH8BIT + cchar_t oldc; + in_wch(&oldc); + if ((wchar_t) c == oldc.chars[0]) + unchanged_buffer++; +#else + chtype oldch = inch(); + unsigned char oldc = oldch & A_CHARTEXT; + if ((unsigned char)c == oldc) + unchanged_buffer++; +#endif + } + if (flags & WATCH_DIFF) { +#ifdef WITH_WATCH8BIT + cchar_t oldc; + in_wch(&oldc); + attr = !first_screen + && ((wchar_t) c != oldc.chars[0] + || + ((flags & WATCH_CUMUL) + && (oldc.attr & A_ATTRIBUTES))); +#else + chtype oldch = inch(); + unsigned char oldc = oldch & A_CHARTEXT; + attr = !first_screen + && ((unsigned char)c != oldc + || + ((flags & WATCH_CUMUL) + && (oldch & A_ATTRIBUTES))); +#endif + } + if (attr) + standout(); +#ifdef WITH_WATCH8BIT + addnwstr((wchar_t *) & c, 1); +#else + addch(c); +#endif + if (attr) + standend(); +#ifdef WITH_WATCH8BIT + if (wcwidth(c) == 0) { + x--; + } + if (wcwidth(c) == 2) { + x++; + } +#endif + } + oldeolseen = eolseen; + if (!line_wrap) { + reset_ansi(); + if (flags & WATCH_COLOR) + attrset(A_NORMAL); + } + if (!line_wrap && !eolseen) + { + find_eol(p); + } + } + + fclose(p); + + /* harvest child process and get status, propagated from command */ + if (waitpid(child, &status, 0) < 0) + xerr(8, _("waitpid")); + + /* if child process exited in error, beep if option_beep is set */ + if ((!WIFEXITED(status) || WEXITSTATUS(status))) { + if (flags & WATCH_BEEP) + beep(); + if (flags & WATCH_ERREXIT) { + mvaddstr(height - 1, 0, + _("command exit with a non-zero status, press a key to exit")); + refresh(); + fgetc(stdin); + endwin(); + exit(8); + } + } + + if (unchanged_buffer == buffer_size && (flags & WATCH_EQUEXIT)) + exit_early = 1; + + first_screen = 0; + refresh(); + return exit_early; +} + +int main(int argc, char *argv[]) +{ + int optc; + double interval = 2; + int max_cycles = 1; + int cycle_count = 0; + char *interval_string; + char *command; + char **command_argv; + int command_length = 0; /* not including final \0 */ + watch_usec_t last_run = 0; + watch_usec_t next_loop = 0; /* next loop time in us, used for precise time + * keeping only */ +#ifdef WITH_WATCH8BIT + wchar_t *wcommand = NULL; + int wcommand_characters = 0; /* not including final \0 */ +#endif /* WITH_WATCH8BIT */ + +#ifdef WITH_COLORWATCH + flags |= WATCH_COLOR; +#endif /* WITH_COLORWATCH */ + + static struct option longopts[] = { + {"color", no_argument, 0, 'c'}, + {"no-color", no_argument, 0, 'C'}, + {"differences", optional_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"interval", required_argument, 0, 'n'}, + {"beep", no_argument, 0, 'b'}, + {"errexit", no_argument, 0, 'e'}, + {"chgexit", no_argument, 0, 'g'}, + {"equexit", required_argument, 0, 'q'}, + {"exec", no_argument, 0, 'x'}, + {"precise", no_argument, 0, 'p'}, + {"no-rerun", no_argument, 0, 'r'}, + {"no-title", no_argument, 0, 't'}, + {"no-wrap", no_argument, 0, 'w'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + +#ifdef HAVE_PROGRAM_INVOCATION_NAME + program_invocation_name = program_invocation_short_name; +#endif + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + interval_string = getenv("WATCH_INTERVAL"); + if(interval_string != NULL) + interval = strtod_nol_or_err(interval_string, _("Could not parse interval from WATCH_INTERVAL")); + + while ((optc = + getopt_long(argc, argv, "+bCced::ghq:n:prtwvx", longopts, (int *)0)) + != EOF) { + switch (optc) { + case 'b': + flags |= WATCH_BEEP; + break; + case 'c': + flags |= WATCH_COLOR; + break; + case 'C': + flags &= ~WATCH_COLOR; + break; + case 'd': + flags |= WATCH_DIFF; + if (optarg) + flags |= WATCH_CUMUL; + break; + case 'e': + flags |= WATCH_ERREXIT; + break; + case 'g': + flags |= WATCH_CHGEXIT; + break; + case 'q': + flags |= WATCH_EQUEXIT; + max_cycles = strtod_nol_or_err(optarg, _("failed to parse argument")); + break; + case 'r': + flags |= WATCH_NORERUN; + break; + case 't': + show_title = 0; + break; + case 'w': + line_wrap = 0; + break; + case 'x': + flags |= WATCH_EXEC; + break; + case 'n': + interval = strtod_nol_or_err(optarg, _("failed to parse argument")); + break; + case 'p': + precise_timekeeping = 1; + break; + case 'h': + usage(stdout); + break; + case 'v': + printf(PROCPS_NG_VERSION); + return EXIT_SUCCESS; + default: + usage(stderr); + break; + } + } + + if (interval < 0.1) + interval = 0.1; + if (interval > UINT_MAX) + interval = UINT_MAX; + + if (optind >= argc) + usage(stderr); + + /* save for later */ + command_argv = &(argv[optind]); + + command = xstrdup(argv[optind++]); + command_length = strlen(command); + for (; optind < argc; optind++) { + char *endp; + int s = strlen(argv[optind]); + /* space and \0 */ + command = xrealloc(command, command_length + s + 2); + endp = command + command_length; + *endp = ' '; + memcpy(endp + 1, argv[optind], s); + /* space then string length */ + command_length += 1 + s; + command[command_length] = '\0'; + } + +#ifdef WITH_WATCH8BIT + /* convert to wide for printing purposes */ + /*mbstowcs(NULL, NULL, 0); */ + wcommand_characters = mbstowcs(NULL, command, 0); + if (wcommand_characters < 0) { + fprintf(stderr, _("unicode handling error\n")); + exit(EXIT_FAILURE); + } + wcommand = + (wchar_t *) malloc((wcommand_characters + 1) * sizeof(wcommand)); + if (wcommand == NULL) { + fprintf(stderr, _("unicode handling error (malloc)\n")); + exit(EXIT_FAILURE); + } + mbstowcs(wcommand, command, wcommand_characters + 1); +#endif /* WITH_WATCH8BIT */ + + get_terminal_size(); + + /* Catch keyboard interrupts so we can put tty back in a sane + * state. */ + signal(SIGINT, die); + signal(SIGTERM, die); + signal(SIGHUP, die); + signal(SIGWINCH, winch_handler); + + /* Set up tty for curses use. */ + curses_started = 1; + initscr(); + if (flags & WATCH_COLOR) { + if (has_colors()) { + start_color(); + use_default_colors(); + init_ansi_colors(); + } else { + flags &= ~WATCH_COLOR; + } + } + nonl(); + noecho(); + cbreak(); + + if (precise_timekeeping) + next_loop = get_time_usec(); + + while (1) { + if (screen_size_changed) { + get_terminal_size(); + resizeterm(height, width); + clear(); + /* redrawwin(stdscr); */ + screen_size_changed = 0; + first_screen = 1; + } + + if (show_title) +#ifdef WITH_WATCH8BIT + output_header(wcommand, wcommand_characters, interval); +#else + output_header(command, interval); +#endif /* WITH_WATCH8BIT */ + + if (!(flags & WATCH_NORERUN) || + get_time_usec() - last_run > interval * USECS_PER_SEC) { + last_run = get_time_usec(); + int exit = run_command(command, command_argv); + + if (flags & WATCH_EQUEXIT) { + if (cycle_count == max_cycles && exit) { + break; + } else if (exit) { + cycle_count++; + } else { + cycle_count = 0; + } + } else if (exit) { + break; + } + } else { + refresh(); + } + + if (precise_timekeeping) { + watch_usec_t cur_time = get_time_usec(); + next_loop += USECS_PER_SEC * interval; + if (cur_time < next_loop) + usleep(next_loop - cur_time); + } else + if (interval < UINT_MAX / USECS_PER_SEC) + usleep(interval * USECS_PER_SEC); + else + sleep(interval); + } + + endwin(); + return EXIT_SUCCESS; +} |