include/partx.h \
include/path.h \
include/pathnames.h \
+ include/pidutils.h \
include/pidfd-utils.h \
include/plymouth-ctrl.h \
include/procfs.h \
--- /dev/null
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Authors: Christian Goeschel Ndjomouo <cgoesc2@wgu.edu> [2025]
+ */
+#ifndef UTIL_LINUX_PIDUTILS_H
+#define UTIL_LINUX_PIDUTILS_H
+
+#include <sys/types.h>
+
+extern int ul_parse_pid_str(char *pidstr, pid_t *pid_num, ino_t *pfd_ino);
+
+#endif /* UTIL_LINUX_PIDUTILS_H */
\ No newline at end of file
lib/mbsalign.c \
lib/mbsedit.c\
lib/md5.c \
+ lib/pidutils.c \
lib/pwdutils.c \
lib/randutils.c \
lib/sha1.c \
mbsalign.c
mbsedit.c
md5.c
+ pidutils.c
procfs.c
pwdutils.c
randutils.c
--- /dev/null
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Authors: Christian Goeschel Ndjomouo <cgoesc2@wgu.edu> [2025]
+ */
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "strutils.h"
+#include "pidutils.h"
+
+/*
+ * ul_parse_pid_str() - Parse a string and store the found pid and/or pidfd inode.
+ *
+ * @pidstr: string in format `pid:pidfd_inode` that is to be parsed
+ * @pid_num: stores pid number
+ * @pfd_ino: stores pidfd inode number
+ *
+ * If @pfd_ino is not destined to be set, pass it as NULL.
+ *
+ * Return: On success, 0 is returned.
+ * On failure, a negative errno number will be returned.
+ */
+int ul_parse_pid_str(char *pidstr, pid_t *pid_num, ino_t *pfd_ino)
+{
+ int rc;
+ char *end = NULL;
+ int64_t num = 0;
+
+ if (!pidstr || !*pidstr || !pid_num)
+ return -EINVAL;
+
+ num = strtoimax(pidstr, &end, 10);
+ if (errno == 0 && ((num && num < 1) || (num && num > SINT_MAX(pid_t))))
+ return -ERANGE;
+ *pid_num = (pid_t) num;
+
+ if (*end == ':' && pfd_ino) {
+ rc = ul_strtou64(++end, pfd_ino, 10);
+ if (rc != 0)
+ return -ERANGE;
+ *end = '\0';
+ }
+ if (errno != 0 || ((end && *end != '\0') || pidstr >= end))
+ return -EINVAL;
+ return 0;
+}
build_by_default: program_tests)
exes += exe
+exe = executable(
+ 'test_kill_pidfdino',
+ 'tests/helpers/test_kill_pidfdino.c',
+ include_directories : includes,
+ link_with : lib_common,
+ build_by_default: program_tests)
+if not is_disabler(exe)
+ exes += exe
+endif
+
if LINUX and lib_rt.found()
exe = executable(
'test_mkfds',
== SYNOPSIS
-*kill* [**-**_signal_|*-s* _signal_|*-p*] [*-q* _value_] [*-a*] [*--timeout* _milliseconds_ _signal_] [*--*] _pid_|_name_...
+*kill* [**-**_signal_|*-s* _signal_|*-p*] [*-q* _value_] [*-a*] [*--timeout* _milliseconds_ _signal_] [*--*] _pid_|_pid_:_pidfd_inode_|_name_...
+
+*kill* [**-**_signal_|*-s* _signal_] _pid_|_pid_:_pidfd_inode_...
*kill* *-l* [_number_|``0x``_sigmask_] | *-L*
**-**__n__;;
where _n_ is larger than 1. All processes in process group _n_ are signaled. When an argument of the form '-n' is given, and it is meant to denote a process group, either a signal must be specified first, or the argument must be preceded by a '--' option, otherwise it will be taken as the signal to send.
+_pid_:_pidfd_inode_::
+A process can be referenced by its _pid_ plus _pidfd_inode_ (pid:pidfd_inode), to uniquely identify it and perform race-free signalling. This works only for the options -s, --signal and -_signal_. Requires kernel version 6.9 and later.
+
_name_::
All processes invoked using this _name_ will be signaled.
*
* Copyright (C) 2014 Sami Kerola <kerolasa@iki.fi>
* Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2025 Christian Goeschel Ndjomouo <cgoesc2@wgu.edu>
*/
#include <ctype.h> /* for isdigit() */
#include <signal.h>
+#include <sys/stat.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "c.h"
#include "closestream.h"
#include "nls.h"
+#include "pidutils.h"
#include "pidfd-utils.h"
#include "procfs.h"
#include "pathnames.h"
#if defined(HAVE_PIDFD_OPEN) && defined(HAVE_PIDFD_SEND_SIGNAL)
# define USE_KILL_WITH_TIMEOUT 1
+# define USE_KILL_WITH_PIDFDINO 1
#endif
enum {
struct kill_control {
char *arg;
pid_t pid;
+ ino_t pidfd_ino;
int numsig;
#ifdef HAVE_SIGQUEUE
union sigval sigdata;
{
FILE *out = stdout;
fputs(USAGE_HEADER, out);
- fprintf(out, _(" %s [options] <pid>|<name>...\n"), program_invocation_short_name);
+ fprintf(out, _(" %s [options] <pid>|<pid>:<pidfd_ino>|<name>...\n"), program_invocation_short_name);
fputs(USAGE_SEPARATOR, out);
fputs(_("Forcibly terminate a process.\n"), out);
#endif
#ifdef USE_KILL_WITH_TIMEOUT
"pidfd",
+#endif
+#ifdef USE_KILL_WITH_PIDFDINO
+ "pidfdino",
#endif
};
}
#endif
+#ifdef USE_KILL_WITH_PIDFDINO
+static int validate_pfd_ino(int pfd, ino_t pfd_ino)
+{
+ int rc;
+ struct stat f;
+
+ rc = fstat(pfd, &f);
+ if (rc != 0)
+ return -EINVAL;
+
+ if (f.st_ino != pfd_ino)
+ return -EINVAL;
+ return 0;
+}
+#endif
+
static int kill_verbose(const struct kill_control *ctl)
{
int rc = 0;
rc = sigqueue(ctl->pid, ctl->numsig, ctl->sigdata);
else
#endif
- rc = kill(ctl->pid, ctl->numsig);
+#ifdef USE_KILL_WITH_PIDFDINO
+ if ((ctl->pidfd_ino > 0)) {
+ int pfd;
+ pfd = pidfd_open(ctl->pid, 0);
+ if (pfd < 0)
+ err(EXIT_FAILURE, _("pidfd_open() failed: %d"), ctl->pid);
+
+ rc = validate_pfd_ino(pfd, ctl->pidfd_ino);
+ if (rc < 0)
+ errx(EXIT_FAILURE, _("pidfd inode %"PRIu64" not found for pid %d: %s"),
+ ctl->pidfd_ino, ctl->pid, strerror(-rc));
+
+ rc = pidfd_send_signal(pfd, ctl->numsig, 0, 0);
+ if (rc < 0)
+ err(EXIT_FAILURE, _("pidfd_send_signal() failed"));
+ } else
+#endif
+ rc = kill(ctl->pid, ctl->numsig);
if (rc < 0)
warn(_("sending signal to %s failed"), ctl->arg);
int main(int argc, char **argv)
{
struct kill_control ctl = { .numsig = SIGTERM };
- int nerrs = 0, ct = 0;
+ int nerrs = 0, ct = 0, rc = 0;
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
/* The rest of the arguments should be process ids and names. */
for ( ; (ctl.arg = *argv) != NULL; argv++) {
- char *ep = NULL;
-
errno = 0;
- ctl.pid = strtol(ctl.arg, &ep, 10);
- if (errno == 0 && ep && *ep == '\0' && ctl.arg < ep) {
+
+ rc = ul_parse_pid_str(ctl.arg, &ctl.pid, &ctl.pidfd_ino);
+ if(errno == 0 && rc == 0) {
if (check_signal_handler(&ctl) <= 0)
continue;
if (kill_verbose(&ctl) != 0)
TS_HELPER_BLKID_FUZZ="${ts_helpersdir}test_blkid_fuzz"
TS_HELPER_PROCFS="${ts_helpersdir}test_procfs"
TS_HELPER_TIMEUTILS="${ts_helpersdir}test_timeutils"
+TS_HELPER_KILL_PIDFDINO="${ts_helpersdir}test_kill_pidfdino"
# paths to commands
TS_CMD_ADDPART=${TS_CMD_ADDPART:-"${ts_commandsdir}addpart"}
write:
test_byteswap:
test_md5:
-test_pathnames:
+test_pathnames:
test_sysinfo:
col:
colcrt:
more: libtinfo
pg: libncursesw libtinfo
rev:
-ul: libtinfo
+ul: libtinfo
+test_kill_pidfdino:
test_uuid_namespace_SOURCES = tests/helpers/test_uuid_namespace.c \
libuuid/src/predefined.c libuuid/src/unpack.c libuuid/src/unparse.c
+check_PROGRAMS += test_kill_pidfdino
+test_kill_pidfdino_SOURCES = tests/helpers/test_kill_pidfdino.c
+test_kill_pidfdino_LDADD = $(LDADD) libcommon.la
+
if LINUX
check_PROGRAMS += test_mkfds
test_mkfds_SOURCES = tests/helpers/test_mkfds.c tests/helpers/test_mkfds.h \
--- /dev/null
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * test_kill_pidfdino - return a pidfd inode for a process using its pid
+ *
+ * Written by Christian Goeschel Ndjomouo <cgoesc2@wgu.edu> [2025]
+ */
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "c.h"
+#include "exitcodes.h"
+#include "strutils.h"
+#include "pidfd-utils.h"
+
+int main(int argc, char **argv)
+{
+ int pfd, rc = 0;
+ pid_t pid;
+ struct stat f;
+
+ if (argc != 2)
+ err(EXIT_FAILURE, "usage: %s PID", *argv);
+
+ pid = strtopid_or_err(argv[1], "invalid pid");
+ pfd = pidfd_open(pid, 0);
+ if (pfd < 0)
+ err_nosys(EXIT_FAILURE, "pidfd_open() failed %d", pid);
+
+ rc = fstat(pfd, &f);
+ if (rc != 0)
+ err(EXIT_FAILURE, "fstat() failed: %d", pfd);
+
+ printf("%"PRIu64"\n", f.st_ino);
+ return EXIT_SUCCESS;
+}
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+
+# 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.
+#
+# Copyright (C) 2025 Christian Goeschel Ndjomouo <cgoesc2@wgu.edu>
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="pidfdino"
+
+. "$TS_TOPDIR/functions.sh"
+ts_init "$*"
+
+# make sure we do not use shell built-in command
+if [ "$TS_USE_SYSTEM_COMMANDS" == "yes" ]; then
+ TS_CMD_KILL="$(which kill)"
+fi
+
+# The pidfs was only introduced in kernel version 6.9,
+# which means we cannot get a valid pidfd from earlier versions,
+# so we can confidently anticipate the test failure.
+if ts_kernel_ver_lt 6 9; then
+ TS_KNOWN_FAIL="yes"
+fi
+
+ts_check_test_command "$TS_CMD_KILL"
+ts_check_test_command "$TS_HELPER_SIGRECEIVE"
+ts_check_test_command "$TS_HELPER_KILL_PIDFDINO"
+
+. "$TS_SELF/kill_functions.sh"
+
+all_ok=true
+
+HELPER_SYMLINK="$(mktemp "${TS_OUTDIR}/opXXXXXXXXXXXXX")"
+ln -sf "$TS_HELPER_SIGRECEIVE" "$HELPER_SYMLINK"
+
+try_option()
+{
+ "$HELPER_SYMLINK" >> $TS_OUTPUT 2>> $TS_ERRLOG &
+ TEST_PID=$!
+ TEST_PIDFD_INO=$( "$TS_HELPER_KILL_PIDFDINO" ${TEST_PID} )
+ ts_skip_exitcode_not_supported
+
+ check_test_sigreceive "${TEST_PID}"
+ [ $? -eq 1 ] || echo "${HELPER_SYMLINK##*/} helper did not start" >> "$TS_OUTPUT"
+
+ "$TS_CMD_KILL" "$@" "${TEST_PID}:${TEST_PIDFD_INO}" >> $TS_OUTPUT 2>> $TS_ERRLOG
+ if [ $? -ne 0 ]; then
+ echo "kill $* did not work" >> "$TS_OUTPUT"
+ all_ok=false
+ fi
+ wait $TEST_PID
+ if [ $? -ne 1 ]; then
+ echo "wait $TEST_PID for $* did not work" >> "$TS_OUTPUT"
+ all_ok=false
+ fi
+}
+
+try_option -s 1
+try_option --signal 1
+try_option --signal HUP
+try_option --signal SIGHUP
+try_option -1
+try_option -HUP
+try_option -SIGHUP
+
+if $all_ok; then
+ echo 'all ok' >> "$TS_OUTPUT"
+fi
+rm -f "$HELPER_SYMLINK"
+
+ts_finalize
\ No newline at end of file