diff --git a/misc-utils/lsfd-cdev.c b/misc-utils/lsfd-cdev.c index 4e35f1543..5f0f30650 100644 --- a/misc-utils/lsfd-cdev.c +++ b/misc-utils/lsfd-cdev.c @@ -33,70 +33,74 @@ struct miscdev { char *name; }; +struct cdev { + struct file file; + const char *devdrv; + const struct cdev_ops *cdev_ops; + void *cdev_data; +}; + +struct cdev_ops { + const struct cdev_ops *parent; + bool (*probe)(const struct cdev *); + char * (*get_name)(struct cdev *); + bool (*fill_column)(struct proc *, + struct cdev *, + struct libscols_line *, + int, + size_t, + char **); + void (*init)(const struct cdev *); + void (*free)(const struct cdev *); + int (*handle_fdinfo)(struct cdev *, const char *, const char *); +}; + static bool cdev_fill_column(struct proc *proc __attribute__((__unused__)), - struct file *file __attribute__((__unused__)), + struct file *file, struct libscols_line *ln, int column_id, size_t column_index) { + struct cdev *cdev = (struct cdev *)file; + const struct cdev_ops *ops = cdev->cdev_ops; char *str = NULL; - const char *devdrv; - const char *miscdev; switch(column_id) { + case COL_NAME: + if (cdev->cdev_ops->get_name) { + str = cdev->cdev_ops->get_name(cdev); + if (str) + break; + } + return false; case COL_TYPE: if (scols_line_set_data(ln, column_index, "CHR")) err(EXIT_FAILURE, _("failed to add output data")); return true; - case COL_MISCDEV: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - if (devdrv && strcmp(devdrv, "misc") == 0) { - miscdev = get_miscdev(minor(file->stat.st_rdev)); - if (miscdev) - str = xstrdup(miscdev); - else - xasprintf(&str, "%u", - minor(file->stat.st_rdev)); - break; - } - return true; case COL_DEVTYPE: if (scols_line_set_data(ln, column_index, "char")) err(EXIT_FAILURE, _("failed to add output data")); return true; case COL_CHRDRV: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - if (devdrv) - str = xstrdup(devdrv); + if (cdev->devdrv) + str = xstrdup(cdev->devdrv); else xasprintf(&str, "%u", major(file->stat.st_rdev)); break; - case COL_SOURCE: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - miscdev = NULL; - if (devdrv && strcmp(devdrv, "misc") == 0) - miscdev = get_miscdev(minor(file->stat.st_rdev)); - if (devdrv) { - if (miscdev) { - xasprintf(&str, "misc:%s", miscdev); - } else { - xasprintf(&str, "%s:%u", devdrv, - minor(file->stat.st_rdev)); - } - break; - } - /* FALL THROUGH */ - case COL_MAJMIN: - xasprintf(&str, "%u:%u", - major(file->stat.st_rdev), - minor(file->stat.st_rdev)); - break; default: + while (ops) { + if (ops->fill_column + && ops->fill_column(proc, cdev, ln, + column_id, column_index, &str)) + goto out; + ops = ops->parent; + } return false; } + out: if (!str) err(EXIT_FAILURE, _("failed to add output data")); if (scols_line_refer_data(ln, column_index, str)) @@ -168,11 +172,220 @@ const char *get_miscdev(unsigned long minor) return NULL; } +/* + * generic (fallback implementation) + */ +static bool cdev_generic_probe(const struct cdev *cdev __attribute__((__unused__))) { + return true; +} + +static bool cdev_generic_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct file *file = &cdev->file; + + switch(column_id) { + case COL_SOURCE: + if (cdev->devdrv) { + xasprintf(str, "%s:%u", cdev->devdrv, + minor(file->stat.st_rdev)); + return true; + } + /* FALL THROUGH */ + case COL_MAJMIN: + xasprintf(str, "%u:%u", + major(file->stat.st_rdev), + minor(file->stat.st_rdev)); + return true; + default: + return false; + } +} + +static struct cdev_ops cdev_generic_ops = { + .probe = cdev_generic_probe, + .fill_column = cdev_generic_fill_column, +}; + +/* + * misc device driver + */ +static bool cdev_misc_probe(const struct cdev *cdev) { + return cdev->devdrv && strcmp(cdev->devdrv, "misc") == 0; +} + +static bool cdev_misc_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct file *file = &cdev->file; + const char *miscdev; + + switch(column_id) { + case COL_MISCDEV: + miscdev = get_miscdev(minor(file->stat.st_rdev)); + if (miscdev) + *str = xstrdup(miscdev); + else + xasprintf(str, "%u", + minor(file->stat.st_rdev)); + return true; + case COL_SOURCE: + miscdev = get_miscdev(minor(file->stat.st_rdev)); + if (miscdev) + xasprintf(str, "misc:%s", miscdev); + else + xasprintf(str, "misc:%u", + minor(file->stat.st_rdev)); + return true; + } + return false; +} + +static struct cdev_ops cdev_misc_ops = { + .parent = &cdev_generic_ops, + .probe = cdev_misc_probe, + .fill_column = cdev_misc_fill_column, +}; + +/* + * tun devcie driver + */ +static bool cdev_tun_probe(const struct cdev *cdev) +{ + const char *miscdev; + + if ((!cdev->devdrv) || strcmp(cdev->devdrv, "misc")) + return false; + + miscdev = get_miscdev(minor(cdev->file.stat.st_rdev)); + if (miscdev && strcmp(miscdev, "tun") == 0) + return true; + return false; +} + +static void cdev_tun_free(const struct cdev *cdev) +{ + if (cdev->cdev_data) + free(cdev->cdev_data); +} + +static char * cdev_tun_get_name(struct cdev *cdev) +{ + char *str = NULL; + + if (cdev->cdev_data == NULL) + return NULL; + + xasprintf(&str, "iface=%s", (const char *)cdev->cdev_data); + return str; +} + +static bool cdev_tun_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + switch(column_id) { + case COL_MISCDEV: + *str = xstrdup("tun"); + return true; + case COL_SOURCE: + *str = xstrdup("misc:tun"); + return true; + case COL_TUN_IFACE: + if (cdev->cdev_data) { + *str = xstrdup(cdev->cdev_data); + return true; + } + } + return false; +} + +static int cdev_tun_handle_fdinfo(struct cdev *cdev, const char *key, const char *val) +{ + if (strcmp(key, "iff") == 0 && cdev->cdev_data == NULL) { + cdev->cdev_data = xstrdup(val); + return 1; + } + return false; +} + +static struct cdev_ops cdev_tun_ops = { + .parent = &cdev_misc_ops, + .probe = cdev_tun_probe, + .free = cdev_tun_free, + .get_name = cdev_tun_get_name, + .fill_column = cdev_tun_fill_column, + .handle_fdinfo = cdev_tun_handle_fdinfo, +}; + +static const struct cdev_ops *cdev_ops[] = { + &cdev_tun_ops, + &cdev_misc_ops, + &cdev_generic_ops /* This must be at the end. */ +}; + +static const struct cdev_ops *cdev_probe(const struct cdev *cdev) +{ + const struct cdev_ops *r = NULL; + + for (size_t i = 0; i < ARRAY_SIZE(cdev_ops); i++) { + if (cdev_ops[i]->probe(cdev)) { + r = cdev_ops[i]; + break; + } + } + + assert(r); + return r; +} + +static void init_cdev_content(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + cdev->devdrv = get_chrdrv(major(file->stat.st_rdev)); + + cdev->cdev_data = NULL; + cdev->cdev_ops = cdev_probe(cdev); + if (cdev->cdev_ops->init) + cdev->cdev_ops->init(cdev); +} + +static void free_cdev_content(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->free) + cdev->cdev_ops->free(cdev); +} + +static int cdev_handle_fdinfo(struct file *file, const char *key, const char *value) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->handle_fdinfo) + return cdev->cdev_ops->handle_fdinfo(cdev, key, value); + return 0; /* Should be handled in parents */ +} + const struct file_class cdev_class = { .super = &file_class, - .size = sizeof(struct file), + .size = sizeof(struct cdev), .initialize_class = cdev_class_initialize, .finalize_class = cdev_class_finalize, .fill_column = cdev_fill_column, - .free_content = NULL, + .initialize_content = init_cdev_content, + .free_content = free_cdev_content, + .handle_fdinfo = cdev_handle_fdinfo, }; diff --git a/misc-utils/lsfd.1.adoc b/misc-utils/lsfd.1.adoc index ee53cec49..8a78d8e23 100644 --- a/misc-utils/lsfd.1.adoc +++ b/misc-utils/lsfd.1.adoc @@ -247,6 +247,9 @@ id=_EVENTFD.ID_ inotify::: inodes=_INOTIFY.INODES_ + +misc:tun::: +iface=_TUN.IFACE_ ++ NETLINK::: protocol=_NETLINK.PROTOCOL_[ lport=_NETLINK.LPORT_[ group=_NETLINK.GROUPS_]] + @@ -426,6 +429,9 @@ Interval. TIMERFD.REMAINING <``number``>:: Remaining time. +TUN.IFACE <``string``>:: +Network intrface behind the tun device. + TYPE <``string``>:: Cooked version of STTYPE. It is same as STTYPE with exceptions. For SOCK, print the value for SOCK.PROTONAME. diff --git a/misc-utils/lsfd.c b/misc-utils/lsfd.c index 7672a026a..520fe182c 100644 --- a/misc-utils/lsfd.c +++ b/misc-utils/lsfd.c @@ -312,6 +312,9 @@ static const struct colinfo infos[] = { [COL_TIMERFD_REMAINING]= { "TIMERFD.REMAINING", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("remaining time") }, + [COL_TUN_IFACE] = { "TUN.IFACE", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, + N_("network interface behind the tun device") }, [COL_TYPE] = { "TYPE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("file type (cooked)") }, diff --git a/misc-utils/lsfd.h b/misc-utils/lsfd.h index 1b86b625b..f2c4c0e97 100644 --- a/misc-utils/lsfd.h +++ b/misc-utils/lsfd.h @@ -101,6 +101,7 @@ enum { COL_TIMERFD_CLOCKID, COL_TIMERFD_INTERVAL, COL_TIMERFD_REMAINING, + COL_TUN_IFACE, COL_TYPE, COL_UDP_LADDR, COL_UDP_RADDR, diff --git a/tests/expected/lsfd/mkfds-cdev-tun b/tests/expected/lsfd/mkfds-cdev-tun new file mode 100644 index 000000000..6bef3aec1 --- /dev/null +++ b/tests/expected/lsfd/mkfds-cdev-tun @@ -0,0 +1,4 @@ + 3 rw- CHR misc:tun +ASSOC,MODE,TYPE,SOURCE: 0 +NAME: 0 +TUN.IFACE: 0 diff --git a/tests/helpers/test_mkfds.c b/tests/helpers/test_mkfds.c index ff6e93087..ab7502594 100644 --- a/tests/helpers/test_mkfds.c +++ b/tests/helpers/test_mkfds.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include /* SIOCGSKNS */ #include @@ -2466,6 +2467,62 @@ static void *make_signalfd(const struct factory *factory _U_, struct fdesc fdesc return NULL; } + +/* ref. linux/Documentation/networking/tuntap.rst */ +static void *make_cdev_tun(const struct factory *factory _U_, struct fdesc fdescs[], + int argc _U_, char ** argv _U_) +{ + int tfd = open("/dev/net/tun", O_RDWR); + struct ifreq ifr; + + if (tfd < 0) + err(EXIT_FAILURE, "failed in opening /dev/net/tun"); + + memset(&ifr, 0, sizeof(ifr)); + + ifr.ifr_flags = IFF_TUN; + strcpy(ifr.ifr_name, "mkfds%d"); + + if (ioctl(tfd, TUNSETIFF, (void *) &ifr) < 0) { + int e = errno; + close(tfd); + errno = e; + err(EXIT_FAILURE, "failed in setting \"lo\" to the tun device"); + } + + if (tfd != fdescs[0].fd) { + if (dup2(tfd, fdescs[0].fd) < 0) { + int e = errno; + close(tfd); + errno = e; + err(EXIT_FAILURE, "failed to dup %d -> %d", tfd, fdescs[0].fd); + } + close(tfd); + } + + fdescs[0] = (struct fdesc){ + .fd = fdescs[0].fd, + .close = close_fdesc, + .data = NULL + }; + + return xstrdup(ifr.ifr_name); +} + +static void report_cdev_tun(const struct factory *factory _U_, + int nth, void *data, FILE *fp) +{ + if (nth == 0) { + char *devname = data; + fprintf(fp, "%s", devname); + } +} + +static void free_cdev_tun(const struct factory * factory _U_, void *data) +{ + free(data); +} + #define PARAM_END { .name = NULL, } static const struct factory factories[] = { { @@ -3139,6 +3196,20 @@ static const struct factory factories[] = { PARAM_END } }, + { + .name = "cdev-tun", + .desc = "open /dev/net/tun", + .priv = true, + .N = 1, + .EX_N = 0, + .EX_R = 1, + .make = make_cdev_tun, + .report = report_cdev_tun, + .free = free_cdev_tun, + .params = (struct parameter []) { + PARAM_END + } + }, }; static int count_parameters(const struct factory *factory) diff --git a/tests/ts/lsfd/mkfds-cdev-tun b/tests/ts/lsfd/mkfds-cdev-tun new file mode 100755 index 000000000..25bf03efc --- /dev/null +++ b/tests/ts/lsfd/mkfds-cdev-tun @@ -0,0 +1,65 @@ +#!/bin/bash +# +# Copyright (C) 2023 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="tun device and interface behind the device" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" +ts_skip_nonroot + +[[ -e /dev/net/tun ]] || ts_skip "/dev/net/tun doest not exist" + +ts_check_test_command "$TS_CMD_LSFD" +ts_check_test_command "$TS_HELPER_MKFDS" + +ts_cd "$TS_OUTDIR" + +PID= +FD=3 +IFNAME= + +{ + coproc MKFDS { "$TS_HELPER_MKFDS" cdev-tun $FD ; } + if read -u ${MKFDS[0]} PID IFNAME; then + EXPR='(FD == '"$FD"')' + ${TS_CMD_LSFD} -p "${PID}" -n -o ASSOC,MODE,TYPE,SOURCE -Q "${EXPR}" + echo 'ASSOC,MODE,TYPE,SOURCE': $? + + output=$(${TS_CMD_LSFD} -p "${PID}" -n --raw -o NAME -Q "${EXPR}") + if [[ "$output" == "iface=$IFNAME" ]]; then + echo 'NAME': $? + else + echo 'NAME': $? + echo "expected NAME: iface=$IFNAME" + echo "output NAME: $output" + fi + + output=$(${TS_CMD_LSFD} -p "${PID}" -n --raw -o TUN.IFACE -Q "${EXPR}") + if [[ "$output" == "$IFNAME" ]]; then + echo 'TUN.IFACE': $? + else + echo 'TUN.IFAEC': $? + echo "expected TUN.IFACE: $IFNAME" + echo "output TUN.IFACE: $output" + fi + + kill -CONT ${PID} + fi + wait ${MKFDS_PID} +} > $TS_OUTPUT 2>&1 + +ts_finalize