]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
getino: new tool to print the unique pidfd or namespace inode number
authorChristian Goeschel Ndjomouo <cgoesc2@wgu.edu>
Sun, 21 Dec 2025 00:59:05 +0000 (19:59 -0500)
committerChristian Goeschel Ndjomouo <cgoesc2@wgu.edu>
Thu, 8 Jan 2026 01:08:18 +0000 (20:08 -0500)
Closes: #3837
Signed-off-by: Christian Goeschel Ndjomouo <cgoesc2@wgu.edu>
.gitignore
AUTHORS
configure.ac
meson.build
meson_options.txt
misc-utils/Makemodule.am
misc-utils/getino.1.adoc [new file with mode: 0644]
misc-utils/getino.c [new file with mode: 0644]
misc-utils/meson.build
po-man/po4a.cfg

index 14d320cf34104de742377557426d7961de0012ab..7f56abe5ce3c4059015d2202af990afe53403ef0 100644 (file)
@@ -120,6 +120,7 @@ ylwrap
 /fsfreeze
 /fstrim
 /getopt
+/getino
 /hardlink
 /hexdump
 /hwclock
diff --git a/AUTHORS b/AUTHORS
index df9a885675027b2c3a71c66dee0128bbbefe3a91..d2dc15e205e57b8ccaa289dcaec2c1c7b1359af7 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -29,6 +29,7 @@ AUTHORS (merged projects & commands):
                        Theodore Ts'o <tytso@mit.edu>
       fstrim:          Lukas Czerner <lczerner@redhat.com>
       fsfreeze:        Hajime Taira <htaira@redhat.com>
+      getino:          Christian Goeschel Ndjomouo <cgoesc2@wgu.edu>
       getopt:          Frodo Looijaard <frodol@dds.nl>
       hardlink:        Jakub Jelinek <jakub@redhat.com>
       hwclock:         Bryan Henderson <bryanh@giraffe-data.com>
index 2ff5c0d32a74e55e90335b06f394d5aabf2643e0..9fafeed04751d9525bce1a0f4fde64d6a26a5a3a 100644 (file)
@@ -562,7 +562,7 @@ AC_CHECK_MEMBERS([struct termios.c_line],,,
 AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec],,,
     [[#include <sys/stat.h>]])
 
-AC_CHECK_TYPES([struct statx], [], [], [[#include <sys/stat.h>]])
+AC_CHECK_TYPES([struct statx], [have_struct_statx=yes], [have_struct_statx=no], [[#include <sys/stat.h>]])
 AC_CHECK_MEMBERS([struct statx.stx_mnt_id],,,
     [[#include <sys/stat.h>]])
 
@@ -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]),
index c09342b1f66572ee8fcfe845442fb427daffb84d..0a3534ca7dc12db0ae663f0a3a413461af574a82 100644 (file)
@@ -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()
index b764b56fbb2bfa1a14b1dae602db0719f2417651..5f2cd09e8babad5304edc3caabb2f6e1b1dedf00 100644 (file)
@@ -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
 
index d0bc8d80653c3d5fbec13bb8ee1a5c8941760d25..7bc0aaae01f9fde463beabbf6853ac6b0840677b 100644 (file)
@@ -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 (file)
index 0000000..8ca9982
--- /dev/null
@@ -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 (file)
index 0000000..161f2fe
--- /dev/null
@@ -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 <cgoesc2@wgu.edu>
+ * Written by Christian Goeschel Ndjomouo <cgoesc2@wgu.edu>
+ *
+ * getino - get the unique pidfd or namespace inode number for a given PID
+ */
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#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;
+}
index 1cd4713ce472f8be7daa3e8f3156c08360a78630..7c7766768825bfbdaaf991094e597465172f830b 100644 (file)
@@ -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')
index 0356df8d30035f305915fc1728d6d3f6209ed5d6..a2e3871ebddfaaafd67228a045002f4577fe749f 100644 (file)
@@ -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