From: Christian Goeschel Ndjomouo Date: Sun, 21 Dec 2025 00:59:05 +0000 (-0500) Subject: getino: new tool to print the unique pidfd or namespace inode number X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=595d05feb4b2dc7a68f2d3921f75fbc5674d4f5d;p=thirdparty%2Futil-linux.git getino: new tool to print the unique pidfd or namespace inode number Closes: #3837 Signed-off-by: Christian Goeschel Ndjomouo --- diff --git a/.gitignore b/.gitignore index 14d320cf3..7f56abe5c 100644 --- a/.gitignore +++ b/.gitignore @@ -120,6 +120,7 @@ ylwrap /fsfreeze /fstrim /getopt +/getino /hardlink /hexdump /hwclock diff --git a/AUTHORS b/AUTHORS index df9a88567..d2dc15e20 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,7 @@ AUTHORS (merged projects & commands): Theodore Ts'o fstrim: Lukas Czerner fsfreeze: Hajime Taira + getino: Christian Goeschel Ndjomouo getopt: Frodo Looijaard hardlink: Jakub Jelinek hwclock: Bryan Henderson diff --git a/configure.ac b/configure.ac index 2ff5c0d32..9fafeed04 100644 --- a/configure.ac +++ b/configure.ac @@ -562,7 +562,7 @@ AC_CHECK_MEMBERS([struct termios.c_line],,, AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec],,, [[#include ]]) -AC_CHECK_TYPES([struct statx], [], [], [[#include ]]) +AC_CHECK_TYPES([struct statx], [have_struct_statx=yes], [have_struct_statx=no], [[#include ]]) AC_CHECK_MEMBERS([struct statx.stx_mnt_id],,, [[#include ]]) @@ -669,7 +669,6 @@ AC_CHECK_FUNCS([ \ setresuid \ sigqueue \ srandom \ - statx \ strnchr \ strndup \ strnlen \ @@ -696,6 +695,7 @@ AC_CHECK_FUNCS([open_memstream], [have_open_memstream=yes],[have_open_memstream= AC_CHECK_FUNCS([posix_fallocate], [have_posix_fallocate=yes], [have_posix_fallocate=no]) AC_CHECK_FUNCS([reboot], [have_reboot=yes],[have_reboot=no]) AC_CHECK_FUNCS([updwtmpx updwtmpx], [have_gnu_utmpx=yes], [have_gnu_utmpx=no]) +AC_CHECK_FUNCS([statx], [have_statx=yes], [have_statx=no]) AM_CONDITIONAL([HAVE_OPENAT], [test "x$have_openat" = xyes]) AM_CONDITIONAL([HAVE_LINUX_LANDLOCK_H], [test "x$ac_cv_header_linux_landlock_h" = xyes]) @@ -2323,6 +2323,16 @@ UL_BUILD_INIT([kill]) UL_REQUIRES_LINUX([kill]) AM_CONDITIONAL([BUILD_KILL], [test "x$build_kill" = xyes]) +AC_ARG_ENABLE([getino], + AS_HELP_STRING([--disable-getino], [do not build getino]), + [], [UL_DEFAULT_ENABLE([getino], [check])] +) +UL_BUILD_INIT([getino]) +UL_REQUIRES_LINUX([getino]) +UL_REQUIRES_SYSCALL_CHECK([getino], [UL_CHECK_SYSCALL([pidfd_open])], [pidfd_open]) +UL_REQUIRES_HAVE([getino], [statx], [statx function]) +UL_REQUIRES_HAVE([getino], [struct_statx], [statx struct]) +AM_CONDITIONAL([BUILD_GETINO], [test "x$build_getino" = xyes]) AC_ARG_ENABLE([last], AS_HELP_STRING([--disable-last], [do not build last]), diff --git a/meson.build b/meson.build index c09342b1f..0a3534ca7 100644 --- a/meson.build +++ b/meson.build @@ -3370,6 +3370,23 @@ if opt and not is_disabler(exe) bashcompletions += ['exch'] endif +opt = get_option('build-getino').require(LINUX and conf.get('HAVE_PIDFD_OPEN').to_string() == '1' \ + and conf.get('HAVE_STATX').to_string() == '1' \ + and conf.get('HAVE_STRUCT_STATX').to_string() == '1').allowed() +exe = executable( + 'getino', + getino_sources, + include_directories : includes, + link_with : [lib_common], + install_dir : usrbin_exec_dir, + install : opt, + build_by_default : opt) +if opt and not is_disabler(exe) + exes += exe + manadocs += getino_manadocs + bashcompletions += ['getino'] +endif + ############################################################ opt = not get_option('build-schedutils').disabled() diff --git a/meson_options.txt b/meson_options.txt index b764b56fb..5f2cd09e8 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -259,6 +259,8 @@ option('build-hexdump', type : 'feature', description : 'build hexdump') option('build-findfs', type : 'feature', description : 'build findfs') +option('build-getino', type : 'feature', + description : 'build getino') # static programs diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index d0bc8d806..7bc0aaae0 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -337,3 +337,12 @@ lsclocks_SOURCES = misc-utils/lsclocks.c lsclocks_LDADD = $(LDADD) libcommon.la libsmartcols.la lsclocks_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) endif + +if BUILD_GETINO +usrbin_exec_PROGRAMS += getino +MANPAGES += misc-utils/getino.1 +dist_noinst_DATA += misc-utils/getino.1.adoc +getino_SOURCES = misc-utils/getino.c +getino_LDADD = $(LDADD) libcommon.la +getino_CFLAGS = $(AM_CFLAGS) +endif diff --git a/misc-utils/getino.1.adoc b/misc-utils/getino.1.adoc new file mode 100644 index 000000000..8ca99825e --- /dev/null +++ b/misc-utils/getino.1.adoc @@ -0,0 +1,93 @@ +//po4a: entry man manual += getino(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: getino + +== NAME + +getino - print the unique inode number associated to a process file descriptor or namespace for a given PID + +== SYNOPSIS + +*getino* [*--pidfs*|*--cgroupns*|*--ipcns*|*--netns*|*--mntns*|*--pidns*|*--timens*|*--userns*|*--utsns*] [*--print-pid*|*-p*] _PID_[:inode]... + +*getino* [*--print-pid*|*-p*] _PID_[:inode]... + +*getino* _PID_[:inode]... + + +== DESCRIPTION + +*getino* is a simple command that prints the inode numbers associated with the process file descriptor (pidfd) or namespace for all PIDs passed to it as arguments. + +The kernel guarantees that the inode number associated with a process's file descriptor is exempt from reuse for the current boot cycle; therefore, a process can be uniquely identified by its PID and the inode number, conveniently so in the format '_PID:inode_'. +As an example, this enables race-free process signalling with the *kill(1)* command, which accepts addressing processes with the aforementioned format. + +Inode numbers associated with a namespace for a given process are essentially namespace IDs, identical to the inode number reported by /proc/pid/ns/_nstype_, see *namespaces(7)* for more details. + +== OPTIONS + +*-p*, *--print-pid*:: +Print both the PID and pidfd/namespace inode separated by a colon _:_, in respective order. +This format convention can be used to address processes in a race-free manner, e.g. for signalling with the *kill(1)* command. + +*--pidfs*:: +Print the unique inode number for a process's pidfs file descriptor. + +*--cgroupns*:: +Print the unique cgroup namespace inode number. See cgroup_namespaces(7) + +*--ipcns*:: +Print the unique ipc namespace inode number. See ipc_namespaces(7) + +*--netns*:: +Print the unique network namespace inode number. See network_namespaces(7) + +*--mntns*:: +Print the unique mount namespace inode number. See mount_namespaces(7) + +*--pidns*:: +Print the unique pid namespace inode number. See pid_namespaces(7) + +*--timens*:: +Print the unique time namespace inode number. See time_namespaces(7) + +*--userns*:: +Print the unique user namespace inode number. See user_namespaces(7) + +*--utsns*:: +Print the unique uts namespace inode number. See uts_namespaces(7) + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*getino* has the following exit status values: + +*0*:: +success +*1*:: +unspecified failure + +== NOTES + +*getino* requires support for the pidfs pseudo-filesystem, introduced in the Linux kernel version 6.9, to retrieve a valid inode for process file descriptor. + +== AUTHORS + +mailto:cgoesc2@wgu.edu[Christian Goeschel Ndjomouo] + +== SEE ALSO + +*kill*(1) *pidfd_open(2)* *namespaces(7)* + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/getino.c b/misc-utils/getino.c new file mode 100644 index 000000000..161f2fe2e --- /dev/null +++ b/misc-utils/getino.c @@ -0,0 +1,248 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (c) 2025 Christian Goeschel Ndjomouo + * Written by Christian Goeschel Ndjomouo + * + * getino - get the unique pidfd or namespace inode number for a given PID + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "pidfd-utils.h" +#include "strutils.h" +#include "optutils.h" +#include "pidutils.h" + +/* operation flags that determine for what to get the inode, i.e. pidfs, namespace... */ +enum { + GETINO_PIDFS = 1, + GETINO_CGROUP_NAMESPACE, + GETINO_IPC_NAMESPACE, + GETINO_NET_NAMESPACE, + GETINO_MNT_NAMESPACE, + GETINO_PID_NAMESPACE, + GETINO_TIME_NAMESPACE, + GETINO_USER_NAMESPACE, + GETINO_UTS_NAMESPACE, +}; + +#define IS_NAMESPACE_OP(op) ((op > GETINO_PIDFS && op <= GETINO_UTS_NAMESPACE) ? 1 : 0) +struct getino_context { + int op_flag; /* controls for what to get the inode, i.e. GETINO_* */ + pid_t pid; /* PID provided on the command line */ + uint64_t pidfd_ino; /* pidfd inode provided on the command line (PID:inode) */ + unsigned int pidfd_ioctl; /* pidfs ioctl command for namespace fd */ + bool print_pid; /* print the pid and inode, colon-separated */ +}; + +struct ns_desc { + const char * const name; + unsigned int ioctl; +}; + +static struct ns_desc ns_info[] = { + [GETINO_CGROUP_NAMESPACE] = { .name = "cgroup", .ioctl = PIDFD_GET_CGROUP_NAMESPACE }, + [GETINO_IPC_NAMESPACE] = { .name = "ipc", .ioctl = PIDFD_GET_IPC_NAMESPACE }, + [GETINO_NET_NAMESPACE] = { .name = "network", .ioctl = PIDFD_GET_NET_NAMESPACE }, + [GETINO_MNT_NAMESPACE] = { .name = "mount", .ioctl = PIDFD_GET_MNT_NAMESPACE }, + [GETINO_PID_NAMESPACE] = { .name = "pid", .ioctl = PIDFD_GET_PID_NAMESPACE }, + [GETINO_TIME_NAMESPACE] = { .name = "time", .ioctl = PIDFD_GET_TIME_NAMESPACE }, + [GETINO_USER_NAMESPACE] = { .name = "user", .ioctl = PIDFD_GET_USER_NAMESPACE }, + [GETINO_UTS_NAMESPACE] = { .name = "uts", .ioctl = PIDFD_GET_UTS_NAMESPACE }, +}; + +static int get_pidfd_ns_ioctl(struct getino_context *ctx) +{ + if(!IS_NAMESPACE_OP(ctx->op_flag)) + return -1; + + return ns_info[ctx->op_flag].ioctl; +} + +static int pidfd_get_nsfd_or_err(struct getino_context *ctx, int pidfd) +{ + int nsfd, pidfd_ioctl; + + pidfd_ioctl = get_pidfd_ns_ioctl(ctx); + if (pidfd_ioctl < 0) + errx(EXIT_FAILURE, _("no appropriate ioctl for the desired namespace")); + + nsfd = ioctl(pidfd, pidfd_ioctl, 0); + if (nsfd < 0) + err(EXIT_FAILURE, _("failed to determine %s namespace for process %d"), + ns_info[ctx->op_flag].name, ctx->pid); + return nsfd; +} + +static void print_inode(struct getino_context *ctx) +{ + int pidfd, target_fd; + uint64_t ino; + + pidfd = ul_get_valid_pidfd_or_err(ctx->pid, ctx->pidfd_ino); + + if (IS_NAMESPACE_OP(ctx->op_flag)) { + target_fd = pidfd_get_nsfd_or_err(ctx, pidfd); + close(pidfd); + } else { + target_fd = pidfd; + } + + ino = pidfd_get_inode(target_fd); + + if (ctx->print_pid) { + printf("%d:%"PRIu64"\n", ctx->pid, ino); + } else { + printf("%"PRIu64"\n", ino); + } + close(target_fd); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + fputs(USAGE_HEADER, stdout); + fprintf(stdout, _(" %s [options] PID[:inode]...\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, stdout); + fputsln(_("Print the unique inode number of a process's file descriptor or namespace."), stdout); + + fputs(USAGE_OPTIONS, stdout); + fputsln(_(" -p, --print-pid enable PID:inode format printing"), stdout); + fputsln(_(" --cgroupns act on the cgroup namespace"), stdout); + fputsln(_(" --ipcns act on the ipc namespace"), stdout); + fputsln(_(" --mntns act on the mount namespace"), stdout); + fputsln(_(" --netns act on the net namespace"), stdout); + fputsln(_(" --pidfs act on the pidfs file descriptor (default)"), stdout); + fputsln(_(" --pidns act on the pid namespace"), stdout); + fputsln(_(" --timens act on the time namespace"), stdout); + fputsln(_(" --userns act on the user namespace"), stdout); + fputsln(_(" --utsns act on the uts namespace"), stdout); + fputs(USAGE_SEPARATOR, stdout); + fprintf(stdout, USAGE_HELP_OPTIONS(21)); /* char offset to align option descriptions */ + fprintf(stdout, USAGE_MAN_TAIL("getino(1)")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int c, rc = 0; + struct getino_context ctx = { + .print_pid = false, + .op_flag = GETINO_PIDFS, + }; + + enum { + OPT_PIDFS = CHAR_MAX + 1, + OPT_CGROUPNS, + OPT_IPCNS, + OPT_NETNS, + OPT_MNTNS, + OPT_PIDNS, + OPT_TIMENS, + OPT_USERNS, + OPT_UTSNS, + }; + + static const struct option longopts[] = { + { "pidfs", no_argument, NULL, OPT_PIDFS }, + { "cgroupns", no_argument, NULL, OPT_CGROUPNS }, + { "ipcns", no_argument, NULL, OPT_IPCNS }, + { "netns", no_argument, NULL, OPT_NETNS }, + { "mntns", no_argument, NULL, OPT_MNTNS }, + { "pidns", no_argument, NULL, OPT_PIDNS }, + { "timens", no_argument, NULL, OPT_TIMENS }, + { "userns", no_argument, NULL, OPT_USERNS }, + { "utsns", no_argument, NULL, OPT_UTSNS }, + { "print-pid", no_argument, NULL, 'p' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { OPT_PIDFS, OPT_CGROUPNS, OPT_IPCNS, + OPT_NETNS, OPT_MNTNS, OPT_PIDNS, + OPT_TIMENS, OPT_USERNS, OPT_UTSNS }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "pVh", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch (c) { + case OPT_PIDFS: + ctx.op_flag = GETINO_PIDFS; + break; + case OPT_CGROUPNS: + ctx.op_flag = GETINO_CGROUP_NAMESPACE; + break; + case OPT_IPCNS: + ctx.op_flag = GETINO_IPC_NAMESPACE; + break; + case OPT_NETNS: + ctx.op_flag = GETINO_NET_NAMESPACE; + break; + case OPT_MNTNS: + ctx.op_flag = GETINO_MNT_NAMESPACE; + break; + case OPT_PIDNS: + ctx.op_flag = GETINO_PID_NAMESPACE; + break; + case OPT_TIMENS: + ctx.op_flag = GETINO_TIME_NAMESPACE; + break; + case OPT_USERNS: + ctx.op_flag = GETINO_USER_NAMESPACE; + break; + case OPT_UTSNS: + ctx.op_flag = GETINO_UTS_NAMESPACE; + break; + case 'p': + ctx.print_pid = true; + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (argc - optind < 1) { + warnx(_("no process specified")); + errtryhelp(EXIT_FAILURE); + } + + argv += optind - 1; + while (*++argv) { + rc = ul_parse_pid_str(*argv, &ctx.pid, &ctx.pidfd_ino); + if (rc) + err(EXIT_FAILURE, _("invalid PID argument '%s'"), *argv); + print_inode(&ctx); + } + + return EXIT_SUCCESS; +} diff --git a/misc-utils/meson.build b/misc-utils/meson.build index 1cd4713ce..7c7766768 100644 --- a/misc-utils/meson.build +++ b/misc-utils/meson.build @@ -229,3 +229,8 @@ lsclocks_sources = files( 'lsclocks.c', ) lsclocks_manadocs = files('lsclocks.1.adoc') + +getino_sources = files( + 'getino.c', +) +getino_manadocs = files('getino.1.adoc') diff --git a/po-man/po4a.cfg b/po-man/po4a.cfg index 0356df8d3..a2e3871eb 100644 --- a/po-man/po4a.cfg +++ b/po-man/po4a.cfg @@ -72,6 +72,7 @@ [type:asciidoc] ../misc-utils/fincore.1.adoc $lang:$lang/fincore.1.adoc [type:asciidoc] ../misc-utils/findfs.8.adoc $lang:$lang/findfs.8.adoc [type:asciidoc] ../misc-utils/findmnt.8.adoc $lang:$lang/findmnt.8.adoc +[type:asciidoc] ../misc-utils/getino.1.adoc $lang:$lang/getino.1.adoc [type:asciidoc] ../misc-utils/getopt.1.adoc $lang:$lang/getopt.1.adoc [type:asciidoc] ../misc-utils/hardlink.1.adoc $lang:$lang/hardlink.1.adoc [type:asciidoc] ../misc-utils/kill.1.adoc $lang:$lang/kill.1.adoc