]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsfd: implement --summary and --counter options
authorMasatake YAMATO <yamato@redhat.com>
Sun, 31 Oct 2021 19:52:48 +0000 (04:52 +0900)
committerMasatake YAMATO <yamato@redhat.com>
Thu, 18 Nov 2021 15:04:00 +0000 (00:04 +0900)
Signed-off-by: Masatake YAMATO <yamato@redhat.com>
misc-utils/Makemodule.am
misc-utils/lsfd-counter.c [new file with mode: 0644]
misc-utils/lsfd-counter.h [new file with mode: 0644]
misc-utils/lsfd.c
misc-utils/meson.build
tests/expected/lsfd/option-summary [new file with mode: 0644]
tests/ts/lsfd/lsfd-functions.bash
tests/ts/lsfd/option-summary [new file with mode: 0755]

index abfddf3b39437d5258e9661964756bb6d960bd82..afd11dbe08e7bed1c9ddfe84c465f39c96da7769 100644 (file)
@@ -255,6 +255,8 @@ lsfd_SOURCES = \
        misc-utils/lsfd.h \
        misc-utils/lsfd-filter.h \
        misc-utils/lsfd-filter.c \
+       misc-utils/lsfd-counter.h \
+       misc-utils/lsfd-counter.c \
        misc-utils/lsfd-file.c \
        misc-utils/lsfd-cdev.c \
        misc-utils/lsfd-bdev.c \
