* 'lsfd--misc-tun' of https://github.com/masatake/util-linux:
  tests: (lsfd) add a case testing TUN.IFACE column
  tests: (mkfds) add a factor for opening tun device
  lsfd: add TUN.IFFACE, a column for interfaces behind tun devices
  lsfd: (refactor) move miscdev specific code to cdev_misc_ops
  lsfd: (refactor) make the way to handle character devices extensible
  lsfd: (refactor) introduce a content data type for char devices
This commit is contained in:
Karel Zak 2023-07-03 12:38:48 +02:00
commit 3e2e4c32e8
7 changed files with 403 additions and 40 deletions

View file

@ -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,
};

View file

@ -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.

View file

@ -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)") },

View file

@ -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,

View file

@ -0,0 +1,4 @@
3 rw- CHR misc:tun
ASSOC,MODE,TYPE,SOURCE: 0
NAME: 0
TUN.IFACE: 0

View file

@ -25,6 +25,7 @@
#include <getopt.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_tun.h>
#include <linux/netlink.h>
#include <linux/sockios.h> /* SIOCGSKNS */
#include <mqueue.h>
@ -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)

65
tests/ts/lsfd/mkfds-cdev-tun Executable file
View file

@ -0,0 +1,65 @@
#!/bin/bash
#
# Copyright (C) 2023 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="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