From d1cdc16f448f0411467d852384bb46aadf4197b0 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Mon, 2 Dec 2024 13:22:10 +0100 Subject: [PATCH] lsfd: add --hyperlink command line option Co-Author: Masatake YAMTO Signed-off-by: Karel Zak --- lsfd-cmd/bdev.c | 3 +- lsfd-cmd/cdev.c | 3 +- lsfd-cmd/fifo.c | 3 +- lsfd-cmd/file.c | 27 +++++--- lsfd-cmd/lsfd.1.adoc | 3 + lsfd-cmd/lsfd.c | 38 +++++++++--- lsfd-cmd/lsfd.h | 4 +- lsfd-cmd/sock.c | 3 +- lsfd-cmd/unkn.c | 3 +- .../lsfd/option-hyperlink-regular-file | 10 +++ tests/ts/lsfd/option-hyperlink | 61 +++++++++++++++++++ 11 files changed, 135 insertions(+), 23 deletions(-) create mode 100644 tests/expected/lsfd/option-hyperlink-regular-file create mode 100755 tests/ts/lsfd/option-hyperlink diff --git a/lsfd-cmd/bdev.c b/lsfd-cmd/bdev.c index b61fdbbf4c..0da71301ac 100644 --- a/lsfd-cmd/bdev.c +++ b/lsfd-cmd/bdev.c @@ -33,7 +33,8 @@ static bool bdev_fill_column(struct proc *proc __attribute__((__unused__)), struct file *file __attribute__((__unused__)), struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { char *str = NULL; const char *partition, *devdrv; diff --git a/lsfd-cmd/cdev.c b/lsfd-cmd/cdev.c index 3319479ae7..9b0663e761 100644 --- a/lsfd-cmd/cdev.c +++ b/lsfd-cmd/cdev.c @@ -67,7 +67,8 @@ static bool cdev_fill_column(struct proc *proc __attribute__((__unused__)), struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { struct cdev *cdev = (struct cdev *)file; const struct cdev_ops *ops = cdev->cdev_ops; diff --git a/lsfd-cmd/fifo.c b/lsfd-cmd/fifo.c index b78e6d4875..45821630c0 100644 --- a/lsfd-cmd/fifo.c +++ b/lsfd-cmd/fifo.c @@ -45,7 +45,8 @@ static bool fifo_fill_column(struct proc *proc __attribute__((__unused__)), struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { char *str = NULL; diff --git a/lsfd-cmd/file.c b/lsfd-cmd/file.c index 91c8d87a46..7f8ec8780b 100644 --- a/lsfd-cmd/file.c +++ b/lsfd-cmd/file.c @@ -116,7 +116,8 @@ static bool abst_fill_column(struct proc *proc, struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { char *str = NULL; @@ -260,7 +261,8 @@ static bool error_fill_column(struct proc *proc __attribute__((__unused__)), struct file *file __attribute__((__unused__)), struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { char *str = NULL; const char *ename; @@ -301,7 +303,8 @@ static bool readlink_error_fill_column(struct proc *proc __attribute__((__unused struct file *file __attribute__((__unused__)), struct libscols_line *ln __attribute__((__unused__)), int column_id, - size_t column_index __attribute__((__unused__))) + size_t column_index __attribute__((__unused__)), + const char *uri __attribute__((__unused__))) { switch(column_id) { case COL_NAME: @@ -402,7 +405,8 @@ static bool file_fill_column(struct proc *proc __attribute__((__unused__)), struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { char *str = NULL; mode_t ftype; @@ -428,6 +432,12 @@ static bool file_fill_column(struct proc *proc __attribute__((__unused__)), if (file->name && scols_line_set_data(ln, column_index, file->name)) err(EXIT_FAILURE, _("failed to add output data")); + + if (uri && (!file->name || *file->name != '/')) { + struct libscols_cell *ce = scols_line_get_cell(ln, column_index); + if (ce) + scols_cell_disable_uri(ce, 1); + } return true; case COL_STTYPE: case COL_TYPE: @@ -797,7 +807,8 @@ static bool nsfs_file_fill_column(struct proc *proc __attribute__((__unused__)), struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { struct nsfs_file *nsfs_file = (struct nsfs_file *)file; char *name = NULL; @@ -874,7 +885,8 @@ static bool mqueue_file_fill_column(struct proc *proc __attribute__((__unused__) struct file *file __attribute__((__unused__)), struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { switch (column_id) { case COL_TYPE: @@ -986,7 +998,8 @@ static bool pidfs_file_fill_column(struct proc *proc __attribute__((__unused__)) struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { struct pidfs_file *pidfs_file = (struct pidfs_file *)file; char *buf = NULL; diff --git a/lsfd-cmd/lsfd.1.adoc b/lsfd-cmd/lsfd.1.adoc index 71078f493f..28f6abd5eb 100644 --- a/lsfd-cmd/lsfd.1.adoc +++ b/lsfd-cmd/lsfd.1.adoc @@ -110,6 +110,9 @@ only for *lsfd* developers. *--dump-counters*:: Dump the definition of counters used in *--summary* output. +*--hyperlink*[=_mode_]:: +Print paths as terminal hyperlinks. The _mode_ can be set to "always", "never", or "auto". The optional argument _when_ can be set to "auto", "never", or "always". If the _when_ argument is omitted, it will default to "auto". The "auto" setting means that hyperlinks will only be used if the output is on a terminal. + *-H*, *--list-columns*:: List available columns that you can specify at *--output* option. diff --git a/lsfd-cmd/lsfd.c b/lsfd-cmd/lsfd.c index b6486379f9..242dd4ef72 100644 --- a/lsfd-cmd/lsfd.c +++ b/lsfd-cmd/lsfd.c @@ -543,6 +543,7 @@ static const struct counter_spec default_counter_specs[] = { struct filler_data { struct proc *proc; struct file *file; + const char *uri; }; struct lsfd_control { @@ -559,6 +560,8 @@ struct lsfd_control { sockets_only : 1, /* display only SOCKETS */ show_xmode : 1; /* XMODE column is enabled. */ + char *uri; + struct libscols_filter *filter; /* filter */ struct libscols_filter **ct_filters; /* counters (NULL terminated array) */ }; @@ -608,7 +611,7 @@ static const struct colinfo *get_column_info(int id) } static struct libscols_column *add_column(struct libscols_table *tb, - int id, int extra) + int id, int extra, char *uri) { const struct colinfo *col; struct libscols_column *cl; @@ -628,6 +631,9 @@ static struct libscols_column *add_column(struct libscols_table *tb, NULL); scols_column_set_safechars(cl, "\n"); } + if (!(extra & SCOLS_FL_HIDDEN) && uri && + (id == COL_NAME || id == COL_KNAME)) + scols_column_set_uri(cl, uri); } return cl; @@ -641,7 +647,7 @@ static struct libscols_column *add_hidden_column(struct lsfd_control *ctl, if (ncolumns >= ARRAY_SIZE(columns)) errx(EXIT_FAILURE, _("too many columns are added via filter expression")); - cl = add_column(ctl->tb, colid, SCOLS_FL_HIDDEN); + cl = add_column(ctl->tb, colid, SCOLS_FL_HIDDEN, ctl->uri); if (!cl) err(EXIT_FAILURE, _("failed to allocate output column")); columns[ncolumns++] = colid; @@ -1448,14 +1454,15 @@ static void fill_column(struct proc *proc, struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { const struct file_class *class = file->class; while (class) { if (class->fill_column && class->fill_column(proc, file, ln, - column_id, column_index)) + column_id, column_index, uri)) break; class = class->super; } @@ -1469,13 +1476,15 @@ static int filter_filler_cb( { struct filler_data *fid = (struct filler_data *) userdata; - fill_column(fid->proc, fid->file, ln, get_column_id(colnum), colnum); + fill_column(fid->proc, fid->file, ln, get_column_id(colnum), colnum, + fid->uri); return 0; } static void convert_file(struct proc *proc, struct file *file, - struct libscols_line *ln) + struct libscols_line *ln, + const char *uri __attribute__((__unused__))) { size_t i; @@ -1483,7 +1492,7 @@ static void convert_file(struct proc *proc, for (i = 0; i < ncolumns; i++) { if (scols_line_is_filled(ln, i)) continue; - fill_column(proc, file, ln, get_column_id(i), i); + fill_column(proc, file, ln, get_column_id(i), i, uri); } } @@ -1506,7 +1515,8 @@ static void convert(struct list_head *procs, struct lsfd_control *ctl) int status = 0; struct filler_data fid = { .proc = proc, - .file = file + .file = file, + .uri = ctl->uri, }; scols_filter_set_filler_cb(ctl->filter, @@ -1519,7 +1529,7 @@ static void convert(struct list_head *procs, struct lsfd_control *ctl) } } - convert_file(proc, file, ln); + convert_file(proc, file, ln, ctl->uri); if (!ctl->ct_filters) continue; @@ -2160,6 +2170,7 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" --debug-filter dump the internal data structure of filter and exit\n"), out); fputs(_(" -C, --counter : define custom counter for --summary output\n"), out); fputs(_(" --dump-counters dump counter definitions\n"), out); + fputs(_(" --hyperlink[=mode] print paths as terminal hyperlinks (always, never, or auto)\n"), out); fputs(_(" --summary[=] print summary information (only, append, or never)\n"), out); fputs(_(" --_drop-privilege (testing purpose) do setuid(1) just after starting\n"), out); @@ -2505,6 +2516,7 @@ int main(int argc, char *argv[]) OPT_SUMMARY, OPT_DUMP_COUNTERS, OPT_DROP_PRIVILEGE, + OPT_HYPERLINK }; static const struct option longopts[] = { { "noheadings", no_argument, NULL, 'n' }, @@ -2524,6 +2536,7 @@ int main(int argc, char *argv[]) { "dump-counters",no_argument, NULL, OPT_DUMP_COUNTERS }, { "list-columns",no_argument, NULL, 'H' }, { "_drop-privilege",no_argument,NULL,OPT_DROP_PRIVILEGE }, + { "hyperlink", optional_argument, NULL, OPT_HYPERLINK }, { NULL, 0, NULL, 0 }, }; @@ -2606,6 +2619,11 @@ int main(int argc, char *argv[]) if (setuid(1) == -1) err(EXIT_FAILURE, _("failed to drop privilege")); break; + case OPT_HYPERLINK: + if (hyperlinkwanted_or_err(optarg, + _("invalid hyperlink argument"))) + ctl.uri = xgethosturi(NULL); + break; case 'V': print_version(EXIT_SUCCESS); case 'h': @@ -2655,7 +2673,7 @@ int main(int argc, char *argv[]) /* create output columns */ for (i = 0; i < ncolumns; i++) { - struct libscols_column *cl = add_column(ctl.tb, get_column_id(i), 0); + struct libscols_column *cl = add_column(ctl.tb, get_column_id(i), 0, ctl.uri); if (!cl) err(EXIT_FAILURE, _("failed to allocate output column")); diff --git a/lsfd-cmd/lsfd.h b/lsfd-cmd/lsfd.h index e25aec4d6c..28eb69c148 100644 --- a/lsfd-cmd/lsfd.h +++ b/lsfd-cmd/lsfd.h @@ -226,7 +226,8 @@ struct file_class { struct file *file, struct libscols_line *ln, int column_id, - size_t column_index); + size_t column_index, + const char *uri); int (*handle_fdinfo)(struct file *file, const char *key, const char* value); void (*attach_xinfo)(struct file *file); void (*initialize_content)(struct file *file); @@ -281,6 +282,7 @@ enum decode_source_level { void decode_source(char *buf, size_t bufsize, unsigned int dev_major, unsigned int dev_minor, enum decode_source_level level); + /* * Name managing */ diff --git a/lsfd-cmd/sock.c b/lsfd-cmd/sock.c index d296ceb4c2..b6eb5fdfee 100644 --- a/lsfd-cmd/sock.c +++ b/lsfd-cmd/sock.c @@ -51,7 +51,8 @@ static bool sock_fill_column(struct proc *proc __attribute__((__unused__)), struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { char *str = NULL; struct sock *sock = (struct sock *)file; diff --git a/lsfd-cmd/unkn.c b/lsfd-cmd/unkn.c index b1883bf34c..f604e64532 100644 --- a/lsfd-cmd/unkn.c +++ b/lsfd-cmd/unkn.c @@ -82,7 +82,8 @@ static bool unkn_fill_column(struct proc *proc, struct file *file, struct libscols_line *ln, int column_id, - size_t column_index) + size_t column_index, + const char *uri __attribute__((__unused__))) { char *str = NULL; struct unkn *unkn = (struct unkn *)file; diff --git a/tests/expected/lsfd/option-hyperlink-regular-file b/tests/expected/lsfd/option-hyperlink-regular-file new file mode 100644 index 0000000000..bc97081d1f --- /dev/null +++ b/tests/expected/lsfd/option-hyperlink-regular-file @@ -0,0 +1,10 @@ +# NAME +00000000 1b 5d 38 3b 3b 66 69 6c 65 3a 2f 2f 2f 65 74 63 |.]8;;file:///etc| +00000010 2f 70 61 73 73 77 64 1b 5c 2f 65 74 63 2f 70 61 |/passwd.\/etc/pa| +00000020 73 73 77 64 1b 5d 38 3b 3b 1b 5c 0a |sswd.]8;;.\.| +0000002c +# KNAME +00000000 1b 5d 38 3b 3b 66 69 6c 65 3a 2f 2f 2f 65 74 63 |.]8;;file:///etc| +00000010 2f 70 61 73 73 77 64 1b 5c 2f 65 74 63 2f 70 61 |/passwd.\/etc/pa| +00000020 73 73 77 64 1b 5d 38 3b 3b 1b 5c 0a |sswd.]8;;.\.| +0000002c diff --git a/tests/ts/lsfd/option-hyperlink b/tests/ts/lsfd/option-hyperlink new file mode 100755 index 0000000000..a7fe12b87a --- /dev/null +++ b/tests/ts/lsfd/option-hyperlink @@ -0,0 +1,61 @@ +#!/bin/bash +# +# Copyright (C) 2024 Masatake YAMATO +# +# 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="--hyperlink option" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command "$TS_CMD_LSFD" +ts_check_test_command "$TS_HELPER_MKFDS" +ts_check_test_command "$TS_HELPER_SYSINFO" +ts_check_test_command "$TS_CMD_HEXDUMP" + +ts_check_prog "sed" + +HOSTNAME= +if ! HOSTNAME=$("$TS_HELPER_SYSINFO" hostname); then + ts_skip "failed in getting hostname" +fi +HOSTNAME_PATTERN=${HOSTNAME//./\\.} +replace_hostname() +{ + # Make the output host independent: replacing file://foo.example.com/ + # with file:/// + sed -e 's@file://'"${HOSTNAME_PATTERN}"'/@file:///@' +} + +ts_cd "$TS_OUTDIR" +PID= +FD=3 +EXPR='(FD == '"$FD"')' + +ts_init_subtest "regular-file" +{ + coproc MKFDS { "$TS_HELPER_MKFDS" ro-regular-file $FD; } + if read -r -u "${MKFDS[0]}" PID; then + for o in NAME KNAME; do + echo "# $o" + "${TS_CMD_LSFD}" -n --hyperlink=always -o "$o" -p "${PID}" -Q "$EXPR" | replace_hostname | "$TS_CMD_HEXDUMP" -C + done + echo DONE >&"${MKFDS[1]}" + fi +} > "$TS_OUTPUT" 2>&1 +wait "${MKFDS_PID}" +ts_finalize_subtest + +ts_finalize -- 2.47.2