diff --git a/misc-utils/lsfd-counter.c b/misc-utils/lsfd-counter.c
new file mode 100644 (file)
index 0000000..55f0fde
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * lsfd-counter.c - counter implementation used in --summary option
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ * Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include "lsfd-counter.h"
+
+#include "xalloc.h"
+
+struct lsfd_counter {
+       char *name;
+       size_t value;
+       struct lsfd_filter *filter;
+};
+
+struct lsfd_counter *lsfd_counter_new(const char *const name, struct lsfd_filter *filter)
+{
+       struct lsfd_counter *counter = xmalloc(sizeof(struct lsfd_counter));
+
+       counter->name = xstrdup(name);
+       counter->value = 0;
+       counter->filter = filter;
+
+       return counter;
+}
+
+void lsfd_counter_free(struct lsfd_counter *counter)
+{
+       lsfd_filter_free(counter->filter);
+       free(counter->name);
+       free(counter);
+}
+
+bool lsfd_counter_accumulate(struct lsfd_counter *counter, struct libscols_line *ln)
+{
+       if (lsfd_filter_apply(counter->filter, ln)) {
+               counter->value++;
+               return true;
+       }
+       return false;
+}
+
+const char *lsfd_counter_name(struct lsfd_counter *counter)
+{
+       return counter->name;
+}
+
+size_t lsfd_counter_value(struct lsfd_counter *counter)
+{
+       return counter->value;
+}
diff --git a/misc-utils/lsfd-counter.h b/misc-utils/lsfd-counter.h
new file mode 100644 (file)
index 0000000..bac11a9
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * lsfd-counter.h - counter implementation used in --summary option
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ * Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_LSFD_COUNTER_H
+#define UTIL_LINUX_LSFD_COUNTER_H
+
+#include "libsmartcols.h"
+#include "lsfd-filter.h"
+#include <stdbool.h>
+
+struct lsfd_counter;
+
+/* The created counter takes the ownership of the filter; the filter is
+ * freed in lsfd_counter_free().
+ */
+struct lsfd_counter *lsfd_counter_new(const char *const name, struct lsfd_filter *filter);
+void lsfd_counter_free(struct lsfd_counter *counter);
+
+bool lsfd_counter_accumulate(struct lsfd_counter *counter, struct libscols_line *ln);
+
+const char *lsfd_counter_name(struct lsfd_counter *counter);
+size_t lsfd_counter_value(struct lsfd_counter *counter);
+
+#endif /* UTIL_LINUX_LSFD_COUNTER_H */
index 874b4919b7982f5f526ad0c1750c62c636b5c7d5..5d9274059e04df717866df74e721b6461c98ff0f 100644 (file)
@@ -59,6 +59,7 @@ static int kcmp(pid_t pid1, pid_t pid2, int type,
 
 #include "lsfd.h"
 #include "lsfd-filter.h"
+#include "lsfd-counter.h"
 
 /*
  * /proc/$pid/mountinfo entries
@@ -189,6 +190,84 @@ static size_t ncolumns;
 static ino_t *mnt_namespaces;
 static size_t nspaces;
 
+struct counter_spec {
+       struct list_head specs;
+       const char *name;
+       const char *expr;
+};
+
+static struct counter_spec default_counter_specs[] = {
+       {
+               .name = N_("processes"),
+               .expr = "ASSOC == 'cwd'",
+       },
+       {
+               .name = N_("root owned processes"),
+               .expr = "(ASSOC == 'cwd') && (UID == 0)",
+       },
+       {
+               .name = N_("kernel threads"),
+               .expr = "(ASSOC == 'cwd') && KTHREAD",
+       },
+       {
+               .name = N_("open files"),
+               .expr = "FD >= 0",
+       },
+       {
+               .name = N_("RO open files"),
+               .expr = "(FD >= 0) and (MODE == 'r--')",
+       },
+       {
+               .name = N_("WO open files"),
+               .expr = "(FD >= 0) and (MODE == '-w-')",
+       },
+       {
+               .name = N_("shared mappings"),
+               .expr = "ASSOC == 'shm'",
+       },
+       {
+               .name = N_("RO shared mappings"),
+               .expr = "(ASSOC == 'shm') and (MODE == 'r--')",
+       },
+       {
+               .name = N_("WO shared mappings"),
+               .expr = "(ASSOC == 'shm') and (MODE == '-w-')",
+       },
+       {
+               .name = N_("regular files"),
+               .expr = "(FD >= 0) && (TYPE == 'REG')",
+       },
+       {
+               .name = N_("directories"),
+               .expr = "(FD >= 0) && (TYPE == 'DIR')",
+       },
+       {
+               .name = N_("sockets"),
+               .expr = "(FD >= 0) && (TYPE == 'SOCK')",
+       },
+       {
+               .name = N_("fifos/pipes"),
+               .expr = "(FD >= 0) && (TYPE == 'FIFO')",
+       },
+       {
+               .name = N_("character devices"),
+               .expr = "(FD >= 0) && (TYPE == 'CHR')",
+       },
+       {
+               .name = N_("block devices"),
+               .expr = "(FD >= 0) && (TYPE == 'BLK')",
+       },
+       {
+               .name = N_("unknown types"),
+               .expr = "(FD >= 0) && (TYPE == 'UNKN')",
+       }
+};
+
+enum {
+       SUMMARY_EMIT = 1 << 0,
+       SUMMARY_ONLY = 1 << 1,
+};
+
 struct lsfd_control {
        struct libscols_table *tb;              /* output */
        struct list_head procs;                 /* list of all processes */
@@ -197,9 +276,11 @@ struct lsfd_control {
                        raw : 1,
                        json : 1,
                        notrunc : 1,
-                       threads : 1;
+                       threads : 1,
+                       summary: 2;
 
        struct lsfd_filter *filter;
+       struct lsfd_counter **counters;         /* NULL terminated array. */
 };
 
 static void xstrappend(char **a, const char *b);
@@ -759,13 +840,23 @@ static void convert(struct list_head *procs, struct lsfd_control *ctl)
                list_for_each (f, &proc->files) {
                        struct file *file = list_entry(f, struct file, files);
                        struct libscols_line *ln = scols_table_new_line(ctl->tb, NULL);
+                       struct lsfd_counter **counter = NULL;
+
                        if (!ln)
                                err(EXIT_FAILURE, _("failed to allocate output line"));
 
                        convert_file(proc, file, ln);
 
-                       if (!lsfd_filter_apply(ctl->filter, ln))
+                       if (!lsfd_filter_apply(ctl->filter, ln)) {
                                scols_table_remove_line(ctl->tb, ln);
+                               continue;
+                       }
+
+                       if (!ctl->counters)
+                               continue;
+
+                       for (counter = ctl->counters; *counter; counter++)
+                               lsfd_counter_accumulate(*counter, ln);
                }
        }
 }
@@ -776,6 +867,12 @@ static void delete(struct list_head *procs, struct lsfd_control *ctl)
 
        scols_unref_table(ctl->tb);
        lsfd_filter_free(ctl->filter);
