summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:34:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:34:44 +0000
commite3be059d4da38aa36f1aee1d56f8ceb943d92f1c (patch)
tree26edef31e4e503dd1c92a112de174f366dd61802 /src
parentInitial commit. (diff)
downloadprocps-upstream/2%4.0.4.tar.xz
procps-upstream/2%4.0.4.zip
Adding upstream version 2:4.0.4.upstream/2%4.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/free.c479
-rw-r--r--src/kill.c167
-rw-r--r--src/pgrep.c1212
-rw-r--r--src/pidof.c420
-rw-r--r--src/pmap.c1217
-rw-r--r--src/ps/HACKING46
-rw-r--r--src/ps/common.h512
-rw-r--r--src/ps/display.c681
-rw-r--r--src/ps/global.c651
-rw-r--r--src/ps/help.c223
-rw-r--r--src/ps/output.c2370
-rw-r--r--src/ps/parser.c1268
-rw-r--r--src/ps/regression26
-rw-r--r--src/ps/select.c162
-rw-r--r--src/ps/signames.c170
-rw-r--r--src/ps/sortformat.c949
-rw-r--r--src/ps/stacktrace.c191
-rw-r--r--src/pwdx.c153
-rw-r--r--src/skill.c600
-rw-r--r--src/slabtop.c388
-rw-r--r--src/sysctl.c1070
-rw-r--r--src/tests/test_fileutils.c10
-rw-r--r--src/tests/test_process.c115
-rw-r--r--src/tests/test_shm.c69
-rw-r--r--src/tests/test_strtod_nol.c51
-rw-r--r--src/tests/test_strutils.c38
-rw-r--r--src/tload.c232
-rw-r--r--src/top/top.c7404
-rw-r--r--src/top/top.h792
-rw-r--r--src/top/top_nls.c867
-rw-r--r--src/top/top_nls.h108
-rw-r--r--src/uptime.c127
-rw-r--r--src/vmstat.c1092
-rw-r--r--src/w.c896
-rw-r--r--src/watch.c1045
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 &lt; 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 &rx;
+} // 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;
+}
diff --git a/src/w.c b/src/w.c
new file mode 100644
index 0000000..fd6e75f
--- /dev/null
+++ b/src/w.c
@@ -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, &ltime);
+ 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;
+}