+       if (ctl->counters) {
+               struct lsfd_counter **counter;
+               for (counter = ctl->counters; *counter; counter++)
+                       lsfd_counter_free(*counter);
+               free(ctl->counters);
+       }
 }
 
 static void emit(struct lsfd_control *ctl)
@@ -1046,6 +1143,9 @@ static void __attribute__((__noreturn__)) usage(void)
        fputs(_(" -p, --pid  <pid(s)>   collect information only specified processes\n"), out);
        fputs(_(" -Q, --filter <expr>   apply display filter\n"), out);
        fputs(_("     --debug-filter    dump the innternal data structure of filter and exit\n"), out);
+       fputs(_(" -C, --counter <name>:<expr>\n"
+               "                       make a counter used in --summary output\n"), out);
+       fputs(_("     --summary[=when]  print summary information (never,always or only)\n"), out);
 
        fputs(USAGE_SEPARATOR, out);
        printf(USAGE_HELP_OPTIONS(23));
@@ -1117,6 +1217,153 @@ static struct lsfd_filter *new_filter(const char *expr, bool debug, const char *
        return filter;
 }
 
+static struct counter_spec *new_counter_spec(const char *spec_str)
+{
+       char *sep;
+       struct counter_spec *spec;
+
+       if (spec_str[0] == '\0')
+               errx(EXIT_FAILURE,
+                    _("too short counter specification: -C/--counter %s"),
+                    spec_str);
+       if (spec_str[0] == ':')
+               errx(EXIT_FAILURE,
+                    _("no name for counter: -C/--counter %s"),
+                    spec_str);
+
+       sep = strchr(spec_str, ':');
+       if (sep == NULL)
+               errx(EXIT_FAILURE,
+                    _("no name for counter: -C/--counter %s"),
+                    spec_str);
+       if (sep[1] == '\0')
+               errx(EXIT_FAILURE,
+                    _("empty ecounter expression given: -C/--counter %s"),
+                    spec_str);
+
+       /* Split the spec_str in to name and expr. */
+       *sep = '\0';
+
+       if (strchr(spec_str, '{'))
+               errx(EXIT_FAILURE,
+                    _("don't use `{' in the name of a counter: %s"),
+                    spec_str);
+
+       spec = xmalloc(sizeof(struct counter_spec));
+       INIT_LIST_HEAD(&spec->specs);
+       spec->name = spec_str;
+       spec->expr = sep + 1;
+
+       return spec;
+}
+
+static void free_counter_spec(struct counter_spec *counter_spec)
+{
+       free(counter_spec);
+}
+
+static struct lsfd_counter *new_counter(struct counter_spec *spec, struct lsfd_control *ctl)
+{
+       struct lsfd_filter *filter;
+
+       filter = new_filter(spec->expr, false,
+                           _("failed in making filter for a counter: "),
+                           ctl);
+       return lsfd_counter_new(spec->name, filter);
+}
+
+static struct lsfd_counter **new_counters(struct list_head *specs, struct lsfd_control *ctl)
+{
+       struct lsfd_counter **counters;
+       size_t len = list_count_entries(specs);
+       size_t i = 0;
+       struct list_head *s;
+
+       counters = xcalloc(len + 1, sizeof(struct lsfd_counter *));
+       list_for_each(s, specs) {
+               struct counter_spec *spec = list_entry(s, struct counter_spec, specs);
+               counters[i++] = new_counter(spec, ctl);
+       }
+       assert(counters[len] == NULL);
+
+       return counters;
+}
+
+static struct lsfd_counter **new_default_counters(struct lsfd_control *ctl)
+{
+       struct lsfd_counter **counters;
+       size_t len = ARRAY_SIZE(default_counter_specs);
+       size_t i;
+
+       counters = xcalloc(len + 1, sizeof(struct lsfd_counter *));
+       for (i = 0; i < len; i++) {
+               struct counter_spec *spec = default_counter_specs + i;
+               counters[i] = new_counter(spec, ctl);
+       }
+       assert(counters[len] == NULL);
+
+       return counters;
+}
+
+static struct libscols_table *new_summary_table(struct lsfd_control *ctl)
+{
+       struct libscols_table *tb = scols_new_table();
+
+       struct libscols_column *name_cl, *value_cl;
+
+       if (!tb)
+               err(EXIT_FAILURE, _("failed to allocate summary table"));
+
+       scols_table_enable_noheadings(tb, ctl->noheadings);
+       scols_table_enable_raw(tb, ctl->raw);
+       scols_table_enable_json(tb, ctl->json);
+
+       if(ctl->json)
+               scols_table_set_name(tb, "lsfd-summary");
+
+
+       value_cl = scols_table_new_column(tb, _("VALUE"), 0, SCOLS_FL_RIGHT);
+       if (!value_cl)
+               err(EXIT_FAILURE, _("failed to allocate summary column"));
+       if (ctl->json)
+               scols_column_set_json_type(value_cl, SCOLS_JSON_NUMBER);
+
+       name_cl = scols_table_new_column(tb, _("COUNTER"), 0, 0);
+       if (!name_cl)
+               err(EXIT_FAILURE, _("failed to allocate summary column"));
+       if (ctl->json)
+               scols_column_set_json_type(name_cl, SCOLS_JSON_STRING);
+
+       return tb;
+}
+
+static void fill_summary_line(struct libscols_line *ln, struct lsfd_counter *counter)
+{
+       char *str = NULL;
+
+       xasprintf(&str, "%llu", (unsigned long long)lsfd_counter_value(counter));
+       if (!str)
+               err(EXIT_FAILURE, _("failed to add summary data"));
+       if (scols_line_refer_data(ln, 0, str))
+               err(EXIT_FAILURE, _("failed to add summary data"));
+
+       if (scols_line_set_data(ln, 1, lsfd_counter_name(counter)))
+               err(EXIT_FAILURE, _("failed to add summary data"));
+}
+
+static void emit_summary(struct lsfd_control *ctl, struct lsfd_counter **counter)
+{
+       struct libscols_table *tb = new_summary_table(ctl);
+
+       for (; *counter; counter++) {
+               struct libscols_line *ln = scols_table_new_line(tb, NULL);
+               fill_summary_line(ln, *counter);
+       }
+       scols_print_table(tb);
+
+       scols_unref_table(tb);
+}
+
 int main(int argc, char *argv[])
 {
        int c;
@@ -1127,9 +1374,13 @@ int main(int argc, char *argv[])
        bool debug_filter = false;
        pid_t *pids = NULL;
        int n_pids = 0;
+       struct list_head counter_specs;
+
+       INIT_LIST_HEAD(&counter_specs);
 
        enum {
-               OPT_DEBUG_FILTER = CHAR_MAX + 1
+               OPT_DEBUG_FILTER = CHAR_MAX + 1,
+               OPT_SUMMARY,
        };
        static const struct option longopts[] = {
                { "noheadings", no_argument, NULL, 'n' },
@@ -1143,6 +1394,8 @@ int main(int argc, char *argv[])
                { "pid",        required_argument, NULL, 'p' },
                { "filter",     required_argument, NULL, 'Q' },
                { "debug-filter",no_argument, NULL, OPT_DEBUG_FILTER },
+               { "summary",    optional_argument, NULL,  OPT_SUMMARY },
+               { "counter",    required_argument, NULL, 'C' },
                { NULL, 0, NULL, 0 },
        };
 
@@ -1151,7 +1404,7 @@ int main(int argc, char *argv[])
        textdomain(PACKAGE);
        close_stdout_atexit();
 
-       while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:", longopts, NULL)) != -1) {
+       while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:C:s", longopts, NULL)) != -1) {
                switch (c) {
                case 'n':
                        ctl.noheadings = 1;
@@ -1177,9 +1430,27 @@ int main(int argc, char *argv[])
                case 'Q':
                        append_filter_expr(&filter_expr, optarg, true);
                        break;
+               case 'C': {
+                       struct counter_spec *c = new_counter_spec(optarg);
+                       list_add_tail(&c->specs, &counter_specs);
+                       break;
+               }
                case OPT_DEBUG_FILTER:
                        debug_filter = true;
                        break;
+               case OPT_SUMMARY:
+                       if (optarg) {
+                               if (strcmp(optarg, "never") == 0)
+                                       ctl.summary = 0;
+                               else if (strcmp(optarg, "only") == 0)
+                                       ctl.summary |= (SUMMARY_ONLY|SUMMARY_EMIT);
+                               else if (strcmp(optarg, "always") == 0)
+                                       ctl.summary |= SUMMARY_EMIT;
+                               else
+                                       errx(EXIT_FAILURE, _("unsupported --summary argument"));
+                       } else
+                               ctl.summary |= SUMMARY_EMIT;
+                       break;
                case 'V':
                        print_version(EXIT_SUCCESS);
                case 'h':
@@ -1239,6 +1510,17 @@ int main(int argc, char *argv[])
                free(filter_expr);
        }
 
+       /* make counters */
+       if (ctl.summary & SUMMARY_EMIT) {
+               if (list_empty(&counter_specs))
+                       ctl.counters = new_default_counters(&ctl);
+               else {
+                       ctl.counters = new_counters(&counter_specs, &ctl);
+                       list_free(&counter_specs, struct counter_spec, specs,
+                                 free_counter_spec);
+               }
+       }
+
        if (n_pids > 0)
                sort_pids(pids, n_pids);
 
@@ -1250,7 +1532,10 @@ int main(int argc, char *argv[])
        free(pids);
 
        convert(&ctl.procs, &ctl);
-       emit(&ctl);
+       if (!(ctl.summary & SUMMARY_ONLY))
+               emit(&ctl);
+       if (ctl.counters && (ctl.summary & SUMMARY_EMIT))
+               emit_summary(&ctl, ctl.counters);
 
        /* cleanup */
        delete(&ctl.procs, &ctl);
index bff8947b05f3058e73ce37470d4fd25f6a2c56ee..d59d8e4b5d106f909b02d5b7b768ef0cb940904e 100644 (file)
@@ -44,6 +44,8 @@ lsfd_sources = files (
   'lsfd.h',
   'lsfd-filter.h',
   'lsfd-filter.c',
+  'lsfd-counter.h',
+  'lsfd-counter.c',
   'lsfd-file.c',
   'lsfd-cdev.c',
   'lsfd-bdev.c',
diff --git a/tests/expected/lsfd/option-summary b/tests/expected/lsfd/option-summary
new file mode 100644 (file)
index 0000000..4e96f55
--- /dev/null
@@ -0,0 +1,3 @@
+   10 GROUP
+    3 PASSWD
+   13 PROC
index 47b2499be467ccdbdc7fcc892da697ff89ffe9c6..597e48cf43790a0a1398cc95f128fb5d0a80a14a 100644 (file)
 # GNU General Public License for more details.
 #
 
+function lsfd_wait_for_pausing {
+       ts_check_prog "sleep"
+
+       local PID=$1
+       until [[ $(ps --no-headers -ostat "${PID}") =~ S.* ]]; do
+               sleep 1
+       done
+}
+
 function lsfd_compare_dev {
     local LSFD=$1
     local FINDMNT=$2
diff --git a/tests/ts/lsfd/option-summary b/tests/ts/lsfd/option-summary
new file mode 100755 (executable)
index 0000000..466e3d8
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com>
+#
+# This file is part of util-linux.
+#
+# This file 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 file 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.
+#
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="directory"
+
+. $TS_TOPDIR/functions.sh
+ts_init "$*"
+
+. $TS_SELF/lsfd-functions.bash
+
+ts_check_test_command "$TS_CMD_LSFD"
+ts_check_test_command "$TS_HELPER_MKFDS"
+ts_check_prog "ps"
+
+ts_cd "$TS_OUTDIR"
+
+FD=3
+F_GROUP=/etc/group
+F_PASSWD=/etc/passwd
+PIDS=
+PID=
+
+for i in {1..10}; do
+    "$TS_HELPER_MKFDS" -q ro-regular-file $FD file=$F_GROUP &
+    PID=$!
+    PIDS="${PIDS} ${PID} "
+    lsfd_wait_for_pausing "${PID}"
+done
+
+for i in {1..3}; do
+    "$TS_HELPER_MKFDS" -q ro-regular-file $FD file=$F_PASSWD &
+    PID=$!
+    PIDS="${PIDS} ${PID} "
+    lsfd_wait_for_pausing "${PID}"
+done
+
+${TS_CMD_LSFD} -n --summary=only \
+                --pid="${PIDS}" \
+                --counter=GROUP:'(NAME == "/etc/group")' \
+                --counter=PASSWD:'(NAME == "/etc/passwd")' \
+                --counter=PROC:'(ASSOC == "exe")' \
+                > $TS_OUTPUT 2>&1
+
+for PID in ${PIDS}; do
+    kill -CONT "${PID}"
+done
+
+ts_finalize