All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC] coresched: Manage core scheduling cookies for tasks
@ 2024-03-16 17:10 Thijs Raymakers
  2024-03-24 20:07 ` Karel Zak
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-03-16 17:10 UTC (permalink / raw)
  To: util-linux; +Cc: Thijs Raymakers

Hi all,

I'm looking for any comments on coresched, a program that allows you to
manage core scheduling cookies for tasks.

=== What is Core Scheduling ===
Core Scheduling can be used to ensure that certain tasks will never be
scheduled on the same physical core. This can be a useful, alternative,
mitigation to hardware vulnerabilities like L1tf or MDS.
The full software mitigation for these vulnerabilities would be to disable
SMT/Hyper-Threading. However, this can be prohibitively expensive and
therefore often not done in practice.
With Core Scheduling you can mitigate in these issues in some scenarios,
while keeping SMT enabled.

Core Scheduling works by adding a random "cookie" to a process. Only
processes with the same core scheduling cookie are allowed to run on
sibling cores. Tasks that trust each other can be given the same
cookie and untrusted tasks are given a different cookie.
This is important when running VMs that don't trust each other, as
it prevents a guest VM to leak data from another guest VM with L1tf or MDS.

=== Motivation ===
The kernel exposes a prctl uapi to manage core scheduling cookies (see
https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/core-scheduling.html)
Last week, I wanted to use core scheduling on some programs. Adding the
prctl calls and recompiling felt a bit inconvenient, so I looked for a
program like taskset that could do the job without having to modify the
target program. I couldn't find any, and so I wrote a small program that
does this. Hopefully it saves the next person some time :)

=== RFC ===
I'm looking forward to any comments that you might have on the patch!
Please note that I haven't written the manpage and the bash completion
script yet. I first wanted to get some feedback on the program before I
start documenting it in more detail.

I'm particularly curious about your thoughts on the following things:

- General comments about interacting with the program: Do the options
  make sense? Are there any necessary functions missing? Are the error
  messages helpful? Is the output too verbose/not verbose enough?
- How should the program behave if the prctl core scheduling API is not
  available? It has been in Linus' tree since november 2021
  (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
  disabled with CONFIG_SCHED_CORE=n
- Most of the options require the user to have the CAP_SYS_PTRACE
  capability. Should the program notify the user that the capability
  is missing when the prctl call returns -EPREM, or does a mention in the
  man page suffice?
- I've currently licensed it under the EUPL v1.2, which is easier to
  enforce in my jurisdiction than the GPL. It is GPL compatible so it
  shouldn't be an issue, but if anybody has any remarks on this, please
  let me know.

Thanks for taking the time!

Best regards,
Thijs Raymakers


Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>

---
 .gitignore                  |   1 +
 bash-completion/coresched   |   0
 configure.ac                |  12 +-
 meson.build                 |  16 +-
 meson_options.txt           |   2 +-
 schedutils/Makemodule.am    |   8 +
 schedutils/coresched.1.adoc |  16 ++
 schedutils/coresched.c      | 340 ++++++++++++++++++++++++++++++++++++
 8 files changed, 389 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index f7baab7a2..8244c43a9 100644
--- a/meson.build
+++ b/meson.build
@@ -3107,13 +3107,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+	       'schedutils/uclampset.1.adoc',
+         'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..60a21cd01
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,16 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..4be8f9fda
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,340 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+
+typedef enum {
+	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
+	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
+	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
+} core_sched_type_t;
+
+typedef enum {
+	SCHED_CORE_CMD_NONE = 0,
+	SCHED_CORE_CMD_GET = 1,
+	SCHED_CORE_CMD_CREATE = 2,
+	SCHED_CORE_CMD_COPY = 4,
+	SCHED_CORE_CMD_EXEC = 8,
+} core_sched_cmd_t;
+
+struct args {
+	pid_t from_pid;
+	pid_t to_pid;
+	core_sched_type_t type;
+	core_sched_cmd_t cmd;
+	int exec_argv_offset;
+};
+
+unsigned long core_sched_get_cookie(struct args *args);
+void core_sched_create_cookie(struct args *args);
+void core_sched_pull_cookie(pid_t from);
+void core_sched_push_cookie(pid_t to, core_sched_type_t type);
+void core_sched_copy_cookie(struct args *args);
+void core_sched_exec_with_cookie(struct args *args, char **argv);
+
+core_sched_type_t parse_core_sched_type(char *str);
+bool verify_arguments(struct args *args);
+void parse_arguments(int argc, char **argv, struct args *args);
+
+unsigned long core_sched_get_cookie(struct args *args)
+{
+	unsigned long cookie = 0;
+	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET,
+				args->from_pid, SCHED_CORE_SCOPE_PID, &cookie);
+	if (prctl_errno) {
+		errx(-prctl_errno, "Failed to get cookie from PID %d",
+		     args->from_pid);
+	}
+	return cookie;
+}
+
+void core_sched_create_cookie(struct args *args)
+{
+	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
+				args->from_pid, args->type, 0);
+	if (prctl_errno) {
+		errx(-prctl_errno, "Failed to create cookie for PID %d",
+		     args->from_pid);
+	}
+}
+
+void core_sched_pull_cookie(pid_t from)
+{
+	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+				SCHED_CORE_SCOPE_PID, 0);
+	if (prctl_errno) {
+		errx(-prctl_errno, "Failed to pull cookie from PID %d", from);
+	}
+}
+
+void core_sched_push_cookie(pid_t to, core_sched_type_t type)
+{
+	int prctl_errno =
+		prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0);
+	if (prctl_errno) {
+		errx(-prctl_errno, "Failed to push cookie to PID %d", to);
+	}
+}
+
+void core_sched_copy_cookie(struct args *args)
+{
+	core_sched_pull_cookie(args->from_pid);
+	core_sched_push_cookie(args->to_pid, args->type);
+}
+
+void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	if (!args->exec_argv_offset) {
+		errx(EINVAL, "when --exec is provided, a program name "
+			     "has to be given.");
+	}
+
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	pid_t pid = fork();
+	if (pid == -1) {
+		errx(errno, "Failed to spawn new process");
+	}
+
+	if (!pid) {
+		// If a source PID is provided, try to copy the cookie from
+		// that PID. Otherwise, create a brand new cookie with the
+		// provided type.
+		if (args->from_pid) {
+			core_sched_pull_cookie(args->from_pid);
+		} else {
+			args->from_pid = getpid();
+			core_sched_create_cookie(args);
+		}
+		if (execvp(argv[0], argv)) {
+			errexec(argv[0]);
+		}
+	} else {
+		int status = 0;
+		waitpid(pid, &status, 0);
+		exit(status);
+	}
+}
+
+core_sched_type_t parse_core_sched_type(char *str)
+{
+	if (!strncmp(str, "pid\0", 4)) {
+		return SCHED_CORE_SCOPE_PID;
+	} else if (!strncmp(str, "tgid\0", 5)) {
+		return SCHED_CORE_SCOPE_TGID;
+	} else if (!strncmp(str, "pgid\0", 5)) {
+		return SCHED_CORE_SCOPE_PGID;
+	}
+
+	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
+	     str);
+	__builtin_unreachable();
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
+	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" -g, --get <PID>         get the core scheduling cookie of a PID"),
+		stdout);
+	fputsln(_(" -n, --new <PID>         assign a new core scheduling cookie to PID"),
+		stdout);
+	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
+		  "                           another PID, requires the --source and --dest option"),
+		stdout);
+	fputsln(_(" -e, --exec              execute a program with a new core scheduling\n"
+		  "                           cookie."),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
+		stdout);
+	fputsln(_(" -d, --dest <PID>        where to copy the core scheduling cookie to."),
+		stdout);
+	fputsln(_(" -t, --type              type of the destination PID, or the type of\n"
+		  "                           the PID when a new core scheduling cookie\n"
+		  "                           is created. Can be one of the following:\n"
+		  "                           pid, tgid or pgid. Defaults to tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fprintf(stdout,
+		USAGE_HELP_OPTIONS(
+			25)); /* char offset to align option descriptions */
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+bool verify_arguments(struct args *args)
+{
+	if (args->cmd == SCHED_CORE_CMD_NONE) {
+		usage();
+	}
+
+	// Check if the value of args->cmd is a power of 2
+	// In that case, only a single function option was set.
+	if (!(args->cmd && !(args->cmd & (args->cmd - 1)))) {
+		errx(EINVAL, "Cannot do more than one function at a time.");
+	}
+
+	if (args->from_pid < 0) {
+		errx(EINVAL, "source PID cannot be negative");
+	}
+
+	if (args->to_pid < 0) {
+		errx(EINVAL, "destination PID cannot be negative");
+	}
+
+	if (args->from_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
+		errx(EINVAL, "valid argument to --source is required");
+	}
+
+	if (args->to_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
+		errx(EINVAL, "valid argument to --dest is required");
+	}
+
+	if (args->from_pid == 0 && args->cmd != SCHED_CORE_CMD_EXEC) {
+		errx(EINVAL, "PID cannot be zero");
+	}
+
+	return true;
+}
+
+void parse_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	enum {
+		OPT_GET = 'g',
+		OPT_NEW = 'n',
+		OPT_COPY = 'c',
+		OPT_EXEC = 'e',
+		OPT_SRC = 's',
+		OPT_DEST = 'd',
+		OPT_TYPE = 't',
+		OPT_VERSION = 'V',
+		OPT_HELP = 'h'
+	};
+
+	static const struct option longopts[] = {
+		{ "get", required_argument, NULL, OPT_GET },
+		{ "new", required_argument, NULL, OPT_NEW },
+		{ "copy", no_argument, NULL, OPT_COPY },
+		{ "exec", no_argument, NULL, OPT_EXEC },
+		{ "source", required_argument, NULL, OPT_SRC },
+		{ "destination", required_argument, NULL, OPT_DEST },
+		{ "type", required_argument, NULL, OPT_TYPE },
+		{ "version", no_argument, NULL, OPT_VERSION },
+		{ "help", no_argument, NULL, OPT_HELP },
+		{ NULL, 0, NULL, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "g:n:ces:d:t:Vh", longopts,
+				NULL)) != -1)
+		switch (c) {
+		case OPT_GET:
+			args->cmd |= SCHED_CORE_CMD_GET;
+			args->from_pid = strtos32_or_err(
+				optarg, "Failed to parse PID for --get");
+			break;
+		case OPT_NEW:
+			args->cmd |= SCHED_CORE_CMD_CREATE;
+			args->from_pid = strtos32_or_err(
+				optarg, "Failed to parse PID for --new");
+			break;
+		case OPT_COPY:
+			args->cmd |= SCHED_CORE_CMD_COPY;
+			break;
+		case OPT_EXEC:
+			args->cmd |= SCHED_CORE_CMD_EXEC;
+			break;
+		case OPT_SRC:
+			args->from_pid = strtos32_or_err(
+				optarg, "Failed to parse PID for --source");
+			break;
+		case OPT_DEST:
+			args->to_pid = strtos32_or_err(
+				optarg, "Failed to parse PID for --dest");
+			break;
+		case OPT_TYPE:
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case OPT_VERSION:
+			print_version(EXIT_SUCCESS);
+		case OPT_HELP:
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+
+	if (argc > optind) {
+		args->exec_argv_offset = optind;
+	}
+	verify_arguments(args);
+}
+
+int main(int argc, char **argv)
+{
+	struct args arguments = { 0 };
+	arguments.type = SCHED_CORE_SCOPE_TGID;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_arguments(argc, argv, &arguments);
+
+	unsigned long cookie = 0;
+	switch (arguments.cmd) {
+	case SCHED_CORE_CMD_GET:
+		cookie = core_sched_get_cookie(&arguments);
+		if (cookie) {
+			printf("core scheduling cookie of pid %d is 0x%lx\n",
+			       arguments.from_pid, cookie);
+		} else {
+			printf("pid %d doesn't have a core scheduling cookie\n",
+			       arguments.from_pid);
+			exit(1);
+		}
+		break;
+	case SCHED_CORE_CMD_CREATE:
+		core_sched_create_cookie(&arguments);
+		break;
+	case SCHED_CORE_CMD_COPY:
+		core_sched_copy_cookie(&arguments);
+		break;
+	case SCHED_CORE_CMD_EXEC:
+		core_sched_exec_with_cookie(&arguments, argv);
+		break;
+	default:
+		usage();
+		exit(1);
+	}
+}
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH RFC] coresched: Manage core scheduling cookies for tasks
  2024-03-16 17:10 [PATCH RFC] coresched: Manage core scheduling cookies for tasks Thijs Raymakers
@ 2024-03-24 20:07 ` Karel Zak
  2024-03-26 14:41   ` Phil Auld
  0 siblings, 1 reply; 42+ messages in thread
From: Karel Zak @ 2024-03-24 20:07 UTC (permalink / raw)
  To: Thijs Raymakers, Phil Auld; +Cc: util-linux

On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
> I'm looking for any comments on coresched, a program that allows you to
> manage core scheduling cookies for tasks.

 https://github.com/util-linux/util-linux/pull/2839

Guys, you should sync your work and merge it together :-) 

CC: Phil Auld 

    Karel

> 
> === What is Core Scheduling ===
> Core Scheduling can be used to ensure that certain tasks will never be
> scheduled on the same physical core. This can be a useful, alternative,
> mitigation to hardware vulnerabilities like L1tf or MDS.
> The full software mitigation for these vulnerabilities would be to disable
> SMT/Hyper-Threading. However, this can be prohibitively expensive and
> therefore often not done in practice.
> With Core Scheduling you can mitigate in these issues in some scenarios,
> while keeping SMT enabled.
> 
> Core Scheduling works by adding a random "cookie" to a process. Only
> processes with the same core scheduling cookie are allowed to run on
> sibling cores. Tasks that trust each other can be given the same
> cookie and untrusted tasks are given a different cookie.
> This is important when running VMs that don't trust each other, as
> it prevents a guest VM to leak data from another guest VM with L1tf or MDS.
> 
> === Motivation ===
> The kernel exposes a prctl uapi to manage core scheduling cookies (see
> https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/core-scheduling.html)
> Last week, I wanted to use core scheduling on some programs. Adding the
> prctl calls and recompiling felt a bit inconvenient, so I looked for a
> program like taskset that could do the job without having to modify the
> target program. I couldn't find any, and so I wrote a small program that
> does this. Hopefully it saves the next person some time :)
> 
> === RFC ===
> I'm looking forward to any comments that you might have on the patch!
> Please note that I haven't written the manpage and the bash completion
> script yet. I first wanted to get some feedback on the program before I
> start documenting it in more detail.
> 
> I'm particularly curious about your thoughts on the following things:
> 
> - General comments about interacting with the program: Do the options
>   make sense? Are there any necessary functions missing? Are the error
>   messages helpful? Is the output too verbose/not verbose enough?
> - How should the program behave if the prctl core scheduling API is not
>   available? It has been in Linus' tree since november 2021
>   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
>   disabled with CONFIG_SCHED_CORE=n
> - Most of the options require the user to have the CAP_SYS_PTRACE
>   capability. Should the program notify the user that the capability
>   is missing when the prctl call returns -EPREM, or does a mention in the
>   man page suffice?
> - I've currently licensed it under the EUPL v1.2, which is easier to
>   enforce in my jurisdiction than the GPL. It is GPL compatible so it
>   shouldn't be an issue, but if anybody has any remarks on this, please
>   let me know.
> 
> Thanks for taking the time!
> 
> Best regards,
> Thijs Raymakers
> 
> 
> Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> 
> ---
>  .gitignore                  |   1 +
>  bash-completion/coresched   |   0
>  configure.ac                |  12 +-
>  meson.build                 |  16 +-
>  meson_options.txt           |   2 +-
>  schedutils/Makemodule.am    |   8 +
>  schedutils/coresched.1.adoc |  16 ++
>  schedutils/coresched.c      | 340 ++++++++++++++++++++++++++++++++++++
>  8 files changed, 389 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
> 
> diff --git a/.gitignore b/.gitignore
> index 6ecbfa7fe..316f3cdcc 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -94,6 +94,7 @@ ylwrap
>  /colcrt
>  /colrm
>  /column
> +/coresched
>  /ctrlaltdel
>  /delpart
>  /dmesg
> diff --git a/bash-completion/coresched b/bash-completion/coresched
> new file mode 100644
> index 000000000..e69de29bb
> diff --git a/configure.ac b/configure.ac
> index ab7c98636..3a189a075 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
>  AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
>  
>  # build_schedutils= is just configure-only variable to control
> -# ionice, taskset and chrt
> +# ionice, taskset, coresched and chrt
>  AC_ARG_ENABLE([schedutils],
> -  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
> +  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
>    [], [UL_DEFAULT_ENABLE([schedutils], [check])]
>  )
>  
> @@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
>  AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
>  
>  
> +UL_ENABLE_ALIAS([coresched], [schedutils])
> +UL_BUILD_INIT([coresched])
> +UL_REQUIRES_SYSCALL_CHECK([coresched],
> +	[UL_CHECK_SYSCALL([prctl])],
> +	[prctl])
> +AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
> +
> +
>  have_schedsetter=no
>  AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
>        [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
> diff --git a/meson.build b/meson.build
> index f7baab7a2..8244c43a9 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -3107,13 +3107,23 @@ exe4 = executable(
>    install : opt,
>    build_by_default : opt)
>  
> +exe5 = executable(
> +  'coresched',
> +  'schedutils/coresched.c',
> +  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, exe2, exe3, exe4]
> +  exes += [exe, exe2, exe3, exe4, exe5]
>    manadocs += ['schedutils/chrt.1.adoc',
>                 'schedutils/ionice.1.adoc',
>                 'schedutils/taskset.1.adoc',
> -	       'schedutils/uclampset.1.adoc']
> -  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
> +	       'schedutils/uclampset.1.adoc',
> +         'schedutils/coresched.1.adoc']
> +  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
>  endif
>  
>  ############################################################
> diff --git a/meson_options.txt b/meson_options.txt
> index 7b8cf3f35..3405c1b73 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
>  option('build-setterm', type : 'feature',
>         description : 'build setterm')
>  option('build-schedutils', type : 'feature',
> -       description : 'build chrt, ionice, taskset')
> +       description : 'build chrt, ionice, taskset, coresched')
>  option('build-wall', type : 'feature',
>         description : 'build wall')
>  option('build-write', type : 'feature',
> diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
> index 1040da85f..0cb655401 100644
> --- a/schedutils/Makemodule.am
> +++ b/schedutils/Makemodule.am
> @@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
>  uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
>  uclampset_LDADD = $(LDADD) libcommon.la
>  endif
> +
> +if BUILD_CORESCHED
> +usrbin_exec_PROGRAMS += coresched
> +MANPAGES += schedutils/coresched.1
> +dist_noinst_DATA += schedutils/coresched.1.adoc
> +coresched_SOURCES = schedutils/coresched.c
> +coresched_LDADD = $(LDADD) libcommon.la
> +endif
> diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
> new file mode 100644
> index 000000000..60a21cd01
> --- /dev/null
> +++ b/schedutils/coresched.1.adoc
> @@ -0,0 +1,16 @@
> +//po4a: entry man manual
> +////
> +coresched(1) manpage
> +////
> += coresched(1)
> +:doctype: manpage
> +:man manual: User Commands
> +:man source: util-linux {release-version}
> +:page-layout: base
> +:command: coresched
> +:colon: :
> +:copyright: ©
> +
> +== NAME
> +
> +coresched - manage core scheduling cookies for tasks
> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> new file mode 100644
> index 000000000..4be8f9fda
> --- /dev/null
> +++ b/schedutils/coresched.c
> @@ -0,0 +1,340 @@
> +/**
> + * SPDX-License-Identifier: EUPL-1.2
> + *
> + * coresched.c - manage core scheduling cookies for tasks
> + *
> + * Copyright (C) 2024 Thijs Raymakers
> + * Licensed under the EUPL v1.2
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <sys/wait.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "strutils.h"
> +
> +typedef enum {
> +	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
> +	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
> +	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
> +} core_sched_type_t;
> +
> +typedef enum {
> +	SCHED_CORE_CMD_NONE = 0,
> +	SCHED_CORE_CMD_GET = 1,
> +	SCHED_CORE_CMD_CREATE = 2,
> +	SCHED_CORE_CMD_COPY = 4,
> +	SCHED_CORE_CMD_EXEC = 8,
> +} core_sched_cmd_t;
> +
> +struct args {
> +	pid_t from_pid;
> +	pid_t to_pid;
> +	core_sched_type_t type;
> +	core_sched_cmd_t cmd;
> +	int exec_argv_offset;
> +};
> +
> +unsigned long core_sched_get_cookie(struct args *args);
> +void core_sched_create_cookie(struct args *args);
> +void core_sched_pull_cookie(pid_t from);
> +void core_sched_push_cookie(pid_t to, core_sched_type_t type);
> +void core_sched_copy_cookie(struct args *args);
> +void core_sched_exec_with_cookie(struct args *args, char **argv);
> +
> +core_sched_type_t parse_core_sched_type(char *str);
> +bool verify_arguments(struct args *args);
> +void parse_arguments(int argc, char **argv, struct args *args);
> +
> +unsigned long core_sched_get_cookie(struct args *args)
> +{
> +	unsigned long cookie = 0;
> +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET,
> +				args->from_pid, SCHED_CORE_SCOPE_PID, &cookie);
> +	if (prctl_errno) {
> +		errx(-prctl_errno, "Failed to get cookie from PID %d",
> +		     args->from_pid);
> +	}
> +	return cookie;
> +}
> +
> +void core_sched_create_cookie(struct args *args)
> +{
> +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
> +				args->from_pid, args->type, 0);
> +	if (prctl_errno) {
> +		errx(-prctl_errno, "Failed to create cookie for PID %d",
> +		     args->from_pid);
> +	}
> +}
> +
> +void core_sched_pull_cookie(pid_t from)
> +{
> +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +				SCHED_CORE_SCOPE_PID, 0);
> +	if (prctl_errno) {
> +		errx(-prctl_errno, "Failed to pull cookie from PID %d", from);
> +	}
> +}
> +
> +void core_sched_push_cookie(pid_t to, core_sched_type_t type)
> +{
> +	int prctl_errno =
> +		prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0);
> +	if (prctl_errno) {
> +		errx(-prctl_errno, "Failed to push cookie to PID %d", to);
> +	}
> +}
> +
> +void core_sched_copy_cookie(struct args *args)
> +{
> +	core_sched_pull_cookie(args->from_pid);
> +	core_sched_push_cookie(args->to_pid, args->type);
> +}
> +
> +void core_sched_exec_with_cookie(struct args *args, char **argv)
> +{
> +	if (!args->exec_argv_offset) {
> +		errx(EINVAL, "when --exec is provided, a program name "
> +			     "has to be given.");
> +	}
> +
> +	// Move the argument list to the first argument of the program
> +	argv = &argv[args->exec_argv_offset];
> +
> +	pid_t pid = fork();
> +	if (pid == -1) {
> +		errx(errno, "Failed to spawn new process");
> +	}
> +
> +	if (!pid) {
> +		// If a source PID is provided, try to copy the cookie from
> +		// that PID. Otherwise, create a brand new cookie with the
> +		// provided type.
> +		if (args->from_pid) {
> +			core_sched_pull_cookie(args->from_pid);
> +		} else {
> +			args->from_pid = getpid();
> +			core_sched_create_cookie(args);
> +		}
> +		if (execvp(argv[0], argv)) {
> +			errexec(argv[0]);
> +		}
> +	} else {
> +		int status = 0;
> +		waitpid(pid, &status, 0);
> +		exit(status);
> +	}
> +}
> +
> +core_sched_type_t parse_core_sched_type(char *str)
> +{
> +	if (!strncmp(str, "pid\0", 4)) {
> +		return SCHED_CORE_SCOPE_PID;
> +	} else if (!strncmp(str, "tgid\0", 5)) {
> +		return SCHED_CORE_SCOPE_TGID;
> +	} else if (!strncmp(str, "pgid\0", 5)) {
> +		return SCHED_CORE_SCOPE_PGID;
> +	}
> +
> +	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
> +	     str);
> +	__builtin_unreachable();
> +}
> +
> +static void __attribute__((__noreturn__)) usage(void)
> +{
> +	fputs(USAGE_HEADER, stdout);
> +	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
> +		program_invocation_short_name);
> +
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> +
> +	fputs(USAGE_FUNCTIONS, stdout);
> +	fputsln(_(" -g, --get <PID>         get the core scheduling cookie of a PID"),
> +		stdout);
> +	fputsln(_(" -n, --new <PID>         assign a new core scheduling cookie to PID"),
> +		stdout);
> +	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
> +		  "                           another PID, requires the --source and --dest option"),
> +		stdout);
> +	fputsln(_(" -e, --exec              execute a program with a new core scheduling\n"
> +		  "                           cookie."),
> +		stdout);
> +
> +	fputs(USAGE_OPTIONS, stdout);
> +	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
> +		stdout);
> +	fputsln(_(" -d, --dest <PID>        where to copy the core scheduling cookie to."),
> +		stdout);
> +	fputsln(_(" -t, --type              type of the destination PID, or the type of\n"
> +		  "                           the PID when a new core scheduling cookie\n"
> +		  "                           is created. Can be one of the following:\n"
> +		  "                           pid, tgid or pgid. Defaults to tgid."),
> +		stdout);
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fprintf(stdout,
> +		USAGE_HELP_OPTIONS(
> +			25)); /* char offset to align option descriptions */
> +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> +	exit(EXIT_SUCCESS);
> +}
> +
> +bool verify_arguments(struct args *args)
> +{
> +	if (args->cmd == SCHED_CORE_CMD_NONE) {
> +		usage();
> +	}
> +
> +	// Check if the value of args->cmd is a power of 2
> +	// In that case, only a single function option was set.
> +	if (!(args->cmd && !(args->cmd & (args->cmd - 1)))) {
> +		errx(EINVAL, "Cannot do more than one function at a time.");
> +	}
> +
> +	if (args->from_pid < 0) {
> +		errx(EINVAL, "source PID cannot be negative");
> +	}
> +
> +	if (args->to_pid < 0) {
> +		errx(EINVAL, "destination PID cannot be negative");
> +	}
> +
> +	if (args->from_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> +		errx(EINVAL, "valid argument to --source is required");
> +	}
> +
> +	if (args->to_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> +		errx(EINVAL, "valid argument to --dest is required");
> +	}
> +
> +	if (args->from_pid == 0 && args->cmd != SCHED_CORE_CMD_EXEC) {
> +		errx(EINVAL, "PID cannot be zero");
> +	}
> +
> +	return true;
> +}
> +
> +void parse_arguments(int argc, char **argv, struct args *args)
> +{
> +	int c;
> +
> +	enum {
> +		OPT_GET = 'g',
> +		OPT_NEW = 'n',
> +		OPT_COPY = 'c',
> +		OPT_EXEC = 'e',
> +		OPT_SRC = 's',
> +		OPT_DEST = 'd',
> +		OPT_TYPE = 't',
> +		OPT_VERSION = 'V',
> +		OPT_HELP = 'h'
> +	};
> +
> +	static const struct option longopts[] = {
> +		{ "get", required_argument, NULL, OPT_GET },
> +		{ "new", required_argument, NULL, OPT_NEW },
> +		{ "copy", no_argument, NULL, OPT_COPY },
> +		{ "exec", no_argument, NULL, OPT_EXEC },
> +		{ "source", required_argument, NULL, OPT_SRC },
> +		{ "destination", required_argument, NULL, OPT_DEST },
> +		{ "type", required_argument, NULL, OPT_TYPE },
> +		{ "version", no_argument, NULL, OPT_VERSION },
> +		{ "help", no_argument, NULL, OPT_HELP },
> +		{ NULL, 0, NULL, 0 }
> +	};
> +
> +	while ((c = getopt_long(argc, argv, "g:n:ces:d:t:Vh", longopts,
> +				NULL)) != -1)
> +		switch (c) {
> +		case OPT_GET:
> +			args->cmd |= SCHED_CORE_CMD_GET;
> +			args->from_pid = strtos32_or_err(
> +				optarg, "Failed to parse PID for --get");
> +			break;
> +		case OPT_NEW:
> +			args->cmd |= SCHED_CORE_CMD_CREATE;
> +			args->from_pid = strtos32_or_err(
> +				optarg, "Failed to parse PID for --new");
> +			break;
> +		case OPT_COPY:
> +			args->cmd |= SCHED_CORE_CMD_COPY;
> +			break;
> +		case OPT_EXEC:
> +			args->cmd |= SCHED_CORE_CMD_EXEC;
> +			break;
> +		case OPT_SRC:
> +			args->from_pid = strtos32_or_err(
> +				optarg, "Failed to parse PID for --source");
> +			break;
> +		case OPT_DEST:
> +			args->to_pid = strtos32_or_err(
> +				optarg, "Failed to parse PID for --dest");
> +			break;
> +		case OPT_TYPE:
> +			args->type = parse_core_sched_type(optarg);
> +			break;
> +		case OPT_VERSION:
> +			print_version(EXIT_SUCCESS);
> +		case OPT_HELP:
> +			usage();
> +		default:
> +			errtryhelp(EXIT_FAILURE);
> +		}
> +
> +	if (argc > optind) {
> +		args->exec_argv_offset = optind;
> +	}
> +	verify_arguments(args);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct args arguments = { 0 };
> +	arguments.type = SCHED_CORE_SCOPE_TGID;
> +
> +	setlocale(LC_ALL, "");
> +	bindtextdomain(PACKAGE, LOCALEDIR);
> +	textdomain(PACKAGE);
> +	close_stdout_atexit();
> +
> +	parse_arguments(argc, argv, &arguments);
> +
> +	unsigned long cookie = 0;
> +	switch (arguments.cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		cookie = core_sched_get_cookie(&arguments);
> +		if (cookie) {
> +			printf("core scheduling cookie of pid %d is 0x%lx\n",
> +			       arguments.from_pid, cookie);
> +		} else {
> +			printf("pid %d doesn't have a core scheduling cookie\n",
> +			       arguments.from_pid);
> +			exit(1);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_CREATE:
> +		core_sched_create_cookie(&arguments);
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		core_sched_copy_cookie(&arguments);
> +		break;
> +	case SCHED_CORE_CMD_EXEC:
> +		core_sched_exec_with_cookie(&arguments, argv);
> +		break;
> +	default:
> +		usage();
> +		exit(1);
> +	}
> +}
> -- 
> 2.44.0
> 
> 

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH RFC] coresched: Manage core scheduling cookies for tasks
  2024-03-24 20:07 ` Karel Zak
@ 2024-03-26 14:41   ` Phil Auld
  2024-03-26 14:45     ` Phil Auld
                       ` (2 more replies)
  0 siblings, 3 replies; 42+ messages in thread
From: Phil Auld @ 2024-03-26 14:41 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: Karel Zak, util-linux

On Sun, Mar 24, 2024 at 09:07:45PM +0100 Karel Zak wrote:
> On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
> > I'm looking for any comments on coresched, a program that allows you to
> > manage core scheduling cookies for tasks.
> 
>  https://github.com/util-linux/util-linux/pull/2839
> 
> Guys, you should sync your work and merge it together :-) 
> 
> CC: Phil Auld 
>

After looking at this some more I'd like to try to add your
pull/push use case to my code instead.

There are some issues I'm having with the arguments in this one.
I think it's a little counter-intuitive.

The exec should not fork first. 

And the error handling I think is just not right. I believe prctl()
returns -1 on error and sets errno. This code will report EPERM for all
prtcl errors.  While EPERM is likely I don't think it's the only error.

I put a few other comments inline as well.


Cheers,
Phil

>     Karel
> 
> > 
> > === What is Core Scheduling ===
> > Core Scheduling can be used to ensure that certain tasks will never be
> > scheduled on the same physical core. This can be a useful, alternative,
> > mitigation to hardware vulnerabilities like L1tf or MDS.
> > The full software mitigation for these vulnerabilities would be to disable
> > SMT/Hyper-Threading. However, this can be prohibitively expensive and
> > therefore often not done in practice.
> > With Core Scheduling you can mitigate in these issues in some scenarios,
> > while keeping SMT enabled.
> > 
> > Core Scheduling works by adding a random "cookie" to a process. Only
> > processes with the same core scheduling cookie are allowed to run on
> > sibling cores. Tasks that trust each other can be given the same
> > cookie and untrusted tasks are given a different cookie.
> > This is important when running VMs that don't trust each other, as
> > it prevents a guest VM to leak data from another guest VM with L1tf or MDS.
> > 
> > === Motivation ===
> > The kernel exposes a prctl uapi to manage core scheduling cookies (see
> > https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/core-scheduling.html)
> > Last week, I wanted to use core scheduling on some programs. Adding the
> > prctl calls and recompiling felt a bit inconvenient, so I looked for a
> > program like taskset that could do the job without having to modify the
> > target program. I couldn't find any, and so I wrote a small program that
> > does this. Hopefully it saves the next person some time :)
> > 
> > === RFC ===
> > I'm looking forward to any comments that you might have on the patch!
> > Please note that I haven't written the manpage and the bash completion
> > script yet. I first wanted to get some feedback on the program before I
> > start documenting it in more detail.
> > 
> > I'm particularly curious about your thoughts on the following things:
> > 
> > - General comments about interacting with the program: Do the options
> >   make sense? Are there any necessary functions missing? Are the error
> >   messages helpful? Is the output too verbose/not verbose enough?
> > - How should the program behave if the prctl core scheduling API is not
> >   available? It has been in Linus' tree since november 2021
> >   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
> >   disabled with CONFIG_SCHED_CORE=n


This one is tricky. If I recall the error you get is ambiguous in this case
(EINVAL, which can be returned for other reasons). It would be nice to be
able to report that specifically. 


> > - Most of the options require the user to have the CAP_SYS_PTRACE
> >   capability. Should the program notify the user that the capability
> >   is missing when the prctl call returns -EPREM, or does a mention in the
> >   man page suffice?

Reporting the correct error message should be enough. 


> > - I've currently licensed it under the EUPL v1.2, which is easier to
> >   enforce in my jurisdiction than the GPL. It is GPL compatible so it
> >   shouldn't be an issue, but if anybody has any remarks on this, please
> >   let me know.

The util-linux package is GPL so personally I think it would be good to just
make any new addition to that package also GPL. 


> > 
> > Thanks for taking the time!
> > 
> > Best regards,
> > Thijs Raymakers
> > 
> > 
> > Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> > 
> > ---
> >  .gitignore                  |   1 +
> >  bash-completion/coresched   |   0
> >  configure.ac                |  12 +-
> >  meson.build                 |  16 +-
> >  meson_options.txt           |   2 +-
> >  schedutils/Makemodule.am    |   8 +
> >  schedutils/coresched.1.adoc |  16 ++
> >  schedutils/coresched.c      | 340 ++++++++++++++++++++++++++++++++++++
> >  8 files changed, 389 insertions(+), 6 deletions(-)
> >  create mode 100644 bash-completion/coresched
> >  create mode 100644 schedutils/coresched.1.adoc
> >  create mode 100644 schedutils/coresched.c
> > 
> > diff --git a/.gitignore b/.gitignore
> > index 6ecbfa7fe..316f3cdcc 100644
> > --- a/.gitignore
> > +++ b/.gitignore
> > @@ -94,6 +94,7 @@ ylwrap
> >  /colcrt
> >  /colrm
> >  /column
> > +/coresched
> >  /ctrlaltdel
> >  /delpart
> >  /dmesg
> > diff --git a/bash-completion/coresched b/bash-completion/coresched
> > new file mode 100644
> > index 000000000..e69de29bb
> > diff --git a/configure.ac b/configure.ac
> > index ab7c98636..3a189a075 100644
> > --- a/configure.ac
> > +++ b/configure.ac
> > @@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
> >  AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
> >  
> >  # build_schedutils= is just configure-only variable to control
> > -# ionice, taskset and chrt
> > +# ionice, taskset, coresched and chrt
> >  AC_ARG_ENABLE([schedutils],
> > -  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
> > +  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
> >    [], [UL_DEFAULT_ENABLE([schedutils], [check])]
> >  )
> >  
> > @@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
> >  AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
> >  
> >  
> > +UL_ENABLE_ALIAS([coresched], [schedutils])
> > +UL_BUILD_INIT([coresched])
> > +UL_REQUIRES_SYSCALL_CHECK([coresched],
> > +	[UL_CHECK_SYSCALL([prctl])],
> > +	[prctl])
> > +AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
> > +
> > +
> >  have_schedsetter=no
> >  AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
> >        [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
> > diff --git a/meson.build b/meson.build
> > index f7baab7a2..8244c43a9 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -3107,13 +3107,23 @@ exe4 = executable(
> >    install : opt,
> >    build_by_default : opt)
> >  
> > +exe5 = executable(
> > +  'coresched',
> > +  'schedutils/coresched.c',
> > +  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, exe2, exe3, exe4]
> > +  exes += [exe, exe2, exe3, exe4, exe5]
> >    manadocs += ['schedutils/chrt.1.adoc',
> >                 'schedutils/ionice.1.adoc',
> >                 'schedutils/taskset.1.adoc',
> > -	       'schedutils/uclampset.1.adoc']
> > -  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
> > +	       'schedutils/uclampset.1.adoc',
> > +         'schedutils/coresched.1.adoc']
> > +  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
> >  endif
> >  
> >  ############################################################
> > diff --git a/meson_options.txt b/meson_options.txt
> > index 7b8cf3f35..3405c1b73 100644
> > --- a/meson_options.txt
> > +++ b/meson_options.txt
> > @@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
> >  option('build-setterm', type : 'feature',
> >         description : 'build setterm')
> >  option('build-schedutils', type : 'feature',
> > -       description : 'build chrt, ionice, taskset')
> > +       description : 'build chrt, ionice, taskset, coresched')
> >  option('build-wall', type : 'feature',
> >         description : 'build wall')
> >  option('build-write', type : 'feature',
> > diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
> > index 1040da85f..0cb655401 100644
> > --- a/schedutils/Makemodule.am
> > +++ b/schedutils/Makemodule.am
> > @@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
> >  uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
> >  uclampset_LDADD = $(LDADD) libcommon.la
> >  endif
> > +
> > +if BUILD_CORESCHED
> > +usrbin_exec_PROGRAMS += coresched
> > +MANPAGES += schedutils/coresched.1
> > +dist_noinst_DATA += schedutils/coresched.1.adoc
> > +coresched_SOURCES = schedutils/coresched.c
> > +coresched_LDADD = $(LDADD) libcommon.la
> > +endif
> > diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
> > new file mode 100644
> > index 000000000..60a21cd01
> > --- /dev/null
> > +++ b/schedutils/coresched.1.adoc
> > @@ -0,0 +1,16 @@
> > +//po4a: entry man manual
> > +////
> > +coresched(1) manpage
> > +////
> > += coresched(1)
> > +:doctype: manpage
> > +:man manual: User Commands
> > +:man source: util-linux {release-version}
> > +:page-layout: base
> > +:command: coresched
> > +:colon: :
> > +:copyright: ©
> > +
> > +== NAME
> > +
> > +coresched - manage core scheduling cookies for tasks
> > diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> > new file mode 100644
> > index 000000000..4be8f9fda
> > --- /dev/null
> > +++ b/schedutils/coresched.c
> > @@ -0,0 +1,340 @@
> > +/**
> > + * SPDX-License-Identifier: EUPL-1.2
> > + *
> > + * coresched.c - manage core scheduling cookies for tasks
> > + *
> > + * Copyright (C) 2024 Thijs Raymakers
> > + * Licensed under the EUPL v1.2
> > + */
> > +
> > +#include <getopt.h>
> > +#include <stdbool.h>
> > +#include <stdio.h>
> > +#include <sys/prctl.h>
> > +#include <sys/wait.h>
> > +
> > +#include "c.h"
> > +#include "closestream.h"
> > +#include "nls.h"
> > +#include "strutils.h"
> > +
> > +typedef enum {
> > +	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
> > +	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
> > +	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
> > +} core_sched_type_t;
> > +

Is this needed? You've made the names a little shorter I guess.
Also some versions of prctl.h don't have the PR_SCHED_CORE_SCOPE* defines
even when they have the base PR_SCHED_CORE macros.


> > +typedef enum {
> > +	SCHED_CORE_CMD_NONE = 0,
> > +	SCHED_CORE_CMD_GET = 1,
> > +	SCHED_CORE_CMD_CREATE = 2,
> > +	SCHED_CORE_CMD_COPY = 4,
> > +	SCHED_CORE_CMD_EXEC = 8,
> > +} core_sched_cmd_t;
> > +
> > +struct args {
> > +	pid_t from_pid;
> > +	pid_t to_pid;
> > +	core_sched_type_t type;
> > +	core_sched_cmd_t cmd;
> > +	int exec_argv_offset;
> > +};
> > +
> > +unsigned long core_sched_get_cookie(struct args *args);
> > +void core_sched_create_cookie(struct args *args);
> > +void core_sched_pull_cookie(pid_t from);
> > +void core_sched_push_cookie(pid_t to, core_sched_type_t type);
> > +void core_sched_copy_cookie(struct args *args);
> > +void core_sched_exec_with_cookie(struct args *args, char **argv);
> > +
> > +core_sched_type_t parse_core_sched_type(char *str);
> > +bool verify_arguments(struct args *args);
> > +void parse_arguments(int argc, char **argv, struct args *args);
> > +
> > +unsigned long core_sched_get_cookie(struct args *args)
> > +{
> > +	unsigned long cookie = 0;
> > +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET,
> > +				args->from_pid, SCHED_CORE_SCOPE_PID, &cookie);
> > +	if (prctl_errno) {
> > +		errx(-prctl_errno, "Failed to get cookie from PID %d",
> > +		     args->from_pid);
> > +	}

prctl_errno will be 0 on success or -1 on failure. This will report EPERM for
every error. 


> > +	return cookie;
> > +}
> > +
> > +void core_sched_create_cookie(struct args *args)
> > +{
> > +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
> > +				args->from_pid, args->type, 0);

We are creating a new cookie on from_pid, seems odd.


> > +	if (prctl_errno) {
> > +		errx(-prctl_errno, "Failed to create cookie for PID %d",
> > +		     args->from_pid);
> > +	}
> > +}
> > +
> > +void core_sched_pull_cookie(pid_t from)
> > +{
> > +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> > +				SCHED_CORE_SCOPE_PID, 0);
> > +	if (prctl_errno) {
> > +		errx(-prctl_errno, "Failed to pull cookie from PID %d", from);
> > +	}
> > +}
> > +
> > +void core_sched_push_cookie(pid_t to, core_sched_type_t type)
> > +{
> > +	int prctl_errno =
> > +		prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0);
> > +	if (prctl_errno) {
> > +		errx(-prctl_errno, "Failed to push cookie to PID %d", to);
> > +	}
> > +}
> > +
> > +void core_sched_copy_cookie(struct args *args)
> > +{
> > +	core_sched_pull_cookie(args->from_pid);
> > +	core_sched_push_cookie(args->to_pid, args->type);
> > +}
> > +
> > +void core_sched_exec_with_cookie(struct args *args, char **argv)
> > +{
> > +	if (!args->exec_argv_offset) {
> > +		errx(EINVAL, "when --exec is provided, a program name "
> > +			     "has to be given.");
> > +	}
> > +
> > +	// Move the argument list to the first argument of the program
> > +	argv = &argv[args->exec_argv_offset];
> > +
> > +	pid_t pid = fork();
> > +	if (pid == -1) {
> > +		errx(errno, "Failed to spawn new process");
> > +	}

Don't fork here.  Just exec to the new program, as the other wrapper
programs like taskset do. 


> > +
> > +	if (!pid) {
> > +		// If a source PID is provided, try to copy the cookie from
> > +		// that PID. Otherwise, create a brand new cookie with the
> > +		// provided type.
> > +		if (args->from_pid) {
> > +			core_sched_pull_cookie(args->from_pid);
> > +		} else {
> > +			args->from_pid = getpid();
> > +			core_sched_create_cookie(args);
> > +		}
> > +		if (execvp(argv[0], argv)) {
> > +			errexec(argv[0]);
> > +		}
> > +	} else {
> > +		int status = 0;
> > +		waitpid(pid, &status, 0);
> > +		exit(status);
> > +	}
> > +}
> > +
> > +core_sched_type_t parse_core_sched_type(char *str)
> > +{
> > +	if (!strncmp(str, "pid\0", 4)) {
> > +		return SCHED_CORE_SCOPE_PID;
> > +	} else if (!strncmp(str, "tgid\0", 5)) {
> > +		return SCHED_CORE_SCOPE_TGID;
> > +	} else if (!strncmp(str, "pgid\0", 5)) {
> > +		return SCHED_CORE_SCOPE_PGID;
> > +	}
> > +
> > +	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
> > +	     str);
> > +	__builtin_unreachable();
> > +}
> > +
> > +static void __attribute__((__noreturn__)) usage(void)
> > +{
> > +	fputs(USAGE_HEADER, stdout);
> > +	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
> > +	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
> > +		program_invocation_short_name);
> > +	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
> > +		program_invocation_short_name);
> > +	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
> > +		program_invocation_short_name);

There are 4 different arguments that take a pid when there are really
only at most 2 needed.

"Get" could just be the default with no arguments given.


> > +
> > +	fputs(USAGE_SEPARATOR, stdout);
> > +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> > +
> > +	fputs(USAGE_FUNCTIONS, stdout);
> > +	fputsln(_(" -g, --get <PID>         get the core scheduling cookie of a PID"),
> > +		stdout);
> > +	fputsln(_(" -n, --new <PID>         assign a new core scheduling cookie to PID"),
> > +		stdout);
> > +	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
> > +		  "                           another PID, requires the --source and --dest option"),
> > +		stdout);
> > +	fputsln(_(" -e, --exec              execute a program with a new core scheduling\n"
> > +		  "                           cookie."),
> > +		stdout);
> > +
> > +	fputs(USAGE_OPTIONS, stdout);
> > +	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
> > +		stdout);
> > +	fputsln(_(" -d, --dest <PID>        where to copy the core scheduling cookie to."),
> > +		stdout);
> > +	fputsln(_(" -t, --type              type of the destination PID, or the type of\n"
> > +		  "                           the PID when a new core scheduling cookie\n"
> > +		  "                           is created. Can be one of the following:\n"
> > +		  "                           pid, tgid or pgid. Defaults to tgid."),
> > +		stdout);
> > +	fputs(USAGE_SEPARATOR, stdout);
> > +	fprintf(stdout,
> > +		USAGE_HELP_OPTIONS(
> > +			25)); /* char offset to align option descriptions */
> > +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> > +	exit(EXIT_SUCCESS);
> > +}
> > +
> > +bool verify_arguments(struct args *args)
> > +{
> > +	if (args->cmd == SCHED_CORE_CMD_NONE) {
> > +		usage();
> > +	}
> > +
> > +	// Check if the value of args->cmd is a power of 2
> > +	// In that case, only a single function option was set.
> > +	if (!(args->cmd && !(args->cmd & (args->cmd - 1)))) {
> > +		errx(EINVAL, "Cannot do more than one function at a time.");
> > +	}
> > +
> > +	if (args->from_pid < 0) {
> > +		errx(EINVAL, "source PID cannot be negative");
> > +	}
> > +
> > +	if (args->to_pid < 0) {
> > +		errx(EINVAL, "destination PID cannot be negative");
> > +	}
> > +
> > +	if (args->from_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> > +		errx(EINVAL, "valid argument to --source is required");
> > +	}
> > +
> > +	if (args->to_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> > +		errx(EINVAL, "valid argument to --dest is required");
> > +	}
> > +
> > +	if (args->from_pid == 0 && args->cmd != SCHED_CORE_CMD_EXEC) {
> > +		errx(EINVAL, "PID cannot be zero");
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +void parse_arguments(int argc, char **argv, struct args *args)
> > +{
> > +	int c;
> > +
> > +	enum {
> > +		OPT_GET = 'g',
> > +		OPT_NEW = 'n',
> > +		OPT_COPY = 'c',
> > +		OPT_EXEC = 'e',
> > +		OPT_SRC = 's',
> > +		OPT_DEST = 'd',
> > +		OPT_TYPE = 't',
> > +		OPT_VERSION = 'V',
> > +		OPT_HELP = 'h'
> > +	};

This seems over-engineered.  You have to specificy the characters
in the getopt_long call anyway so this just complicates the code for
no reason it seems to me.

> > +
> > +	static const struct option longopts[] = {
> > +		{ "get", required_argument, NULL, OPT_GET },
> > +		{ "new", required_argument, NULL, OPT_NEW },
> > +		{ "copy", no_argument, NULL, OPT_COPY },
> > +		{ "exec", no_argument, NULL, OPT_EXEC },
> > +		{ "source", required_argument, NULL, OPT_SRC },
> > +		{ "destination", required_argument, NULL, OPT_DEST },
> > +		{ "type", required_argument, NULL, OPT_TYPE },
> > +		{ "version", no_argument, NULL, OPT_VERSION },
> > +		{ "help", no_argument, NULL, OPT_HELP },
> > +		{ NULL, 0, NULL, 0 }
> > +	};
> > +
> > +	while ((c = getopt_long(argc, argv, "g:n:ces:d:t:Vh", longopts,
> > +				NULL)) != -1)
> > +		switch (c) {
> > +		case OPT_GET:
> > +			args->cmd |= SCHED_CORE_CMD_GET;
> > +			args->from_pid = strtos32_or_err(
> > +				optarg, "Failed to parse PID for --get");
> > +			break;
> > +		case OPT_NEW:
> > +			args->cmd |= SCHED_CORE_CMD_CREATE;
> > +			args->from_pid = strtos32_or_err(
> > +				optarg, "Failed to parse PID for --new");
> > +			break;
> > +		case OPT_COPY:
> > +			args->cmd |= SCHED_CORE_CMD_COPY;
> > +			break;
> > +		case OPT_EXEC:
> > +			args->cmd |= SCHED_CORE_CMD_EXEC;
> > +			break;
> > +		case OPT_SRC:
> > +			args->from_pid = strtos32_or_err(
> > +				optarg, "Failed to parse PID for --source");
> > +			break;
> > +		case OPT_DEST:
> > +			args->to_pid = strtos32_or_err(
> > +				optarg, "Failed to parse PID for --dest");
> > +			break;
> > +		case OPT_TYPE:
> > +			args->type = parse_core_sched_type(optarg);
> > +			break;
> > +		case OPT_VERSION:
> > +			print_version(EXIT_SUCCESS);
> > +		case OPT_HELP:
> > +			usage();
> > +		default:
> > +			errtryhelp(EXIT_FAILURE);
> > +		}
> > +
> > +	if (argc > optind) {
> > +		args->exec_argv_offset = optind;
> > +	}
> > +	verify_arguments(args);
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	struct args arguments = { 0 };
> > +	arguments.type = SCHED_CORE_SCOPE_TGID;
> > +
> > +	setlocale(LC_ALL, "");
> > +	bindtextdomain(PACKAGE, LOCALEDIR);
> > +	textdomain(PACKAGE);
> > +	close_stdout_atexit();
> > +
> > +	parse_arguments(argc, argv, &arguments);
> > +
> > +	unsigned long cookie = 0;
> > +	switch (arguments.cmd) {
> > +	case SCHED_CORE_CMD_GET:
> > +		cookie = core_sched_get_cookie(&arguments);
> > +		if (cookie) {
> > +			printf("core scheduling cookie of pid %d is 0x%lx\n",
> > +			       arguments.from_pid, cookie);
> > +		} else {
> > +			printf("pid %d doesn't have a core scheduling cookie\n",
> > +			       arguments.from_pid);
> > +			exit(1);
> > +		}
> > +		break;
> > +	case SCHED_CORE_CMD_CREATE:
> > +		core_sched_create_cookie(&arguments);
> > +		break;
> > +	case SCHED_CORE_CMD_COPY:
> > +		core_sched_copy_cookie(&arguments);
> > +		break;
> > +	case SCHED_CORE_CMD_EXEC:
> > +		core_sched_exec_with_cookie(&arguments, argv);
> > +		break;
> > +	default:
> > +		usage();
> > +		exit(1);
> > +	}
> > +}
> > -- 
> > 2.44.0
> > 
> >
> 
> -- 
>  Karel Zak  <kzak@redhat.com>
>  http://karelzak.blogspot.com
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH RFC] coresched: Manage core scheduling cookies for tasks
  2024-03-26 14:41   ` Phil Auld
@ 2024-03-26 14:45     ` Phil Auld
  2024-03-26 17:49     ` [PATCH 0/1] " Thijs Raymakers
  2024-03-26 17:49     ` [PATCH 1/1] " Thijs Raymakers
  2 siblings, 0 replies; 42+ messages in thread
From: Phil Auld @ 2024-03-26 14:45 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: Karel Zak, util-linux

On Tue, Mar 26, 2024 at 10:41:50AM -0400 Phil Auld wrote:
> On Sun, Mar 24, 2024 at 09:07:45PM +0100 Karel Zak wrote:
> > On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
> > > I'm looking for any comments on coresched, a program that allows you to
> > > manage core scheduling cookies for tasks.
> > 
> >  https://github.com/util-linux/util-linux/pull/2839
> > 
> > Guys, you should sync your work and merge it together :-) 
> > 
> > CC: Phil Auld 
> >
> 
> After looking at this some more I'd like to try to add your
> pull/push use case to my code instead.
>

I should add that I like your short words for the scope and
may also use those...


Cheers,
Phil


> There are some issues I'm having with the arguments in this one.
> I think it's a little counter-intuitive.
> 
> The exec should not fork first. 
> 
> And the error handling I think is just not right. I believe prctl()
> returns -1 on error and sets errno. This code will report EPERM for all
> prtcl errors.  While EPERM is likely I don't think it's the only error.
> 
> I put a few other comments inline as well.
> 
> 
> Cheers,
> Phil
> 
> >     Karel
> > 
> > > 
> > > === What is Core Scheduling ===
> > > Core Scheduling can be used to ensure that certain tasks will never be
> > > scheduled on the same physical core. This can be a useful, alternative,
> > > mitigation to hardware vulnerabilities like L1tf or MDS.
> > > The full software mitigation for these vulnerabilities would be to disable
> > > SMT/Hyper-Threading. However, this can be prohibitively expensive and
> > > therefore often not done in practice.
> > > With Core Scheduling you can mitigate in these issues in some scenarios,
> > > while keeping SMT enabled.
> > > 
> > > Core Scheduling works by adding a random "cookie" to a process. Only
> > > processes with the same core scheduling cookie are allowed to run on
> > > sibling cores. Tasks that trust each other can be given the same
> > > cookie and untrusted tasks are given a different cookie.
> > > This is important when running VMs that don't trust each other, as
> > > it prevents a guest VM to leak data from another guest VM with L1tf or MDS.
> > > 
> > > === Motivation ===
> > > The kernel exposes a prctl uapi to manage core scheduling cookies (see
> > > https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/core-scheduling.html)
> > > Last week, I wanted to use core scheduling on some programs. Adding the
> > > prctl calls and recompiling felt a bit inconvenient, so I looked for a
> > > program like taskset that could do the job without having to modify the
> > > target program. I couldn't find any, and so I wrote a small program that
> > > does this. Hopefully it saves the next person some time :)
> > > 
> > > === RFC ===
> > > I'm looking forward to any comments that you might have on the patch!
> > > Please note that I haven't written the manpage and the bash completion
> > > script yet. I first wanted to get some feedback on the program before I
> > > start documenting it in more detail.
> > > 
> > > I'm particularly curious about your thoughts on the following things:
> > > 
> > > - General comments about interacting with the program: Do the options
> > >   make sense? Are there any necessary functions missing? Are the error
> > >   messages helpful? Is the output too verbose/not verbose enough?
> > > - How should the program behave if the prctl core scheduling API is not
> > >   available? It has been in Linus' tree since november 2021
> > >   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
> > >   disabled with CONFIG_SCHED_CORE=n
> 
> 
> This one is tricky. If I recall the error you get is ambiguous in this case
> (EINVAL, which can be returned for other reasons). It would be nice to be
> able to report that specifically. 
> 
> 
> > > - Most of the options require the user to have the CAP_SYS_PTRACE
> > >   capability. Should the program notify the user that the capability
> > >   is missing when the prctl call returns -EPREM, or does a mention in the
> > >   man page suffice?
> 
> Reporting the correct error message should be enough. 
> 
> 
> > > - I've currently licensed it under the EUPL v1.2, which is easier to
> > >   enforce in my jurisdiction than the GPL. It is GPL compatible so it
> > >   shouldn't be an issue, but if anybody has any remarks on this, please
> > >   let me know.
> 
> The util-linux package is GPL so personally I think it would be good to just
> make any new addition to that package also GPL. 
> 
> 
> > > 
> > > Thanks for taking the time!
> > > 
> > > Best regards,
> > > Thijs Raymakers
> > > 
> > > 
> > > Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> > > 
> > > ---
> > >  .gitignore                  |   1 +
> > >  bash-completion/coresched   |   0
> > >  configure.ac                |  12 +-
> > >  meson.build                 |  16 +-
> > >  meson_options.txt           |   2 +-
> > >  schedutils/Makemodule.am    |   8 +
> > >  schedutils/coresched.1.adoc |  16 ++
> > >  schedutils/coresched.c      | 340 ++++++++++++++++++++++++++++++++++++
> > >  8 files changed, 389 insertions(+), 6 deletions(-)
> > >  create mode 100644 bash-completion/coresched
> > >  create mode 100644 schedutils/coresched.1.adoc
> > >  create mode 100644 schedutils/coresched.c
> > > 
> > > diff --git a/.gitignore b/.gitignore
> > > index 6ecbfa7fe..316f3cdcc 100644
> > > --- a/.gitignore
> > > +++ b/.gitignore
> > > @@ -94,6 +94,7 @@ ylwrap
> > >  /colcrt
> > >  /colrm
> > >  /column
> > > +/coresched
> > >  /ctrlaltdel
> > >  /delpart
> > >  /dmesg
> > > diff --git a/bash-completion/coresched b/bash-completion/coresched
> > > new file mode 100644
> > > index 000000000..e69de29bb
> > > diff --git a/configure.ac b/configure.ac
> > > index ab7c98636..3a189a075 100644
> > > --- a/configure.ac
> > > +++ b/configure.ac
> > > @@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
> > >  AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
> > >  
> > >  # build_schedutils= is just configure-only variable to control
> > > -# ionice, taskset and chrt
> > > +# ionice, taskset, coresched and chrt
> > >  AC_ARG_ENABLE([schedutils],
> > > -  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
> > > +  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
> > >    [], [UL_DEFAULT_ENABLE([schedutils], [check])]
> > >  )
> > >  
> > > @@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
> > >  AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
> > >  
> > >  
> > > +UL_ENABLE_ALIAS([coresched], [schedutils])
> > > +UL_BUILD_INIT([coresched])
> > > +UL_REQUIRES_SYSCALL_CHECK([coresched],
> > > +	[UL_CHECK_SYSCALL([prctl])],
> > > +	[prctl])
> > > +AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
> > > +
> > > +
> > >  have_schedsetter=no
> > >  AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
> > >        [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
> > > diff --git a/meson.build b/meson.build
> > > index f7baab7a2..8244c43a9 100644
> > > --- a/meson.build
> > > +++ b/meson.build
> > > @@ -3107,13 +3107,23 @@ exe4 = executable(
> > >    install : opt,
> > >    build_by_default : opt)
> > >  
> > > +exe5 = executable(
> > > +  'coresched',
> > > +  'schedutils/coresched.c',
> > > +  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, exe2, exe3, exe4]
> > > +  exes += [exe, exe2, exe3, exe4, exe5]
> > >    manadocs += ['schedutils/chrt.1.adoc',
> > >                 'schedutils/ionice.1.adoc',
> > >                 'schedutils/taskset.1.adoc',
> > > -	       'schedutils/uclampset.1.adoc']
> > > -  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
> > > +	       'schedutils/uclampset.1.adoc',
> > > +         'schedutils/coresched.1.adoc']
> > > +  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
> > >  endif
> > >  
> > >  ############################################################
> > > diff --git a/meson_options.txt b/meson_options.txt
> > > index 7b8cf3f35..3405c1b73 100644
> > > --- a/meson_options.txt
> > > +++ b/meson_options.txt
> > > @@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
> > >  option('build-setterm', type : 'feature',
> > >         description : 'build setterm')
> > >  option('build-schedutils', type : 'feature',
> > > -       description : 'build chrt, ionice, taskset')
> > > +       description : 'build chrt, ionice, taskset, coresched')
> > >  option('build-wall', type : 'feature',
> > >         description : 'build wall')
> > >  option('build-write', type : 'feature',
> > > diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
> > > index 1040da85f..0cb655401 100644
> > > --- a/schedutils/Makemodule.am
> > > +++ b/schedutils/Makemodule.am
> > > @@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
> > >  uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
> > >  uclampset_LDADD = $(LDADD) libcommon.la
> > >  endif
> > > +
> > > +if BUILD_CORESCHED
> > > +usrbin_exec_PROGRAMS += coresched
> > > +MANPAGES += schedutils/coresched.1
> > > +dist_noinst_DATA += schedutils/coresched.1.adoc
> > > +coresched_SOURCES = schedutils/coresched.c
> > > +coresched_LDADD = $(LDADD) libcommon.la
> > > +endif
> > > diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
> > > new file mode 100644
> > > index 000000000..60a21cd01
> > > --- /dev/null
> > > +++ b/schedutils/coresched.1.adoc
> > > @@ -0,0 +1,16 @@
> > > +//po4a: entry man manual
> > > +////
> > > +coresched(1) manpage
> > > +////
> > > += coresched(1)
> > > +:doctype: manpage
> > > +:man manual: User Commands
> > > +:man source: util-linux {release-version}
> > > +:page-layout: base
> > > +:command: coresched
> > > +:colon: :
> > > +:copyright: ©
> > > +
> > > +== NAME
> > > +
> > > +coresched - manage core scheduling cookies for tasks
> > > diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> > > new file mode 100644
> > > index 000000000..4be8f9fda
> > > --- /dev/null
> > > +++ b/schedutils/coresched.c
> > > @@ -0,0 +1,340 @@
> > > +/**
> > > + * SPDX-License-Identifier: EUPL-1.2
> > > + *
> > > + * coresched.c - manage core scheduling cookies for tasks
> > > + *
> > > + * Copyright (C) 2024 Thijs Raymakers
> > > + * Licensed under the EUPL v1.2
> > > + */
> > > +
> > > +#include <getopt.h>
> > > +#include <stdbool.h>
> > > +#include <stdio.h>
> > > +#include <sys/prctl.h>
> > > +#include <sys/wait.h>
> > > +
> > > +#include "c.h"
> > > +#include "closestream.h"
> > > +#include "nls.h"
> > > +#include "strutils.h"
> > > +
> > > +typedef enum {
> > > +	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
> > > +	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
> > > +	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
> > > +} core_sched_type_t;
> > > +
> 
> Is this needed? You've made the names a little shorter I guess.
> Also some versions of prctl.h don't have the PR_SCHED_CORE_SCOPE* defines
> even when they have the base PR_SCHED_CORE macros.
> 
> 
> > > +typedef enum {
> > > +	SCHED_CORE_CMD_NONE = 0,
> > > +	SCHED_CORE_CMD_GET = 1,
> > > +	SCHED_CORE_CMD_CREATE = 2,
> > > +	SCHED_CORE_CMD_COPY = 4,
> > > +	SCHED_CORE_CMD_EXEC = 8,
> > > +} core_sched_cmd_t;
> > > +
> > > +struct args {
> > > +	pid_t from_pid;
> > > +	pid_t to_pid;
> > > +	core_sched_type_t type;
> > > +	core_sched_cmd_t cmd;
> > > +	int exec_argv_offset;
> > > +};
> > > +
> > > +unsigned long core_sched_get_cookie(struct args *args);
> > > +void core_sched_create_cookie(struct args *args);
> > > +void core_sched_pull_cookie(pid_t from);
> > > +void core_sched_push_cookie(pid_t to, core_sched_type_t type);
> > > +void core_sched_copy_cookie(struct args *args);
> > > +void core_sched_exec_with_cookie(struct args *args, char **argv);
> > > +
> > > +core_sched_type_t parse_core_sched_type(char *str);
> > > +bool verify_arguments(struct args *args);
> > > +void parse_arguments(int argc, char **argv, struct args *args);
> > > +
> > > +unsigned long core_sched_get_cookie(struct args *args)
> > > +{
> > > +	unsigned long cookie = 0;
> > > +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET,
> > > +				args->from_pid, SCHED_CORE_SCOPE_PID, &cookie);
> > > +	if (prctl_errno) {
> > > +		errx(-prctl_errno, "Failed to get cookie from PID %d",
> > > +		     args->from_pid);
> > > +	}
> 
> prctl_errno will be 0 on success or -1 on failure. This will report EPERM for
> every error. 
> 
> 
> > > +	return cookie;
> > > +}
> > > +
> > > +void core_sched_create_cookie(struct args *args)
> > > +{
> > > +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
> > > +				args->from_pid, args->type, 0);
> 
> We are creating a new cookie on from_pid, seems odd.
> 
> 
> > > +	if (prctl_errno) {
> > > +		errx(-prctl_errno, "Failed to create cookie for PID %d",
> > > +		     args->from_pid);
> > > +	}
> > > +}
> > > +
> > > +void core_sched_pull_cookie(pid_t from)
> > > +{
> > > +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> > > +				SCHED_CORE_SCOPE_PID, 0);
> > > +	if (prctl_errno) {
> > > +		errx(-prctl_errno, "Failed to pull cookie from PID %d", from);
> > > +	}
> > > +}
> > > +
> > > +void core_sched_push_cookie(pid_t to, core_sched_type_t type)
> > > +{
> > > +	int prctl_errno =
> > > +		prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0);
> > > +	if (prctl_errno) {
> > > +		errx(-prctl_errno, "Failed to push cookie to PID %d", to);
> > > +	}
> > > +}
> > > +
> > > +void core_sched_copy_cookie(struct args *args)
> > > +{
> > > +	core_sched_pull_cookie(args->from_pid);
> > > +	core_sched_push_cookie(args->to_pid, args->type);
> > > +}
> > > +
> > > +void core_sched_exec_with_cookie(struct args *args, char **argv)
> > > +{
> > > +	if (!args->exec_argv_offset) {
> > > +		errx(EINVAL, "when --exec is provided, a program name "
> > > +			     "has to be given.");
> > > +	}
> > > +
> > > +	// Move the argument list to the first argument of the program
> > > +	argv = &argv[args->exec_argv_offset];
> > > +
> > > +	pid_t pid = fork();
> > > +	if (pid == -1) {
> > > +		errx(errno, "Failed to spawn new process");
> > > +	}
> 
> Don't fork here.  Just exec to the new program, as the other wrapper
> programs like taskset do. 
> 
> 
> > > +
> > > +	if (!pid) {
> > > +		// If a source PID is provided, try to copy the cookie from
> > > +		// that PID. Otherwise, create a brand new cookie with the
> > > +		// provided type.
> > > +		if (args->from_pid) {
> > > +			core_sched_pull_cookie(args->from_pid);
> > > +		} else {
> > > +			args->from_pid = getpid();
> > > +			core_sched_create_cookie(args);
> > > +		}
> > > +		if (execvp(argv[0], argv)) {
> > > +			errexec(argv[0]);
> > > +		}
> > > +	} else {
> > > +		int status = 0;
> > > +		waitpid(pid, &status, 0);
> > > +		exit(status);
> > > +	}
> > > +}
> > > +
> > > +core_sched_type_t parse_core_sched_type(char *str)
> > > +{
> > > +	if (!strncmp(str, "pid\0", 4)) {
> > > +		return SCHED_CORE_SCOPE_PID;
> > > +	} else if (!strncmp(str, "tgid\0", 5)) {
> > > +		return SCHED_CORE_SCOPE_TGID;
> > > +	} else if (!strncmp(str, "pgid\0", 5)) {
> > > +		return SCHED_CORE_SCOPE_PGID;
> > > +	}
> > > +
> > > +	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
> > > +	     str);
> > > +	__builtin_unreachable();
> > > +}
> > > +
> > > +static void __attribute__((__noreturn__)) usage(void)
> > > +{
> > > +	fputs(USAGE_HEADER, stdout);
> > > +	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
> > > +	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
> > > +		program_invocation_short_name);
> > > +	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
> > > +		program_invocation_short_name);
> > > +	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
> > > +		program_invocation_short_name);
> 
> There are 4 different arguments that take a pid when there are really
> only at most 2 needed.
> 
> "Get" could just be the default with no arguments given.
> 
> 
> > > +
> > > +	fputs(USAGE_SEPARATOR, stdout);
> > > +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> > > +
> > > +	fputs(USAGE_FUNCTIONS, stdout);
> > > +	fputsln(_(" -g, --get <PID>         get the core scheduling cookie of a PID"),
> > > +		stdout);
> > > +	fputsln(_(" -n, --new <PID>         assign a new core scheduling cookie to PID"),
> > > +		stdout);
> > > +	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
> > > +		  "                           another PID, requires the --source and --dest option"),
> > > +		stdout);
> > > +	fputsln(_(" -e, --exec              execute a program with a new core scheduling\n"
> > > +		  "                           cookie."),
> > > +		stdout);
> > > +
> > > +	fputs(USAGE_OPTIONS, stdout);
> > > +	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
> > > +		stdout);
> > > +	fputsln(_(" -d, --dest <PID>        where to copy the core scheduling cookie to."),
> > > +		stdout);
> > > +	fputsln(_(" -t, --type              type of the destination PID, or the type of\n"
> > > +		  "                           the PID when a new core scheduling cookie\n"
> > > +		  "                           is created. Can be one of the following:\n"
> > > +		  "                           pid, tgid or pgid. Defaults to tgid."),
> > > +		stdout);
> > > +	fputs(USAGE_SEPARATOR, stdout);
> > > +	fprintf(stdout,
> > > +		USAGE_HELP_OPTIONS(
> > > +			25)); /* char offset to align option descriptions */
> > > +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> > > +	exit(EXIT_SUCCESS);
> > > +}
> > > +
> > > +bool verify_arguments(struct args *args)
> > > +{
> > > +	if (args->cmd == SCHED_CORE_CMD_NONE) {
> > > +		usage();
> > > +	}
> > > +
> > > +	// Check if the value of args->cmd is a power of 2
> > > +	// In that case, only a single function option was set.
> > > +	if (!(args->cmd && !(args->cmd & (args->cmd - 1)))) {
> > > +		errx(EINVAL, "Cannot do more than one function at a time.");
> > > +	}
> > > +
> > > +	if (args->from_pid < 0) {
> > > +		errx(EINVAL, "source PID cannot be negative");
> > > +	}
> > > +
> > > +	if (args->to_pid < 0) {
> > > +		errx(EINVAL, "destination PID cannot be negative");
> > > +	}
> > > +
> > > +	if (args->from_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> > > +		errx(EINVAL, "valid argument to --source is required");
> > > +	}
> > > +
> > > +	if (args->to_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> > > +		errx(EINVAL, "valid argument to --dest is required");
> > > +	}
> > > +
> > > +	if (args->from_pid == 0 && args->cmd != SCHED_CORE_CMD_EXEC) {
> > > +		errx(EINVAL, "PID cannot be zero");
> > > +	}
> > > +
> > > +	return true;
> > > +}
> > > +
> > > +void parse_arguments(int argc, char **argv, struct args *args)
> > > +{
> > > +	int c;
> > > +
> > > +	enum {
> > > +		OPT_GET = 'g',
> > > +		OPT_NEW = 'n',
> > > +		OPT_COPY = 'c',
> > > +		OPT_EXEC = 'e',
> > > +		OPT_SRC = 's',
> > > +		OPT_DEST = 'd',
> > > +		OPT_TYPE = 't',
> > > +		OPT_VERSION = 'V',
> > > +		OPT_HELP = 'h'
> > > +	};
> 
> This seems over-engineered.  You have to specificy the characters
> in the getopt_long call anyway so this just complicates the code for
> no reason it seems to me.
> 
> > > +
> > > +	static const struct option longopts[] = {
> > > +		{ "get", required_argument, NULL, OPT_GET },
> > > +		{ "new", required_argument, NULL, OPT_NEW },
> > > +		{ "copy", no_argument, NULL, OPT_COPY },
> > > +		{ "exec", no_argument, NULL, OPT_EXEC },
> > > +		{ "source", required_argument, NULL, OPT_SRC },
> > > +		{ "destination", required_argument, NULL, OPT_DEST },
> > > +		{ "type", required_argument, NULL, OPT_TYPE },
> > > +		{ "version", no_argument, NULL, OPT_VERSION },
> > > +		{ "help", no_argument, NULL, OPT_HELP },
> > > +		{ NULL, 0, NULL, 0 }
> > > +	};
> > > +
> > > +	while ((c = getopt_long(argc, argv, "g:n:ces:d:t:Vh", longopts,
> > > +				NULL)) != -1)
> > > +		switch (c) {
> > > +		case OPT_GET:
> > > +			args->cmd |= SCHED_CORE_CMD_GET;
> > > +			args->from_pid = strtos32_or_err(
> > > +				optarg, "Failed to parse PID for --get");
> > > +			break;
> > > +		case OPT_NEW:
> > > +			args->cmd |= SCHED_CORE_CMD_CREATE;
> > > +			args->from_pid = strtos32_or_err(
> > > +				optarg, "Failed to parse PID for --new");
> > > +			break;
> > > +		case OPT_COPY:
> > > +			args->cmd |= SCHED_CORE_CMD_COPY;
> > > +			break;
> > > +		case OPT_EXEC:
> > > +			args->cmd |= SCHED_CORE_CMD_EXEC;
> > > +			break;
> > > +		case OPT_SRC:
> > > +			args->from_pid = strtos32_or_err(
> > > +				optarg, "Failed to parse PID for --source");
> > > +			break;
> > > +		case OPT_DEST:
> > > +			args->to_pid = strtos32_or_err(
> > > +				optarg, "Failed to parse PID for --dest");
> > > +			break;
> > > +		case OPT_TYPE:
> > > +			args->type = parse_core_sched_type(optarg);
> > > +			break;
> > > +		case OPT_VERSION:
> > > +			print_version(EXIT_SUCCESS);
> > > +		case OPT_HELP:
> > > +			usage();
> > > +		default:
> > > +			errtryhelp(EXIT_FAILURE);
> > > +		}
> > > +
> > > +	if (argc > optind) {
> > > +		args->exec_argv_offset = optind;
> > > +	}
> > > +	verify_arguments(args);
> > > +}
> > > +
> > > +int main(int argc, char **argv)
> > > +{
> > > +	struct args arguments = { 0 };
> > > +	arguments.type = SCHED_CORE_SCOPE_TGID;
> > > +
> > > +	setlocale(LC_ALL, "");
> > > +	bindtextdomain(PACKAGE, LOCALEDIR);
> > > +	textdomain(PACKAGE);
> > > +	close_stdout_atexit();
> > > +
> > > +	parse_arguments(argc, argv, &arguments);
> > > +
> > > +	unsigned long cookie = 0;
> > > +	switch (arguments.cmd) {
> > > +	case SCHED_CORE_CMD_GET:
> > > +		cookie = core_sched_get_cookie(&arguments);
> > > +		if (cookie) {
> > > +			printf("core scheduling cookie of pid %d is 0x%lx\n",
> > > +			       arguments.from_pid, cookie);
> > > +		} else {
> > > +			printf("pid %d doesn't have a core scheduling cookie\n",
> > > +			       arguments.from_pid);
> > > +			exit(1);
> > > +		}
> > > +		break;
> > > +	case SCHED_CORE_CMD_CREATE:
> > > +		core_sched_create_cookie(&arguments);
> > > +		break;
> > > +	case SCHED_CORE_CMD_COPY:
> > > +		core_sched_copy_cookie(&arguments);
> > > +		break;
> > > +	case SCHED_CORE_CMD_EXEC:
> > > +		core_sched_exec_with_cookie(&arguments, argv);
> > > +		break;
> > > +	default:
> > > +		usage();
> > > +		exit(1);
> > > +	}
> > > +}
> > > -- 
> > > 2.44.0
> > > 
> > >
> > 
> > -- 
> >  Karel Zak  <kzak@redhat.com>
> >  http://karelzak.blogspot.com
> > 
> 
> -- 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 14:41   ` Phil Auld
  2024-03-26 14:45     ` Phil Auld
@ 2024-03-26 17:49     ` Thijs Raymakers
  2024-03-26 18:13       ` Phil Auld
  2024-03-26 19:09       ` [PATCH 0/1] " Phil Auld
  2024-03-26 17:49     ` [PATCH 1/1] " Thijs Raymakers
  2 siblings, 2 replies; 42+ messages in thread
From: Thijs Raymakers @ 2024-03-26 17:49 UTC (permalink / raw)
  To: pauld; +Cc: kzak, util-linux, Thijs Raymakers

Hi Phil,

Thank you for your comments. I've attached an interdiff to this email
with all the changes that I've made in response.

Phil Auld wrote on 26-03-2024 at 15:41:
> The exec should not fork first.

You're right, this is not necessary. I've removed it.

> And the error handling I think is just not right. I believe prctl()
> returns -1 on error and sets errno. This code will report EPERM for all
> prtcl errors.  While EPERM is likely I don't think it's the only error.

Good catch! I assumed that the return values of the `prctl()` call
matched the return values of `sched_core_share_pid` in
`kernel/sched/core_sched.c`. I've updated the error handling code to
just use errno instead.

> On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
>> - How should the program behave if the prctl core scheduling API is not
>>   available? It has been in Linus' tree since november 2021
>>   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
>>   disabled with CONFIG_SCHED_CORE=n
> 
> This one is tricky. If I recall the error you get is ambiguous in this case
> (EINVAL, which can be returned for other reasons). It would be nice to be
> able to report that specifically. 

In general, `prctl` does indeed return EINVAL if the operation is not
recognized, or not supported on the system. The `PR_SCHED_CORE`
operation itself only returns EINVAL if it is called with
  - an invalid operation
  - an invalid type
  - a negative PID
  - an invalid cookie store address (for PR_SCHED_CORE_GET)
Assuming that all these cases are prevented by the util, we could
interpret a EINVAL as a sign that PR_SCHED_CORE is not supported on
the system.

>> - I've currently licensed it under the EUPL v1.2, which is easier to
>>   enforce in my jurisdiction than the GPL. It is GPL compatible so it
>>   shouldn't be an issue, but if anybody has any remarks on this, please
>>   let me know.
> 
> The util-linux package is GPL so personally I think it would be good to just
> make any new addition to that package also GPL. 

As far as I understand it, util-linux is a collection of different works
with different licenses (see the README.licensing file). Not every
program included in util-linux has to be GPL.

>> +typedef enum {
>> +	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
>> +	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
>> +	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
>> +} core_sched_type_t;
>> +
> 
> Is this needed? You've made the names a little shorter I guess.
> Also some versions of prctl.h don't have the PR_SCHED_CORE_SCOPE* defines
> even when they have the base PR_SCHED_CORE macros.

I've changed to to just use the PR_SCHED_CORE_SCOPE* definitions
instead.

>> +void core_sched_create_cookie(struct args *args)
>> +{
>> +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
>> +				args->from_pid, args->type, 0);
> 
> We are creating a new cookie on from_pid, seems odd.

I agree, I changed to to `args->to_pid` because that is more logical.

>> +	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
>> +	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
>> +		program_invocation_short_name);
>> +	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
>> +		program_invocation_short_name);
>> +	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
>> +		program_invocation_short_name);
> 
> There are 4 different arguments that take a pid when there are really
> only at most 2 needed.

I chose this to keep the amount of options that you have to provide as
small possible. I thought that `coresched -n 123` would be easier to use
than `coresched -n -p 123`. I guess the amount of different arguments
that can take PIDs can be reduced to two, but it would make the commands
a bit longer.

> "Get" could just be the default with no arguments given.

I've removed the explicit --exec and made it the default when a program
name is provided. If no program name is provided, it defaults to --get.

>> +	enum {
>> +		OPT_GET = 'g',
>> +		OPT_NEW = 'n',
>> +		OPT_COPY = 'c',
>> +		OPT_EXEC = 'e',
>> +		OPT_SRC = 's',
>> +		OPT_DEST = 'd',
>> +		OPT_TYPE = 't',
>> +		OPT_VERSION = 'V',
>> +		OPT_HELP = 'h'
>> +	};
> 
> This seems over-engineered.  You have to specificy the characters
> in the getopt_long call anyway so this just complicates the code for
> no reason it seems to me.

Fair point, I've removed this. I initially added this to make it easier
to experiment a bit with different values for the options, but that is
no longer necessary.

Thanks!

- Thijs

Thijs Raymakers (1):
  coresched: Manage core scheduling cookies for tasks

 .gitignore                  |   1 +
 bash-completion/coresched   |   0
 configure.ac                |  12 +-
 meson.build                 |  16 +-
 meson_options.txt           |   2 +-
 schedutils/Makemodule.am    |   8 +
 schedutils/coresched.1.adoc |  16 ++
 schedutils/coresched.c      | 353 ++++++++++++++++++++++++++++++++++++
 8 files changed, 402 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c

Interdiff:
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
index 4be8f9fda..17d775f2d 100644
--- a/schedutils/coresched.c
+++ b/schedutils/coresched.c
@@ -18,18 +18,24 @@
 #include "nls.h"
 #include "strutils.h"
 
+// These definitions might not be defined, even if the
+// prctl interface accepts them.
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int core_sched_type_t;
 typedef enum {
-	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
-	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
-	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
-} core_sched_type_t;
-
-typedef enum {
-	SCHED_CORE_CMD_NONE = 0,
+	SCHED_CORE_CMD_EXEC = 0,
 	SCHED_CORE_CMD_GET = 1,
 	SCHED_CORE_CMD_CREATE = 2,
 	SCHED_CORE_CMD_COPY = 4,
-	SCHED_CORE_CMD_EXEC = 8,
 } core_sched_cmd_t;
 
 struct args {
@@ -50,44 +56,40 @@ void core_sched_exec_with_cookie(struct args *args, char **argv);
 core_sched_type_t parse_core_sched_type(char *str);
 bool verify_arguments(struct args *args);
 void parse_arguments(int argc, char **argv, struct args *args);
+void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
+static void __attribute__((__noreturn__)) usage(void);
 
 unsigned long core_sched_get_cookie(struct args *args)
 {
 	unsigned long cookie = 0;
-	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET,
-				args->from_pid, SCHED_CORE_SCOPE_PID, &cookie);
-	if (prctl_errno) {
-		errx(-prctl_errno, "Failed to get cookie from PID %d",
-		     args->from_pid);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, args->from_pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
+		err(errno, "Failed to get cookie from PID %d", args->from_pid);
 	}
 	return cookie;
 }
 
 void core_sched_create_cookie(struct args *args)
 {
-	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
-				args->from_pid, args->type, 0);
-	if (prctl_errno) {
-		errx(-prctl_errno, "Failed to create cookie for PID %d",
-		     args->from_pid);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, args->to_pid, args->type,
+		  0)) {
+		err(errno, "Failed to create cookie for PID %d",
+		    args->from_pid);
 	}
 }
 
 void core_sched_pull_cookie(pid_t from)
 {
-	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
-				SCHED_CORE_SCOPE_PID, 0);
-	if (prctl_errno) {
-		errx(-prctl_errno, "Failed to pull cookie from PID %d", from);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
+		err(errno, "Failed to pull cookie from PID %d", from);
 	}
 }
 
 void core_sched_push_cookie(pid_t to, core_sched_type_t type)
 {
-	int prctl_errno =
-		prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0);
-	if (prctl_errno) {
-		errx(-prctl_errno, "Failed to push cookie to PID %d", to);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
+		err(errno, "Failed to push cookie to PID %d", to);
 	}
 }
 
@@ -100,46 +102,35 @@ void core_sched_copy_cookie(struct args *args)
 void core_sched_exec_with_cookie(struct args *args, char **argv)
 {
 	if (!args->exec_argv_offset) {
-		errx(EINVAL, "when --exec is provided, a program name "
-			     "has to be given.");
+		usage();
 	}
 
 	// Move the argument list to the first argument of the program
 	argv = &argv[args->exec_argv_offset];
 
-	pid_t pid = fork();
-	if (pid == -1) {
-		errx(errno, "Failed to spawn new process");
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->from_pid) {
+		core_sched_pull_cookie(args->from_pid);
+	} else {
+		args->to_pid = getpid();
+		core_sched_create_cookie(args);
 	}
 
-	if (!pid) {
-		// If a source PID is provided, try to copy the cookie from
-		// that PID. Otherwise, create a brand new cookie with the
-		// provided type.
-		if (args->from_pid) {
-			core_sched_pull_cookie(args->from_pid);
-		} else {
-			args->from_pid = getpid();
-			core_sched_create_cookie(args);
-		}
-		if (execvp(argv[0], argv)) {
-			errexec(argv[0]);
-		}
-	} else {
-		int status = 0;
-		waitpid(pid, &status, 0);
-		exit(status);
+	if (execvp(argv[0], argv)) {
+		errexec(argv[0]);
 	}
 }
 
 core_sched_type_t parse_core_sched_type(char *str)
 {
 	if (!strncmp(str, "pid\0", 4)) {
-		return SCHED_CORE_SCOPE_PID;
+		return PR_SCHED_CORE_SCOPE_THREAD;
 	} else if (!strncmp(str, "tgid\0", 5)) {
-		return SCHED_CORE_SCOPE_TGID;
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
 	} else if (!strncmp(str, "pgid\0", 5)) {
-		return SCHED_CORE_SCOPE_PGID;
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
 	}
 
 	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
@@ -155,7 +146,7 @@ static void __attribute__((__noreturn__)) usage(void)
 		program_invocation_short_name);
 	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
 		program_invocation_short_name);
-	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
+	fprintf(stdout, _(" %s [-s <PID>] -- PROGRAM ARGS... \n"),
 		program_invocation_short_name);
 
 	fputs(USAGE_SEPARATOR, stdout);
@@ -169,9 +160,6 @@ static void __attribute__((__noreturn__)) usage(void)
 	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
 		  "                           another PID, requires the --source and --dest option"),
 		stdout);
-	fputsln(_(" -e, --exec              execute a program with a new core scheduling\n"
-		  "                           cookie."),
-		stdout);
 
 	fputs(USAGE_OPTIONS, stdout);
 	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
@@ -193,116 +181,141 @@ static void __attribute__((__noreturn__)) usage(void)
 
 bool verify_arguments(struct args *args)
 {
-	if (args->cmd == SCHED_CORE_CMD_NONE) {
-		usage();
-	}
-
 	// Check if the value of args->cmd is a power of 2
 	// In that case, only a single function option was set.
-	if (!(args->cmd && !(args->cmd & (args->cmd - 1)))) {
-		errx(EINVAL, "Cannot do more than one function at a time.");
-	}
-
-	if (args->from_pid < 0) {
-		errx(EINVAL, "source PID cannot be negative");
+	if (args->cmd & (args->cmd - 1)) {
+		errx(EINVAL,
+		     "Cannot do more than one function at a time. See %s --help",
+		     program_invocation_short_name);
 	}
 
-	if (args->to_pid < 0) {
-		errx(EINVAL, "destination PID cannot be negative");
-	}
-
-	if (args->from_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
-		errx(EINVAL, "valid argument to --source is required");
-	}
-
-	if (args->to_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
-		errx(EINVAL, "valid argument to --dest is required");
+	switch (args->cmd) {
+	case SCHED_CORE_CMD_GET:
+		if (args->to_pid) {
+			errx(EINVAL,
+			     "Cannot use -d/--dest with this -g/--get. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
+	case SCHED_CORE_CMD_CREATE:
+		if (args->from_pid) {
+			errx(EINVAL,
+			     "Cannot use -s/--source with this -n/--new. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (!args->from_pid) {
+			errx(EINVAL,
+			     "-s/--source PID is required when copying");
+		}
+		if (!args->to_pid) {
+			errx(EINVAL, "-d/--dest PID is required when copying");
+		}
+		break;
+	case SCHED_CORE_CMD_EXEC:
+		if (args->to_pid) {
+			errx(EINVAL,
+			     "Cannot use -d/--dest when spawning a program. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
 	}
+	return true;
+}
 
-	if (args->from_pid == 0 && args->cmd != SCHED_CORE_CMD_EXEC) {
-		errx(EINVAL, "PID cannot be zero");
+void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg)
+{
+	if (*dest) {
+		errx(EINVAL, "Ambigious usage: %s", err_msg);
+	} else {
+		*dest = src;
 	}
-
-	return true;
 }
 
+static const char *ERR_MSG_MULTIPLE_SOURCE_PIDS =
+	"Multiple source PIDs defined";
 void parse_arguments(int argc, char **argv, struct args *args)
 {
 	int c;
-
-	enum {
-		OPT_GET = 'g',
-		OPT_NEW = 'n',
-		OPT_COPY = 'c',
-		OPT_EXEC = 'e',
-		OPT_SRC = 's',
-		OPT_DEST = 'd',
-		OPT_TYPE = 't',
-		OPT_VERSION = 'V',
-		OPT_HELP = 'h'
-	};
+	pid_t tmp;
 
 	static const struct option longopts[] = {
-		{ "get", required_argument, NULL, OPT_GET },
-		{ "new", required_argument, NULL, OPT_NEW },
-		{ "copy", no_argument, NULL, OPT_COPY },
-		{ "exec", no_argument, NULL, OPT_EXEC },
-		{ "source", required_argument, NULL, OPT_SRC },
-		{ "destination", required_argument, NULL, OPT_DEST },
-		{ "type", required_argument, NULL, OPT_TYPE },
-		{ "version", no_argument, NULL, OPT_VERSION },
-		{ "help", no_argument, NULL, OPT_HELP },
+		{ "get", required_argument, NULL, 'g' },
+		{ "new", required_argument, NULL, 'n' },
+		{ "copy", no_argument, NULL, 'c' },
+		{ "source", required_argument, NULL, 's' },
+		{ "destination", required_argument, NULL, 'd' },
+		{ "type", required_argument, NULL, 't' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
 		{ NULL, 0, NULL, 0 }
 	};
 
-	while ((c = getopt_long(argc, argv, "g:n:ces:d:t:Vh", longopts,
-				NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "g:n:cs:d:t:Vh", longopts, NULL)) !=
+	       -1)
 		switch (c) {
-		case OPT_GET:
+		case 'g':
 			args->cmd |= SCHED_CORE_CMD_GET;
-			args->from_pid = strtos32_or_err(
-				optarg, "Failed to parse PID for --get");
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -g/--get");
+			set_pid_or_err(&args->from_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
 			break;
-		case OPT_NEW:
+		case 'n':
 			args->cmd |= SCHED_CORE_CMD_CREATE;
-			args->from_pid = strtos32_or_err(
-				optarg, "Failed to parse PID for --new");
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -n/--new");
+			set_pid_or_err(&args->to_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
 			break;
-		case OPT_COPY:
+		case 'c':
 			args->cmd |= SCHED_CORE_CMD_COPY;
 			break;
-		case OPT_EXEC:
-			args->cmd |= SCHED_CORE_CMD_EXEC;
-			break;
-		case OPT_SRC:
-			args->from_pid = strtos32_or_err(
-				optarg, "Failed to parse PID for --source");
+		case 's':
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -s/--source");
+			set_pid_or_err(&args->from_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
 			break;
-		case OPT_DEST:
-			args->to_pid = strtos32_or_err(
-				optarg, "Failed to parse PID for --dest");
+		case 'd':
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -d/--dest");
+			set_pid_or_err(&args->to_pid, tmp,
+				       "Multiple destination PIDs defined");
 			break;
-		case OPT_TYPE:
+		case 't':
 			args->type = parse_core_sched_type(optarg);
 			break;
-		case OPT_VERSION:
+		case 'V':
 			print_version(EXIT_SUCCESS);
-		case OPT_HELP:
+		case 'h':
 			usage();
 		default:
 			errtryhelp(EXIT_FAILURE);
 		}
 
 	if (argc > optind) {
-		args->exec_argv_offset = optind;
+		if (args->cmd == SCHED_CORE_CMD_EXEC) {
+			args->exec_argv_offset = optind;
+		} else {
+			// -g, -n or -c AND a program to run is provided
+			errx(EINVAL, "bad usage, see %s --help",
+			     program_invocation_short_name);
+		}
+	} else if (argc == optind && args->from_pid) {
+		// Neither a function (-g, -n, or -c), nor a program to
+		// run is given
+		args->cmd = SCHED_CORE_CMD_GET;
 	}
+
 	verify_arguments(args);
 }
 
 int main(int argc, char **argv)
 {
 	struct args arguments = { 0 };
-	arguments.type = SCHED_CORE_SCOPE_TGID;
+	arguments.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
 
 	setlocale(LC_ALL, "");
 	bindtextdomain(PACKAGE, LOCALEDIR);
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PATCH 1/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 14:41   ` Phil Auld
  2024-03-26 14:45     ` Phil Auld
  2024-03-26 17:49     ` [PATCH 0/1] " Thijs Raymakers
@ 2024-03-26 17:49     ` Thijs Raymakers
  2 siblings, 0 replies; 42+ messages in thread
From: Thijs Raymakers @ 2024-03-26 17:49 UTC (permalink / raw)
  To: pauld; +Cc: kzak, util-linux, Thijs Raymakers

Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
---
 .gitignore                  |   1 +
 bash-completion/coresched   |   0
 configure.ac                |  12 +-
 meson.build                 |  16 +-
 meson_options.txt           |   2 +-
 schedutils/Makemodule.am    |   8 +
 schedutils/coresched.1.adoc |  16 ++
 schedutils/coresched.c      | 353 ++++++++++++++++++++++++++++++++++++
 8 files changed, 402 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 9600ce49f..9a2c04e8f 100644
--- a/meson.build
+++ b/meson.build
@@ -3111,13 +3111,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+	       'schedutils/uclampset.1.adoc',
+         'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..60a21cd01
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,16 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..17d775f2d
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,353 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+
+// These definitions might not be defined, even if the
+// prctl interface accepts them.
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int core_sched_type_t;
+typedef enum {
+	SCHED_CORE_CMD_EXEC = 0,
+	SCHED_CORE_CMD_GET = 1,
+	SCHED_CORE_CMD_CREATE = 2,
+	SCHED_CORE_CMD_COPY = 4,
+} core_sched_cmd_t;
+
+struct args {
+	pid_t from_pid;
+	pid_t to_pid;
+	core_sched_type_t type;
+	core_sched_cmd_t cmd;
+	int exec_argv_offset;
+};
+
+unsigned long core_sched_get_cookie(struct args *args);
+void core_sched_create_cookie(struct args *args);
+void core_sched_pull_cookie(pid_t from);
+void core_sched_push_cookie(pid_t to, core_sched_type_t type);
+void core_sched_copy_cookie(struct args *args);
+void core_sched_exec_with_cookie(struct args *args, char **argv);
+
+core_sched_type_t parse_core_sched_type(char *str);
+bool verify_arguments(struct args *args);
+void parse_arguments(int argc, char **argv, struct args *args);
+void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
+static void __attribute__((__noreturn__)) usage(void);
+
+unsigned long core_sched_get_cookie(struct args *args)
+{
+	unsigned long cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, args->from_pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
+		err(errno, "Failed to get cookie from PID %d", args->from_pid);
+	}
+	return cookie;
+}
+
+void core_sched_create_cookie(struct args *args)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, args->to_pid, args->type,
+		  0)) {
+		err(errno, "Failed to create cookie for PID %d",
+		    args->from_pid);
+	}
+}
+
+void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
+		err(errno, "Failed to pull cookie from PID %d", from);
+	}
+}
+
+void core_sched_push_cookie(pid_t to, core_sched_type_t type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
+		err(errno, "Failed to push cookie to PID %d", to);
+	}
+}
+
+void core_sched_copy_cookie(struct args *args)
+{
+	core_sched_pull_cookie(args->from_pid);
+	core_sched_push_cookie(args->to_pid, args->type);
+}
+
+void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	if (!args->exec_argv_offset) {
+		usage();
+	}
+
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->from_pid) {
+		core_sched_pull_cookie(args->from_pid);
+	} else {
+		args->to_pid = getpid();
+		core_sched_create_cookie(args);
+	}
+
+	if (execvp(argv[0], argv)) {
+		errexec(argv[0]);
+	}
+}
+
+core_sched_type_t parse_core_sched_type(char *str)
+{
+	if (!strncmp(str, "pid\0", 4)) {
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	} else if (!strncmp(str, "tgid\0", 5)) {
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	} else if (!strncmp(str, "pgid\0", 5)) {
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+	}
+
+	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
+	     str);
+	__builtin_unreachable();
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
+	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s [-s <PID>] -- PROGRAM ARGS... \n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" -g, --get <PID>         get the core scheduling cookie of a PID"),
+		stdout);
+	fputsln(_(" -n, --new <PID>         assign a new core scheduling cookie to PID"),
+		stdout);
+	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
+		  "                           another PID, requires the --source and --dest option"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
+		stdout);
+	fputsln(_(" -d, --dest <PID>        where to copy the core scheduling cookie to."),
+		stdout);
+	fputsln(_(" -t, --type              type of the destination PID, or the type of\n"
+		  "                           the PID when a new core scheduling cookie\n"
+		  "                           is created. Can be one of the following:\n"
+		  "                           pid, tgid or pgid. Defaults to tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fprintf(stdout,
+		USAGE_HELP_OPTIONS(
+			25)); /* char offset to align option descriptions */
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+bool verify_arguments(struct args *args)
+{
+	// Check if the value of args->cmd is a power of 2
+	// In that case, only a single function option was set.
+	if (args->cmd & (args->cmd - 1)) {
+		errx(EINVAL,
+		     "Cannot do more than one function at a time. See %s --help",
+		     program_invocation_short_name);
+	}
+
+	switch (args->cmd) {
+	case SCHED_CORE_CMD_GET:
+		if (args->to_pid) {
+			errx(EINVAL,
+			     "Cannot use -d/--dest with this -g/--get. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
+	case SCHED_CORE_CMD_CREATE:
+		if (args->from_pid) {
+			errx(EINVAL,
+			     "Cannot use -s/--source with this -n/--new. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (!args->from_pid) {
+			errx(EINVAL,
+			     "-s/--source PID is required when copying");
+		}
+		if (!args->to_pid) {
+			errx(EINVAL, "-d/--dest PID is required when copying");
+		}
+		break;
+	case SCHED_CORE_CMD_EXEC:
+		if (args->to_pid) {
+			errx(EINVAL,
+			     "Cannot use -d/--dest when spawning a program. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
+	}
+	return true;
+}
+
+void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg)
+{
+	if (*dest) {
+		errx(EINVAL, "Ambigious usage: %s", err_msg);
+	} else {
+		*dest = src;
+	}
+}
+
+static const char *ERR_MSG_MULTIPLE_SOURCE_PIDS =
+	"Multiple source PIDs defined";
+void parse_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+	pid_t tmp;
+
+	static const struct option longopts[] = {
+		{ "get", required_argument, NULL, 'g' },
+		{ "new", required_argument, NULL, 'n' },
+		{ "copy", no_argument, NULL, 'c' },
+		{ "source", required_argument, NULL, 's' },
+		{ "destination", required_argument, NULL, 'd' },
+		{ "type", required_argument, NULL, 't' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "g:n:cs:d:t:Vh", longopts, NULL)) !=
+	       -1)
+		switch (c) {
+		case 'g':
+			args->cmd |= SCHED_CORE_CMD_GET;
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -g/--get");
+			set_pid_or_err(&args->from_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
+			break;
+		case 'n':
+			args->cmd |= SCHED_CORE_CMD_CREATE;
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -n/--new");
+			set_pid_or_err(&args->to_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
+			break;
+		case 'c':
+			args->cmd |= SCHED_CORE_CMD_COPY;
+			break;
+		case 's':
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -s/--source");
+			set_pid_or_err(&args->from_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
+			break;
+		case 'd':
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -d/--dest");
+			set_pid_or_err(&args->to_pid, tmp,
+				       "Multiple destination PIDs defined");
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+
+	if (argc > optind) {
+		if (args->cmd == SCHED_CORE_CMD_EXEC) {
+			args->exec_argv_offset = optind;
+		} else {
+			// -g, -n or -c AND a program to run is provided
+			errx(EINVAL, "bad usage, see %s --help",
+			     program_invocation_short_name);
+		}
+	} else if (argc == optind && args->from_pid) {
+		// Neither a function (-g, -n, or -c), nor a program to
+		// run is given
+		args->cmd = SCHED_CORE_CMD_GET;
+	}
+
+	verify_arguments(args);
+}
+
+int main(int argc, char **argv)
+{
+	struct args arguments = { 0 };
+	arguments.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_arguments(argc, argv, &arguments);
+
+	unsigned long cookie = 0;
+	switch (arguments.cmd) {
+	case SCHED_CORE_CMD_GET:
+		cookie = core_sched_get_cookie(&arguments);
+		if (cookie) {
+			printf("core scheduling cookie of pid %d is 0x%lx\n",
+			       arguments.from_pid, cookie);
+		} else {
+			printf("pid %d doesn't have a core scheduling cookie\n",
+			       arguments.from_pid);
+			exit(1);
+		}
+		break;
+	case SCHED_CORE_CMD_CREATE:
+		core_sched_create_cookie(&arguments);
+		break;
+	case SCHED_CORE_CMD_COPY:
+		core_sched_copy_cookie(&arguments);
+		break;
+	case SCHED_CORE_CMD_EXEC:
+		core_sched_exec_with_cookie(&arguments, argv);
+		break;
+	default:
+		usage();
+		exit(1);
+	}
+}
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 17:49     ` [PATCH 0/1] " Thijs Raymakers
@ 2024-03-26 18:13       ` Phil Auld
  2024-03-27 12:43         ` [PATCH v2 " Thijs Raymakers
  2024-03-27 12:43         ` [PATCH v2 1/1] " Thijs Raymakers
  2024-03-26 19:09       ` [PATCH 0/1] " Phil Auld
  1 sibling, 2 replies; 42+ messages in thread
From: Phil Auld @ 2024-03-26 18:13 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux


Hi Thijs,

On Tue, Mar 26, 2024 at 06:49:07PM +0100 Thijs Raymakers wrote:
> Hi Phil,
> 
> Thank you for your comments. I've attached an interdiff to this email
> with all the changes that I've made in response.

Fwiw, I've updated my version with the ability to handle your pull/push
use case if you want to give that a try and see if it works for you.

https://github.com/util-linux/util-linux/pull/2839

I still find it a little closer to taskset and thus maybe more
intuitive for people. But I'll try to give your version a try on
my setup and see if my fingers/muscle memory can do it :)

> 
> Phil Auld wrote on 26-03-2024 at 15:41:
> > The exec should not fork first.
> 
> You're right, this is not necessary. I've removed it.
> 
> > And the error handling I think is just not right. I believe prctl()
> > returns -1 on error and sets errno. This code will report EPERM for all
> > prtcl errors.  While EPERM is likely I don't think it's the only error.
> 
> Good catch! I assumed that the return values of the `prctl()` call
> matched the return values of `sched_core_share_pid` in
> `kernel/sched/core_sched.c`. I've updated the error handling code to
> just use errno instead.
> 
> > On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
> >> - How should the program behave if the prctl core scheduling API is not
> >>   available? It has been in Linus' tree since november 2021
> >>   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
> >>   disabled with CONFIG_SCHED_CORE=n
> > 
> > This one is tricky. If I recall the error you get is ambiguous in this case
> > (EINVAL, which can be returned for other reasons). It would be nice to be
> > able to report that specifically. 
> 
> In general, `prctl` does indeed return EINVAL if the operation is not
> recognized, or not supported on the system. The `PR_SCHED_CORE`
> operation itself only returns EINVAL if it is called with
>   - an invalid operation
>   - an invalid type
>   - a negative PID
>   - an invalid cookie store address (for PR_SCHED_CORE_GET)
> Assuming that all these cases are prevented by the util, we could
> interpret a EINVAL as a sign that PR_SCHED_CORE is not supported on
> the system.
>

Fair enough. Could say something like "got EINVAL. Does your kernel
support CONFIG_SCHED_CORE?". 


> >> - I've currently licensed it under the EUPL v1.2, which is easier to
> >>   enforce in my jurisdiction than the GPL. It is GPL compatible so it
> >>   shouldn't be an issue, but if anybody has any remarks on this, please
> >>   let me know.
> > 
> > The util-linux package is GPL so personally I think it would be good to just
> > make any new addition to that package also GPL. 
> 
> As far as I understand it, util-linux is a collection of different works
> with different licenses (see the README.licensing file). Not every
> program included in util-linux has to be GPL.
> 
> >> +typedef enum {
> >> +	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
> >> +	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
> >> +	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
> >> +} core_sched_type_t;
> >> +
> > 
> > Is this needed? You've made the names a little shorter I guess.
> > Also some versions of prctl.h don't have the PR_SCHED_CORE_SCOPE* defines
> > even when they have the base PR_SCHED_CORE macros.
> 
> I've changed to to just use the PR_SCHED_CORE_SCOPE* definitions
> instead.
> 
> >> +void core_sched_create_cookie(struct args *args)
> >> +{
> >> +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
> >> +				args->from_pid, args->type, 0);
> > 
> > We are creating a new cookie on from_pid, seems odd.
> 
> I agree, I changed to to `args->to_pid` because that is more logical.
> 
> >> +	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
> >> +	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
> >> +		program_invocation_short_name);
> >> +	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
> >> +		program_invocation_short_name);
> >> +	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
> >> +		program_invocation_short_name);
> > 
> > There are 4 different arguments that take a pid when there are really
> > only at most 2 needed.
> 
> I chose this to keep the amount of options that you have to provide as
> small possible. I thought that `coresched -n 123` would be easier to use
> than `coresched -n -p 123`. I guess the amount of different arguments
> that can take PIDs can be reduced to two, but it would make the commands
> a bit longer.
> 
> > "Get" could just be the default with no arguments given.
> 
> I've removed the explicit --exec and made it the default when a program
> name is provided. If no program name is provided, it defaults to --get.
> 
> >> +	enum {
> >> +		OPT_GET = 'g',
> >> +		OPT_NEW = 'n',
> >> +		OPT_COPY = 'c',
> >> +		OPT_EXEC = 'e',
> >> +		OPT_SRC = 's',
> >> +		OPT_DEST = 'd',
> >> +		OPT_TYPE = 't',
> >> +		OPT_VERSION = 'V',
> >> +		OPT_HELP = 'h'
> >> +	};
> > 
> > This seems over-engineered.  You have to specificy the characters
> > in the getopt_long call anyway so this just complicates the code for
> > no reason it seems to me.
> 
> Fair point, I've removed this. I initially added this to make it easier
> to experiment a bit with different values for the options, but that is
> no longer necessary.
>

I do see that in some other util-linux programs but it doesn't make sense
there either, to me :)

There is also
       err_exclusive_options(c, longopts, excl, excl_st);

in the optuils.h code which can handle the tests for mutually exclusive
arguments.

Thomas pointed me to that. It works nicely and can remove some of the extra
checks (once you get it setup). And the error then looks the same as other
util-linux progs. 


You might consider "errtryhelp(EXIT_FAILURE);"  in usage failures. A lot of
the progs (incl taskset which is my model for this) do that. Then you only
do the full usage when given -h/--help.



Cheers,
Phil



> Thanks!
> 
> - Thijs
> 
> Thijs Raymakers (1):
>   coresched: Manage core scheduling cookies for tasks
> 
>  .gitignore                  |   1 +
>  bash-completion/coresched   |   0
>  configure.ac                |  12 +-
>  meson.build                 |  16 +-
>  meson_options.txt           |   2 +-
>  schedutils/Makemodule.am    |   8 +
>  schedutils/coresched.1.adoc |  16 ++
>  schedutils/coresched.c      | 353 ++++++++++++++++++++++++++++++++++++
>  8 files changed, 402 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
> 
> Interdiff:
> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> index 4be8f9fda..17d775f2d 100644
> --- a/schedutils/coresched.c
> +++ b/schedutils/coresched.c
> @@ -18,18 +18,24 @@
>  #include "nls.h"
>  #include "strutils.h"
>  
> +// These definitions might not be defined, even if the
> +// prctl interface accepts them.
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int core_sched_type_t;
>  typedef enum {
> -	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
> -	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
> -	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
> -} core_sched_type_t;
> -
> -typedef enum {
> -	SCHED_CORE_CMD_NONE = 0,
> +	SCHED_CORE_CMD_EXEC = 0,
>  	SCHED_CORE_CMD_GET = 1,
>  	SCHED_CORE_CMD_CREATE = 2,
>  	SCHED_CORE_CMD_COPY = 4,
> -	SCHED_CORE_CMD_EXEC = 8,
>  } core_sched_cmd_t;
>  
>  struct args {
> @@ -50,44 +56,40 @@ void core_sched_exec_with_cookie(struct args *args, char **argv);
>  core_sched_type_t parse_core_sched_type(char *str);
>  bool verify_arguments(struct args *args);
>  void parse_arguments(int argc, char **argv, struct args *args);
> +void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
> +static void __attribute__((__noreturn__)) usage(void);
>  
>  unsigned long core_sched_get_cookie(struct args *args)
>  {
>  	unsigned long cookie = 0;
> -	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET,
> -				args->from_pid, SCHED_CORE_SCOPE_PID, &cookie);
> -	if (prctl_errno) {
> -		errx(-prctl_errno, "Failed to get cookie from PID %d",
> -		     args->from_pid);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, args->from_pid,
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> +		err(errno, "Failed to get cookie from PID %d", args->from_pid);
>  	}
>  	return cookie;
>  }
>  
>  void core_sched_create_cookie(struct args *args)
>  {
> -	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
> -				args->from_pid, args->type, 0);
> -	if (prctl_errno) {
> -		errx(-prctl_errno, "Failed to create cookie for PID %d",
> -		     args->from_pid);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, args->to_pid, args->type,
> +		  0)) {
> +		err(errno, "Failed to create cookie for PID %d",
> +		    args->from_pid);
>  	}
>  }
>  
>  void core_sched_pull_cookie(pid_t from)
>  {
> -	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> -				SCHED_CORE_SCOPE_PID, 0);
> -	if (prctl_errno) {
> -		errx(-prctl_errno, "Failed to pull cookie from PID %d", from);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
> +		err(errno, "Failed to pull cookie from PID %d", from);
>  	}
>  }
>  
>  void core_sched_push_cookie(pid_t to, core_sched_type_t type)
>  {
> -	int prctl_errno =
> -		prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0);
> -	if (prctl_errno) {
> -		errx(-prctl_errno, "Failed to push cookie to PID %d", to);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
> +		err(errno, "Failed to push cookie to PID %d", to);
>  	}
>  }
>  
> @@ -100,46 +102,35 @@ void core_sched_copy_cookie(struct args *args)
>  void core_sched_exec_with_cookie(struct args *args, char **argv)
>  {
>  	if (!args->exec_argv_offset) {
> -		errx(EINVAL, "when --exec is provided, a program name "
> -			     "has to be given.");
> +		usage();
>  	}
>  
>  	// Move the argument list to the first argument of the program
>  	argv = &argv[args->exec_argv_offset];
>  
> -	pid_t pid = fork();
> -	if (pid == -1) {
> -		errx(errno, "Failed to spawn new process");
> +	// If a source PID is provided, try to copy the cookie from
> +	// that PID. Otherwise, create a brand new cookie with the
> +	// provided type.
> +	if (args->from_pid) {
> +		core_sched_pull_cookie(args->from_pid);
> +	} else {
> +		args->to_pid = getpid();
> +		core_sched_create_cookie(args);
>  	}
>  
> -	if (!pid) {
> -		// If a source PID is provided, try to copy the cookie from
> -		// that PID. Otherwise, create a brand new cookie with the
> -		// provided type.
> -		if (args->from_pid) {
> -			core_sched_pull_cookie(args->from_pid);
> -		} else {
> -			args->from_pid = getpid();
> -			core_sched_create_cookie(args);
> -		}
> -		if (execvp(argv[0], argv)) {
> -			errexec(argv[0]);
> -		}
> -	} else {
> -		int status = 0;
> -		waitpid(pid, &status, 0);
> -		exit(status);
> +	if (execvp(argv[0], argv)) {
> +		errexec(argv[0]);
>  	}
>  }
>  
>  core_sched_type_t parse_core_sched_type(char *str)
>  {
>  	if (!strncmp(str, "pid\0", 4)) {
> -		return SCHED_CORE_SCOPE_PID;
> +		return PR_SCHED_CORE_SCOPE_THREAD;
>  	} else if (!strncmp(str, "tgid\0", 5)) {
> -		return SCHED_CORE_SCOPE_TGID;
> +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
>  	} else if (!strncmp(str, "pgid\0", 5)) {
> -		return SCHED_CORE_SCOPE_PGID;
> +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
>  	}
>  
>  	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
> @@ -155,7 +146,7 @@ static void __attribute__((__noreturn__)) usage(void)
>  		program_invocation_short_name);
>  	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
>  		program_invocation_short_name);
> -	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
> +	fprintf(stdout, _(" %s [-s <PID>] -- PROGRAM ARGS... \n"),
>  		program_invocation_short_name);
>  
>  	fputs(USAGE_SEPARATOR, stdout);
> @@ -169,9 +160,6 @@ static void __attribute__((__noreturn__)) usage(void)
>  	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
>  		  "                           another PID, requires the --source and --dest option"),
>  		stdout);
> -	fputsln(_(" -e, --exec              execute a program with a new core scheduling\n"
> -		  "                           cookie."),
> -		stdout);
>  
>  	fputs(USAGE_OPTIONS, stdout);
>  	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
> @@ -193,116 +181,141 @@ static void __attribute__((__noreturn__)) usage(void)
>  
>  bool verify_arguments(struct args *args)
>  {
> -	if (args->cmd == SCHED_CORE_CMD_NONE) {
> -		usage();
> -	}
> -
>  	// Check if the value of args->cmd is a power of 2
>  	// In that case, only a single function option was set.
> -	if (!(args->cmd && !(args->cmd & (args->cmd - 1)))) {
> -		errx(EINVAL, "Cannot do more than one function at a time.");
> -	}
> -
> -	if (args->from_pid < 0) {
> -		errx(EINVAL, "source PID cannot be negative");
> +	if (args->cmd & (args->cmd - 1)) {
> +		errx(EINVAL,
> +		     "Cannot do more than one function at a time. See %s --help",
> +		     program_invocation_short_name);
>  	}
>  
> -	if (args->to_pid < 0) {
> -		errx(EINVAL, "destination PID cannot be negative");
> -	}
> -
> -	if (args->from_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> -		errx(EINVAL, "valid argument to --source is required");
> -	}
> -
> -	if (args->to_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> -		errx(EINVAL, "valid argument to --dest is required");
> +	switch (args->cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		if (args->to_pid) {
> +			errx(EINVAL,
> +			     "Cannot use -d/--dest with this -g/--get. See %s --help",
> +			     program_invocation_short_name);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_CREATE:
> +		if (args->from_pid) {
> +			errx(EINVAL,
> +			     "Cannot use -s/--source with this -n/--new. See %s --help",
> +			     program_invocation_short_name);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		if (!args->from_pid) {
> +			errx(EINVAL,
> +			     "-s/--source PID is required when copying");
> +		}
> +		if (!args->to_pid) {
> +			errx(EINVAL, "-d/--dest PID is required when copying");
> +		}
> +		break;
> +	case SCHED_CORE_CMD_EXEC:
> +		if (args->to_pid) {
> +			errx(EINVAL,
> +			     "Cannot use -d/--dest when spawning a program. See %s --help",
> +			     program_invocation_short_name);
> +		}
> +		break;
>  	}
> +	return true;
> +}
>  
> -	if (args->from_pid == 0 && args->cmd != SCHED_CORE_CMD_EXEC) {
> -		errx(EINVAL, "PID cannot be zero");
> +void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg)
> +{
> +	if (*dest) {
> +		errx(EINVAL, "Ambigious usage: %s", err_msg);
> +	} else {
> +		*dest = src;
>  	}
> -
> -	return true;
>  }
>  
> +static const char *ERR_MSG_MULTIPLE_SOURCE_PIDS =
> +	"Multiple source PIDs defined";
>  void parse_arguments(int argc, char **argv, struct args *args)
>  {
>  	int c;
> -
> -	enum {
> -		OPT_GET = 'g',
> -		OPT_NEW = 'n',
> -		OPT_COPY = 'c',
> -		OPT_EXEC = 'e',
> -		OPT_SRC = 's',
> -		OPT_DEST = 'd',
> -		OPT_TYPE = 't',
> -		OPT_VERSION = 'V',
> -		OPT_HELP = 'h'
> -	};
> +	pid_t tmp;
>  
>  	static const struct option longopts[] = {
> -		{ "get", required_argument, NULL, OPT_GET },
> -		{ "new", required_argument, NULL, OPT_NEW },
> -		{ "copy", no_argument, NULL, OPT_COPY },
> -		{ "exec", no_argument, NULL, OPT_EXEC },
> -		{ "source", required_argument, NULL, OPT_SRC },
> -		{ "destination", required_argument, NULL, OPT_DEST },
> -		{ "type", required_argument, NULL, OPT_TYPE },
> -		{ "version", no_argument, NULL, OPT_VERSION },
> -		{ "help", no_argument, NULL, OPT_HELP },
> +		{ "get", required_argument, NULL, 'g' },
> +		{ "new", required_argument, NULL, 'n' },
> +		{ "copy", no_argument, NULL, 'c' },
> +		{ "source", required_argument, NULL, 's' },
> +		{ "destination", required_argument, NULL, 'd' },
> +		{ "type", required_argument, NULL, 't' },
> +		{ "version", no_argument, NULL, 'V' },
> +		{ "help", no_argument, NULL, 'h' },
>  		{ NULL, 0, NULL, 0 }
>  	};
>  
> -	while ((c = getopt_long(argc, argv, "g:n:ces:d:t:Vh", longopts,
> -				NULL)) != -1)
> +	while ((c = getopt_long(argc, argv, "g:n:cs:d:t:Vh", longopts, NULL)) !=
> +	       -1)
>  		switch (c) {
> -		case OPT_GET:
> +		case 'g':
>  			args->cmd |= SCHED_CORE_CMD_GET;
> -			args->from_pid = strtos32_or_err(
> -				optarg, "Failed to parse PID for --get");
> +			tmp = strtopid_or_err(
> +				optarg, "Failed to parse PID for -g/--get");
> +			set_pid_or_err(&args->from_pid, tmp,
> +				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
>  			break;
> -		case OPT_NEW:
> +		case 'n':
>  			args->cmd |= SCHED_CORE_CMD_CREATE;
> -			args->from_pid = strtos32_or_err(
> -				optarg, "Failed to parse PID for --new");
> +			tmp = strtopid_or_err(
> +				optarg, "Failed to parse PID for -n/--new");
> +			set_pid_or_err(&args->to_pid, tmp,
> +				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
>  			break;
> -		case OPT_COPY:
> +		case 'c':
>  			args->cmd |= SCHED_CORE_CMD_COPY;
>  			break;
> -		case OPT_EXEC:
> -			args->cmd |= SCHED_CORE_CMD_EXEC;
> -			break;
> -		case OPT_SRC:
> -			args->from_pid = strtos32_or_err(
> -				optarg, "Failed to parse PID for --source");
> +		case 's':
> +			tmp = strtopid_or_err(
> +				optarg, "Failed to parse PID for -s/--source");
> +			set_pid_or_err(&args->from_pid, tmp,
> +				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
>  			break;
> -		case OPT_DEST:
> -			args->to_pid = strtos32_or_err(
> -				optarg, "Failed to parse PID for --dest");
> +		case 'd':
> +			tmp = strtopid_or_err(
> +				optarg, "Failed to parse PID for -d/--dest");
> +			set_pid_or_err(&args->to_pid, tmp,
> +				       "Multiple destination PIDs defined");
>  			break;
> -		case OPT_TYPE:
> +		case 't':
>  			args->type = parse_core_sched_type(optarg);
>  			break;
> -		case OPT_VERSION:
> +		case 'V':
>  			print_version(EXIT_SUCCESS);
> -		case OPT_HELP:
> +		case 'h':
>  			usage();
>  		default:
>  			errtryhelp(EXIT_FAILURE);
>  		}
>  
>  	if (argc > optind) {
> -		args->exec_argv_offset = optind;
> +		if (args->cmd == SCHED_CORE_CMD_EXEC) {
> +			args->exec_argv_offset = optind;
> +		} else {
> +			// -g, -n or -c AND a program to run is provided
> +			errx(EINVAL, "bad usage, see %s --help",
> +			     program_invocation_short_name);
> +		}
> +	} else if (argc == optind && args->from_pid) {
> +		// Neither a function (-g, -n, or -c), nor a program to
> +		// run is given
> +		args->cmd = SCHED_CORE_CMD_GET;
>  	}
> +
>  	verify_arguments(args);
>  }
>  
>  int main(int argc, char **argv)
>  {
>  	struct args arguments = { 0 };
> -	arguments.type = SCHED_CORE_SCOPE_TGID;
> +	arguments.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
>  
>  	setlocale(LC_ALL, "");
>  	bindtextdomain(PACKAGE, LOCALEDIR);
> -- 
> 2.44.0
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 17:49     ` [PATCH 0/1] " Thijs Raymakers
  2024-03-26 18:13       ` Phil Auld
@ 2024-03-26 19:09       ` Phil Auld
  2024-03-26 19:26         ` Thijs Raymakers
  1 sibling, 1 reply; 42+ messages in thread
From: Phil Auld @ 2024-03-26 19:09 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux


Hi Thijs,

On Tue, Mar 26, 2024 at 06:49:07PM +0100 Thijs Raymakers wrote:
> Hi Phil,
> 
> Thank you for your comments. I've attached an interdiff to this email
> with all the changes that I've made in response.
>

I tried this version out and it doesn't work for me. One of the
basic use cases of a wrapper like this is to run a command with
a new cookie (say starting a container or something).

Coresched requires a pid to do that:
 # ./coresched -n ls
 coresched: Failed to parse PID for -n/--new: 'ls'   

With my coreset utility it does work and gives some information about what
it did:

 # coreset -n ls
 pid 20860's current cookie: 0x0
 pid 20860's new cookie: 0xa9fcfbf1
 ABOUT-NLS        chrt           configure      ionice        libsmartcols  Makefile.in
...

Did I miss something?  I think this will be one of the primary use cases for
this utility.


Cheers,
Phil


> Phil Auld wrote on 26-03-2024 at 15:41:
> > The exec should not fork first.
> 
> You're right, this is not necessary. I've removed it.
> 
> > And the error handling I think is just not right. I believe prctl()
> > returns -1 on error and sets errno. This code will report EPERM for all
> > prtcl errors.  While EPERM is likely I don't think it's the only error.
> 
> Good catch! I assumed that the return values of the `prctl()` call
> matched the return values of `sched_core_share_pid` in
> `kernel/sched/core_sched.c`. I've updated the error handling code to
> just use errno instead.
> 
> > On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
> >> - How should the program behave if the prctl core scheduling API is not
> >>   available? It has been in Linus' tree since november 2021
> >>   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
> >>   disabled with CONFIG_SCHED_CORE=n
> > 
> > This one is tricky. If I recall the error you get is ambiguous in this case
> > (EINVAL, which can be returned for other reasons). It would be nice to be
> > able to report that specifically. 
> 
> In general, `prctl` does indeed return EINVAL if the operation is not
> recognized, or not supported on the system. The `PR_SCHED_CORE`
> operation itself only returns EINVAL if it is called with
>   - an invalid operation
>   - an invalid type
>   - a negative PID
>   - an invalid cookie store address (for PR_SCHED_CORE_GET)
> Assuming that all these cases are prevented by the util, we could
> interpret a EINVAL as a sign that PR_SCHED_CORE is not supported on
> the system.
> 
> >> - I've currently licensed it under the EUPL v1.2, which is easier to
> >>   enforce in my jurisdiction than the GPL. It is GPL compatible so it
> >>   shouldn't be an issue, but if anybody has any remarks on this, please
> >>   let me know.
> > 
> > The util-linux package is GPL so personally I think it would be good to just
> > make any new addition to that package also GPL. 
> 
> As far as I understand it, util-linux is a collection of different works
> with different licenses (see the README.licensing file). Not every
> program included in util-linux has to be GPL.
> 
> >> +typedef enum {
> >> +	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
> >> +	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
> >> +	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
> >> +} core_sched_type_t;
> >> +
> > 
> > Is this needed? You've made the names a little shorter I guess.
> > Also some versions of prctl.h don't have the PR_SCHED_CORE_SCOPE* defines
> > even when they have the base PR_SCHED_CORE macros.
> 
> I've changed to to just use the PR_SCHED_CORE_SCOPE* definitions
> instead.
> 
> >> +void core_sched_create_cookie(struct args *args)
> >> +{
> >> +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
> >> +				args->from_pid, args->type, 0);
> > 
> > We are creating a new cookie on from_pid, seems odd.
> 
> I agree, I changed to to `args->to_pid` because that is more logical.
> 
> >> +	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
> >> +	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
> >> +		program_invocation_short_name);
> >> +	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
> >> +		program_invocation_short_name);
> >> +	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
> >> +		program_invocation_short_name);
> > 
> > There are 4 different arguments that take a pid when there are really
> > only at most 2 needed.
> 
> I chose this to keep the amount of options that you have to provide as
> small possible. I thought that `coresched -n 123` would be easier to use
> than `coresched -n -p 123`. I guess the amount of different arguments
> that can take PIDs can be reduced to two, but it would make the commands
> a bit longer.
> 
> > "Get" could just be the default with no arguments given.
> 
> I've removed the explicit --exec and made it the default when a program
> name is provided. If no program name is provided, it defaults to --get.
> 
> >> +	enum {
> >> +		OPT_GET = 'g',
> >> +		OPT_NEW = 'n',
> >> +		OPT_COPY = 'c',
> >> +		OPT_EXEC = 'e',
> >> +		OPT_SRC = 's',
> >> +		OPT_DEST = 'd',
> >> +		OPT_TYPE = 't',
> >> +		OPT_VERSION = 'V',
> >> +		OPT_HELP = 'h'
> >> +	};
> > 
> > This seems over-engineered.  You have to specificy the characters
> > in the getopt_long call anyway so this just complicates the code for
> > no reason it seems to me.
> 
> Fair point, I've removed this. I initially added this to make it easier
> to experiment a bit with different values for the options, but that is
> no longer necessary.
> 
> Thanks!
> 
> - Thijs
> 
> Thijs Raymakers (1):
>   coresched: Manage core scheduling cookies for tasks
> 
>  .gitignore                  |   1 +
>  bash-completion/coresched   |   0
>  configure.ac                |  12 +-
>  meson.build                 |  16 +-
>  meson_options.txt           |   2 +-
>  schedutils/Makemodule.am    |   8 +
>  schedutils/coresched.1.adoc |  16 ++
>  schedutils/coresched.c      | 353 ++++++++++++++++++++++++++++++++++++
>  8 files changed, 402 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
> 
> Interdiff:
> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> index 4be8f9fda..17d775f2d 100644
> --- a/schedutils/coresched.c
> +++ b/schedutils/coresched.c
> @@ -18,18 +18,24 @@
>  #include "nls.h"
>  #include "strutils.h"
>  
> +// These definitions might not be defined, even if the
> +// prctl interface accepts them.
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int core_sched_type_t;
>  typedef enum {
> -	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
> -	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
> -	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
> -} core_sched_type_t;
> -
> -typedef enum {
> -	SCHED_CORE_CMD_NONE = 0,
> +	SCHED_CORE_CMD_EXEC = 0,
>  	SCHED_CORE_CMD_GET = 1,
>  	SCHED_CORE_CMD_CREATE = 2,
>  	SCHED_CORE_CMD_COPY = 4,
> -	SCHED_CORE_CMD_EXEC = 8,
>  } core_sched_cmd_t;
>  
>  struct args {
> @@ -50,44 +56,40 @@ void core_sched_exec_with_cookie(struct args *args, char **argv);
>  core_sched_type_t parse_core_sched_type(char *str);
>  bool verify_arguments(struct args *args);
>  void parse_arguments(int argc, char **argv, struct args *args);
> +void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
> +static void __attribute__((__noreturn__)) usage(void);
>  
>  unsigned long core_sched_get_cookie(struct args *args)
>  {
>  	unsigned long cookie = 0;
> -	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET,
> -				args->from_pid, SCHED_CORE_SCOPE_PID, &cookie);
> -	if (prctl_errno) {
> -		errx(-prctl_errno, "Failed to get cookie from PID %d",
> -		     args->from_pid);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, args->from_pid,
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> +		err(errno, "Failed to get cookie from PID %d", args->from_pid);
>  	}
>  	return cookie;
>  }
>  
>  void core_sched_create_cookie(struct args *args)
>  {
> -	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
> -				args->from_pid, args->type, 0);
> -	if (prctl_errno) {
> -		errx(-prctl_errno, "Failed to create cookie for PID %d",
> -		     args->from_pid);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, args->to_pid, args->type,
> +		  0)) {
> +		err(errno, "Failed to create cookie for PID %d",
> +		    args->from_pid);
>  	}
>  }
>  
>  void core_sched_pull_cookie(pid_t from)
>  {
> -	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> -				SCHED_CORE_SCOPE_PID, 0);
> -	if (prctl_errno) {
> -		errx(-prctl_errno, "Failed to pull cookie from PID %d", from);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
> +		err(errno, "Failed to pull cookie from PID %d", from);
>  	}
>  }
>  
>  void core_sched_push_cookie(pid_t to, core_sched_type_t type)
>  {
> -	int prctl_errno =
> -		prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0);
> -	if (prctl_errno) {
> -		errx(-prctl_errno, "Failed to push cookie to PID %d", to);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
> +		err(errno, "Failed to push cookie to PID %d", to);
>  	}
>  }
>  
> @@ -100,46 +102,35 @@ void core_sched_copy_cookie(struct args *args)
>  void core_sched_exec_with_cookie(struct args *args, char **argv)
>  {
>  	if (!args->exec_argv_offset) {
> -		errx(EINVAL, "when --exec is provided, a program name "
> -			     "has to be given.");
> +		usage();
>  	}
>  
>  	// Move the argument list to the first argument of the program
>  	argv = &argv[args->exec_argv_offset];
>  
> -	pid_t pid = fork();
> -	if (pid == -1) {
> -		errx(errno, "Failed to spawn new process");
> +	// If a source PID is provided, try to copy the cookie from
> +	// that PID. Otherwise, create a brand new cookie with the
> +	// provided type.
> +	if (args->from_pid) {
> +		core_sched_pull_cookie(args->from_pid);
> +	} else {
> +		args->to_pid = getpid();
> +		core_sched_create_cookie(args);
>  	}
>  
> -	if (!pid) {
> -		// If a source PID is provided, try to copy the cookie from
> -		// that PID. Otherwise, create a brand new cookie with the
> -		// provided type.
> -		if (args->from_pid) {
> -			core_sched_pull_cookie(args->from_pid);
> -		} else {
> -			args->from_pid = getpid();
> -			core_sched_create_cookie(args);
> -		}
> -		if (execvp(argv[0], argv)) {
> -			errexec(argv[0]);
> -		}
> -	} else {
> -		int status = 0;
> -		waitpid(pid, &status, 0);
> -		exit(status);
> +	if (execvp(argv[0], argv)) {
> +		errexec(argv[0]);
>  	}
>  }
>  
>  core_sched_type_t parse_core_sched_type(char *str)
>  {
>  	if (!strncmp(str, "pid\0", 4)) {
> -		return SCHED_CORE_SCOPE_PID;
> +		return PR_SCHED_CORE_SCOPE_THREAD;
>  	} else if (!strncmp(str, "tgid\0", 5)) {
> -		return SCHED_CORE_SCOPE_TGID;
> +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
>  	} else if (!strncmp(str, "pgid\0", 5)) {
> -		return SCHED_CORE_SCOPE_PGID;
> +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
>  	}
>  
>  	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
> @@ -155,7 +146,7 @@ static void __attribute__((__noreturn__)) usage(void)
>  		program_invocation_short_name);
>  	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
>  		program_invocation_short_name);
> -	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
> +	fprintf(stdout, _(" %s [-s <PID>] -- PROGRAM ARGS... \n"),
>  		program_invocation_short_name);
>  
>  	fputs(USAGE_SEPARATOR, stdout);
> @@ -169,9 +160,6 @@ static void __attribute__((__noreturn__)) usage(void)
>  	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
>  		  "                           another PID, requires the --source and --dest option"),
>  		stdout);
> -	fputsln(_(" -e, --exec              execute a program with a new core scheduling\n"
> -		  "                           cookie."),
> -		stdout);
>  
>  	fputs(USAGE_OPTIONS, stdout);
>  	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
> @@ -193,116 +181,141 @@ static void __attribute__((__noreturn__)) usage(void)
>  
>  bool verify_arguments(struct args *args)
>  {
> -	if (args->cmd == SCHED_CORE_CMD_NONE) {
> -		usage();
> -	}
> -
>  	// Check if the value of args->cmd is a power of 2
>  	// In that case, only a single function option was set.
> -	if (!(args->cmd && !(args->cmd & (args->cmd - 1)))) {
> -		errx(EINVAL, "Cannot do more than one function at a time.");
> -	}
> -
> -	if (args->from_pid < 0) {
> -		errx(EINVAL, "source PID cannot be negative");
> +	if (args->cmd & (args->cmd - 1)) {
> +		errx(EINVAL,
> +		     "Cannot do more than one function at a time. See %s --help",
> +		     program_invocation_short_name);
>  	}
>  
> -	if (args->to_pid < 0) {
> -		errx(EINVAL, "destination PID cannot be negative");
> -	}
> -
> -	if (args->from_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> -		errx(EINVAL, "valid argument to --source is required");
> -	}
> -
> -	if (args->to_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
> -		errx(EINVAL, "valid argument to --dest is required");
> +	switch (args->cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		if (args->to_pid) {
> +			errx(EINVAL,
> +			     "Cannot use -d/--dest with this -g/--get. See %s --help",
> +			     program_invocation_short_name);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_CREATE:
> +		if (args->from_pid) {
> +			errx(EINVAL,
> +			     "Cannot use -s/--source with this -n/--new. See %s --help",
> +			     program_invocation_short_name);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		if (!args->from_pid) {
> +			errx(EINVAL,
> +			     "-s/--source PID is required when copying");
> +		}
> +		if (!args->to_pid) {
> +			errx(EINVAL, "-d/--dest PID is required when copying");
> +		}
> +		break;
> +	case SCHED_CORE_CMD_EXEC:
> +		if (args->to_pid) {
> +			errx(EINVAL,
> +			     "Cannot use -d/--dest when spawning a program. See %s --help",
> +			     program_invocation_short_name);
> +		}
> +		break;
>  	}
> +	return true;
> +}
>  
> -	if (args->from_pid == 0 && args->cmd != SCHED_CORE_CMD_EXEC) {
> -		errx(EINVAL, "PID cannot be zero");
> +void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg)
> +{
> +	if (*dest) {
> +		errx(EINVAL, "Ambigious usage: %s", err_msg);
> +	} else {
> +		*dest = src;
>  	}
> -
> -	return true;
>  }
>  
> +static const char *ERR_MSG_MULTIPLE_SOURCE_PIDS =
> +	"Multiple source PIDs defined";
>  void parse_arguments(int argc, char **argv, struct args *args)
>  {
>  	int c;
> -
> -	enum {
> -		OPT_GET = 'g',
> -		OPT_NEW = 'n',
> -		OPT_COPY = 'c',
> -		OPT_EXEC = 'e',
> -		OPT_SRC = 's',
> -		OPT_DEST = 'd',
> -		OPT_TYPE = 't',
> -		OPT_VERSION = 'V',
> -		OPT_HELP = 'h'
> -	};
> +	pid_t tmp;
>  
>  	static const struct option longopts[] = {
> -		{ "get", required_argument, NULL, OPT_GET },
> -		{ "new", required_argument, NULL, OPT_NEW },
> -		{ "copy", no_argument, NULL, OPT_COPY },
> -		{ "exec", no_argument, NULL, OPT_EXEC },
> -		{ "source", required_argument, NULL, OPT_SRC },
> -		{ "destination", required_argument, NULL, OPT_DEST },
> -		{ "type", required_argument, NULL, OPT_TYPE },
> -		{ "version", no_argument, NULL, OPT_VERSION },
> -		{ "help", no_argument, NULL, OPT_HELP },
> +		{ "get", required_argument, NULL, 'g' },
> +		{ "new", required_argument, NULL, 'n' },
> +		{ "copy", no_argument, NULL, 'c' },
> +		{ "source", required_argument, NULL, 's' },
> +		{ "destination", required_argument, NULL, 'd' },
> +		{ "type", required_argument, NULL, 't' },
> +		{ "version", no_argument, NULL, 'V' },
> +		{ "help", no_argument, NULL, 'h' },
>  		{ NULL, 0, NULL, 0 }
>  	};
>  
> -	while ((c = getopt_long(argc, argv, "g:n:ces:d:t:Vh", longopts,
> -				NULL)) != -1)
> +	while ((c = getopt_long(argc, argv, "g:n:cs:d:t:Vh", longopts, NULL)) !=
> +	       -1)
>  		switch (c) {
> -		case OPT_GET:
> +		case 'g':
>  			args->cmd |= SCHED_CORE_CMD_GET;
> -			args->from_pid = strtos32_or_err(
> -				optarg, "Failed to parse PID for --get");
> +			tmp = strtopid_or_err(
> +				optarg, "Failed to parse PID for -g/--get");
> +			set_pid_or_err(&args->from_pid, tmp,
> +				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
>  			break;
> -		case OPT_NEW:
> +		case 'n':
>  			args->cmd |= SCHED_CORE_CMD_CREATE;
> -			args->from_pid = strtos32_or_err(
> -				optarg, "Failed to parse PID for --new");
> +			tmp = strtopid_or_err(
> +				optarg, "Failed to parse PID for -n/--new");
> +			set_pid_or_err(&args->to_pid, tmp,
> +				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
>  			break;
> -		case OPT_COPY:
> +		case 'c':
>  			args->cmd |= SCHED_CORE_CMD_COPY;
>  			break;
> -		case OPT_EXEC:
> -			args->cmd |= SCHED_CORE_CMD_EXEC;
> -			break;
> -		case OPT_SRC:
> -			args->from_pid = strtos32_or_err(
> -				optarg, "Failed to parse PID for --source");
> +		case 's':
> +			tmp = strtopid_or_err(
> +				optarg, "Failed to parse PID for -s/--source");
> +			set_pid_or_err(&args->from_pid, tmp,
> +				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
>  			break;
> -		case OPT_DEST:
> -			args->to_pid = strtos32_or_err(
> -				optarg, "Failed to parse PID for --dest");
> +		case 'd':
> +			tmp = strtopid_or_err(
> +				optarg, "Failed to parse PID for -d/--dest");
> +			set_pid_or_err(&args->to_pid, tmp,
> +				       "Multiple destination PIDs defined");
>  			break;
> -		case OPT_TYPE:
> +		case 't':
>  			args->type = parse_core_sched_type(optarg);
>  			break;
> -		case OPT_VERSION:
> +		case 'V':
>  			print_version(EXIT_SUCCESS);
> -		case OPT_HELP:
> +		case 'h':
>  			usage();
>  		default:
>  			errtryhelp(EXIT_FAILURE);
>  		}
>  
>  	if (argc > optind) {
> -		args->exec_argv_offset = optind;
> +		if (args->cmd == SCHED_CORE_CMD_EXEC) {
> +			args->exec_argv_offset = optind;
> +		} else {
> +			// -g, -n or -c AND a program to run is provided
> +			errx(EINVAL, "bad usage, see %s --help",
> +			     program_invocation_short_name);
> +		}
> +	} else if (argc == optind && args->from_pid) {
> +		// Neither a function (-g, -n, or -c), nor a program to
> +		// run is given
> +		args->cmd = SCHED_CORE_CMD_GET;
>  	}
> +
>  	verify_arguments(args);
>  }
>  
>  int main(int argc, char **argv)
>  {
>  	struct args arguments = { 0 };
> -	arguments.type = SCHED_CORE_SCOPE_TGID;
> +	arguments.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
>  
>  	setlocale(LC_ALL, "");
>  	bindtextdomain(PACKAGE, LOCALEDIR);
> -- 
> 2.44.0
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 19:09       ` [PATCH 0/1] " Phil Auld
@ 2024-03-26 19:26         ` Thijs Raymakers
  2024-03-26 20:16           ` Phil Auld
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-03-26 19:26 UTC (permalink / raw)
  To: Phil Auld; +Cc: kzak, util-linux

Op 26-03-2024 om 20:09 schreef Phil Auld:
> I tried this version out and it doesn't work for me. One of the
> basic use cases of a wrapper like this is to run a command with
> a new cookie (say starting a container or something).
> 
> Coresched requires a pid to do that:
>  # ./coresched -n ls
>  coresched: Failed to parse PID for -n/--new: 'ls'   
> 
> With my coreset utility it does work and gives some information about what
> it did:
> 
>  # coreset -n ls
>  pid 20860's current cookie: 0x0
>  pid 20860's new cookie: 0xa9fcfbf1
>  ABOUT-NLS        chrt           configure      ionice        libsmartcols  Makefile.in
> ...
> 
> Did I miss something?  I think this will be one of the primary use cases for
> this utility.

Hi Phil,

The following command works

# ./coresched ls
chrt.1	    chrt.1.deps  coresched.1.adoc  coresched.o  ionice.1.adoc
...

By default, it will spawn the provided program with a new core
scheduling cookie.

This command
# ./coresched -n 123

is to assign a new core scheduling cookie to PID 123. The commands don't
show what the current and/or new cookie is, because that is of limited
utility in my opinion (and --get also does this). You can only use it to
check if two programs have the same cookie. An error message will be
printed and the exit code will be set if it doesn't succeed. Otherwise,
if the program succeeds then it won't write anything to stderr/stdout.

Thijs

^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 19:26         ` Thijs Raymakers
@ 2024-03-26 20:16           ` Phil Auld
  2024-03-26 20:17             ` Phil Auld
  0 siblings, 1 reply; 42+ messages in thread
From: Phil Auld @ 2024-03-26 20:16 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux

On Tue, Mar 26, 2024 at 08:26:23PM +0100 Thijs Raymakers wrote:
> Op 26-03-2024 om 20:09 schreef Phil Auld:
> > I tried this version out and it doesn't work for me. One of the
> > basic use cases of a wrapper like this is to run a command with
> > a new cookie (say starting a container or something).
> > 
> > Coresched requires a pid to do that:
> >  # ./coresched -n ls
> >  coresched: Failed to parse PID for -n/--new: 'ls'   
> > 
> > With my coreset utility it does work and gives some information about what
> > it did:
> > 
> >  # coreset -n ls
> >  pid 20860's current cookie: 0x0
> >  pid 20860's new cookie: 0xa9fcfbf1
> >  ABOUT-NLS        chrt           configure      ionice        libsmartcols  Makefile.in
> > ...
> > 
> > Did I miss something?  I think this will be one of the primary use cases for
> > this utility.
> 
> Hi Phil,
> 
> The following command works
> 
> # ./coresched ls
> chrt.1	    chrt.1.deps  coresched.1.adoc  coresched.o  ionice.1.adoc
> ...

Hhm, I'd prefer the no argument case to be a noop. 

But okay, I was missing something :)  This is where some output might
have helped though ...

So "-n/--new" creates a new cookie and so does "". Just one on an existing task
and one on the exec'd task.  Seems inconsistent, no?

> 
> By default, it will spawn the provided program with a new core
> scheduling cookie.
>

See above about default. But maybe that's just my taste.

Since you have expplicit commands to do things why not just make --get be
the default no command one?  So coresched -p 1234  just reports the cookie
of pid 1234 or corsched ls just reports (in this case the meaningless) cookie
of the exec'd ls command.

The use the -n and friends as an action operation to create/copy etc.


> This command
> # ./coresched -n 123
> 
> is to assign a new core scheduling cookie to PID 123. The commands don't
> show what the current and/or new cookie is, because that is of limited
> utility in my opinion (and --get also does this).

Yes, true. But it's sometimes nice to see that the command you ran did
something,


But anyway, that's more of my 2 cents. I'll try it again with new
knowledge.


Cheers,
Phil



> You can only use it to check if two programs have the same cookie.
> An error message will be printed and the exit code will be set if it
> doesn't succeed. Otherwise,
> if the program succeeds then it won't write anything to stderr/stdout.
> 
> Thijs
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 20:16           ` Phil Auld
@ 2024-03-26 20:17             ` Phil Auld
  0 siblings, 0 replies; 42+ messages in thread
From: Phil Auld @ 2024-03-26 20:17 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux

On Tue, Mar 26, 2024 at 04:16:23PM -0400 Phil Auld wrote:
> On Tue, Mar 26, 2024 at 08:26:23PM +0100 Thijs Raymakers wrote:
> > Op 26-03-2024 om 20:09 schreef Phil Auld:
> > > I tried this version out and it doesn't work for me. One of the
> > > basic use cases of a wrapper like this is to run a command with
> > > a new cookie (say starting a container or something).
> > > 
> > > Coresched requires a pid to do that:
> > >  # ./coresched -n ls
> > >  coresched: Failed to parse PID for -n/--new: 'ls'   
> > > 
> > > With my coreset utility it does work and gives some information about what
> > > it did:
> > > 
> > >  # coreset -n ls
> > >  pid 20860's current cookie: 0x0
> > >  pid 20860's new cookie: 0xa9fcfbf1
> > >  ABOUT-NLS        chrt           configure      ionice        libsmartcols  Makefile.in
> > > ...
> > > 
> > > Did I miss something?  I think this will be one of the primary use cases for
> > > this utility.
> > 
> > Hi Phil,
> > 
> > The following command works
> > 
> > # ./coresched ls
> > chrt.1	    chrt.1.deps  coresched.1.adoc  coresched.o  ionice.1.adoc
> > ...
> 
> Hhm, I'd prefer the no argument case to be a noop. 
> 
> But okay, I was missing something :)  This is where some output might
> have helped though ...
> 
> So "-n/--new" creates a new cookie and so does "". Just one on an existing task
> and one on the exec'd task.  Seems inconsistent, no?
> 
> > 
> > By default, it will spawn the provided program with a new core
> > scheduling cookie.
> >
> 
> See above about default. But maybe that's just my taste.
> 
> Since you have expplicit commands to do things why not just make --get be
> the default no command one?  So coresched -p 1234  just reports the cookie
> of pid 1234 or corsched ls just reports (in this case the meaningless) cookie
> of the exec'd ls command.
> 
> The use the -n and friends as an action operation to create/copy etc.
> 
> 
> > This command
> > # ./coresched -n 123
> > 
> > is to assign a new core scheduling cookie to PID 123. The commands don't
> > show what the current and/or new cookie is, because that is of limited
> > utility in my opinion (and --get also does this).
> 
> Yes, true. But it's sometimes nice to see that the command you ran did
> something,
> 
> 
> But anyway, that's more of my 2 cents. I'll try it again with new
> knowledge.
>

Btw, did you try coreset to see if it does what you need?


Cheers,
Phil



> 
> Cheers,
> Phil
> 
> 
> 
> > You can only use it to check if two programs have the same cookie.
> > An error message will be printed and the exit code will be set if it
> > doesn't succeed. Otherwise,
> > if the program succeeds then it won't write anything to stderr/stdout.
> > 
> > Thijs
> > 
> 
> -- 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v2 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 18:13       ` Phil Auld
@ 2024-03-27 12:43         ` Thijs Raymakers
  2024-03-27 13:21           ` Phil Auld
  2024-03-27 12:43         ` [PATCH v2 1/1] " Thijs Raymakers
  1 sibling, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-03-27 12:43 UTC (permalink / raw)
  To: pauld; +Cc: kzak, util-linux, Thijs Raymakers

Hi Phil,

Op 26-03-2024 om 19:13 schreef Phil Auld:
> On Tue, Mar 26, 2024 at 06:49:07PM +0100 Thijs Raymakers wrote:
>> Op 26-03-2024 om 15:41 schreef Phil Auld:
>>> On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
>>>> - How should the program behave if the prctl core scheduling API is not
>>>>   available? It has been in Linus' tree since november 2021
>>>>   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
>>>>   disabled with CONFIG_SCHED_CORE=n
>>> This one is tricky. If I recall the error you get is ambiguous in this case
>>> (EINVAL, which can be returned for other reasons). It would be nice to be
>>> able to report that specifically. 
>> In general, `prctl` does indeed return EINVAL if the operation is not
>> recognized, or not supported on the system. The `PR_SCHED_CORE`
>> operation itself only returns EINVAL if it is called with
>>   - an invalid operation
>>   - an invalid type
>>   - a negative PID
>>   - an invalid cookie store address (for PR_SCHED_CORE_GET)
>> Assuming that all these cases are prevented by the util, we could
>> interpret a EINVAL as a sign that PR_SCHED_CORE is not supported on
>> the system.
> Fair enough. Could say something like "got EINVAL. Does your kernel
> support CONFIG_SCHED_CORE?". 

I've added this as a warning in case a prctl call returns EINVAL. I
haven't tested it on a kernel with CONFIG_SCHED_CORE=n yet but I will do
that later.

> There is also
>        err_exclusive_options(c, longopts, excl, excl_st);
> 
> in the optuils.h code which can handle the tests for mutually exclusive
> arguments.
> 
> Thomas pointed me to that. It works nicely and can remove some of the extra
> checks (once you get it setup). And the error then looks the same as other
> util-linux progs. 

Good suggestion, I replaced some of the earlier mutually exclusive
checks with a call to err_exclusive_options.

> You might consider "errtryhelp(EXIT_FAILURE);"  in usage failures. A lot of
> the progs (incl taskset which is my model for this) do that. Then you only
> do the full usage when given -h/--help.

This does indeed feel a slightly nicer to use. I've added this in case a
of a usage failure. In case no options are given, it defaults to
printing the --help page. This matches the behavior of some other utils like
uclampset, and saves some time.


Op 26-03-2024 om 21:16 schreef Phil Auld:
> So "-n/--new" creates a new cookie and so does "". Just one on an existing task
> and one on the exec'd task.  Seems inconsistent, no?

Hmmm, I didn't intially see this as inconsistent behavior but I get why
you say this. I've modified this now to have -n/--new deal both with
assigning cookies to existing processes and to spawn new processes.


Op 26-03-2024 om 21:17 schreef Phil Auld:
> Btw, did you try coreset to see if it does what you need?

I did take a look at it. Thanks for adding the functionality of copying
a cookie from/to an existing PID. The commands take a bit of getting
used to for me, so I guess it is just a matter of preference of what feels
natural to use.
Some things that weren't directly clear to me:
- Both `coreset ls` and `coreset -n ls` spawn a new program. If I
  understood it correctly, the former spawns the program without
  a cookie, and the latter spawns it with a cookie. I'm not entirely
  sure what the point of the former would be, as the cookie would either
  be 0 or equal to the cookie of the `coreset` process itself since the
  cookie in inherited across execs. It is not necessary to run the
  provided program in that case.
- Not entirely sure what the difference between --dest and --to is.
  The names of these options are similar in meaning and do almost the
  same thing.
- It is not clear in what cases --scope is ignored and in which
  cases it is necessary.
- What does `coreset -c -s 0 -d 2 -p 1 ls` do? Does it first copy the
  cookie from PID 1 to PID 2, and then run `ls` with the same or
  different cookie? Or the other way around?

Now I do see what you meant earlier with imitating the behavior of the
`taskset` command. I don't think this is strictly necessary since it is
a different utility program, but I can understand that it can be useful
for people if the command behaves similar to a program that they already
know and have certain expectations of.

I've modified the options of coresched to be more in line with these
expectations, while also addressing the things I mentioned above.

Usage:
 coresched [-p PID]
 coresched --new [-t <TYPE>] -p <PID>
 coresched --new [-t <TYPE>] -- PROGRAM [ARGS...]
 coresched --copy -p <PID> [-t <TYPE>] -d <PID>
 coresched --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]

This follows the taskset's behavior (i.e. perform the same operation
on either an existing PID or on a new program) and taskset's arguments
(-p for PID instead of the -s I had earlier) more closely than before.

I've attached an interdiff of the util compared to the previous version
that I sent yesterday. I think that this version encompases both our use
cases quite nicely.

Thijs

Thijs Raymakers (1):
  coresched: Manage core scheduling cookies for tasks

 .gitignore                  |   1 +
 bash-completion/coresched   |   0
 configure.ac                |  12 +-
 meson.build                 |  16 +-
 meson_options.txt           |   2 +-
 schedutils/Makemodule.am    |   8 +
 schedutils/coresched.1.adoc |  16 ++
 schedutils/coresched.c      | 363 ++++++++++++++++++++++++++++++++++++
 8 files changed, 412 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c

Interdiff against v1:
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
index 17d775f2d..537281fdb 100644
--- a/schedutils/coresched.c
+++ b/schedutils/coresched.c
@@ -12,10 +12,12 @@
 #include <stdio.h>
 #include <sys/prctl.h>
 #include <sys/wait.h>
+#include <unistd.h>
 
 #include "c.h"
 #include "closestream.h"
 #include "nls.h"
+#include "optutils.h"
 #include "strutils.h"
 
 // These definitions might not be defined, even if the
@@ -26,32 +28,34 @@
 #ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
 #define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
 #endif
+
 #ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
 #define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
 #endif
 
 typedef int core_sched_type_t;
+typedef unsigned long cookie_t;
 typedef enum {
-	SCHED_CORE_CMD_EXEC = 0,
-	SCHED_CORE_CMD_GET = 1,
-	SCHED_CORE_CMD_CREATE = 2,
-	SCHED_CORE_CMD_COPY = 4,
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
 } core_sched_cmd_t;
 
 struct args {
-	pid_t from_pid;
-	pid_t to_pid;
+	pid_t pid;
+	pid_t dest;
 	core_sched_type_t type;
 	core_sched_cmd_t cmd;
 	int exec_argv_offset;
 };
 
-unsigned long core_sched_get_cookie(struct args *args);
-void core_sched_create_cookie(struct args *args);
+cookie_t core_sched_get_cookie(pid_t pid);
+void core_sched_create_cookie(pid_t pid, core_sched_type_t type);
 void core_sched_pull_cookie(pid_t from);
 void core_sched_push_cookie(pid_t to, core_sched_type_t type);
-void core_sched_copy_cookie(struct args *args);
+void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type);
 void core_sched_exec_with_cookie(struct args *args, char **argv);
+void core_sched_get_and_print_cookie(pid_t pid);
 
 core_sched_type_t parse_core_sched_type(char *str);
 bool verify_arguments(struct args *args);
@@ -59,22 +63,31 @@ void parse_arguments(int argc, char **argv, struct args *args);
 void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
 static void __attribute__((__noreturn__)) usage(void);
 
-unsigned long core_sched_get_cookie(struct args *args)
+#define bad_usage(FMT...) \
+	warnx(FMT);       \
+	errtryhelp(EINVAL);
+
+#define check_coresched_in_kernel(errno)                              \
+	if (errno == EINVAL) {                                        \
+		warnx("Does your kernel support CONFIG_SCHED_CORE?"); \
+	}
+
+cookie_t core_sched_get_cookie(pid_t pid)
 {
-	unsigned long cookie = 0;
-	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, args->from_pid,
+	cookie_t cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
 		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
-		err(errno, "Failed to get cookie from PID %d", args->from_pid);
+		check_coresched_in_kernel(errno);
+		err(errno, "Failed to get cookie from PID %d", pid);
 	}
 	return cookie;
 }
 
-void core_sched_create_cookie(struct args *args)
+void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
 {
-	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, args->to_pid, args->type,
-		  0)) {
-		err(errno, "Failed to create cookie for PID %d",
-		    args->from_pid);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
+		check_coresched_in_kernel(errno);
+		err(errno, "Failed to create cookie for PID %d", pid);
 	}
 }
 
@@ -82,6 +95,7 @@ void core_sched_pull_cookie(pid_t from)
 {
 	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
 		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
+		check_coresched_in_kernel(errno);
 		err(errno, "Failed to pull cookie from PID %d", from);
 	}
 }
@@ -89,14 +103,18 @@ void core_sched_pull_cookie(pid_t from)
 void core_sched_push_cookie(pid_t to, core_sched_type_t type)
 {
 	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
+		check_coresched_in_kernel(errno);
 		err(errno, "Failed to push cookie to PID %d", to);
 	}
 }
 
-void core_sched_copy_cookie(struct args *args)
+void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type)
 {
-	core_sched_pull_cookie(args->from_pid);
-	core_sched_push_cookie(args->to_pid, args->type);
+	core_sched_pull_cookie(from);
+	cookie_t before = core_sched_get_cookie(from);
+	core_sched_push_cookie(to, to_type);
+	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
+	       program_invocation_short_name, before, from, to);
 }
 
 void core_sched_exec_with_cookie(struct args *args, char **argv)
@@ -111,11 +129,15 @@ void core_sched_exec_with_cookie(struct args *args, char **argv)
 	// If a source PID is provided, try to copy the cookie from
 	// that PID. Otherwise, create a brand new cookie with the
 	// provided type.
-	if (args->from_pid) {
-		core_sched_pull_cookie(args->from_pid);
+	if (args->pid) {
+		core_sched_pull_cookie(args->pid);
+		core_sched_get_and_print_cookie(args->pid);
 	} else {
-		args->to_pid = getpid();
-		core_sched_create_cookie(args);
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		cookie_t after = core_sched_get_cookie(pid);
+		printf("%s: set cookie of PID %d to 0x%lx\n",
+		       program_invocation_short_name, pid, after);
 	}
 
 	if (execvp(argv[0], argv)) {
@@ -123,6 +145,13 @@ void core_sched_exec_with_cookie(struct args *args, char **argv)
 	}
 }
 
+void core_sched_get_and_print_cookie(pid_t pid)
+{
+	cookie_t after = core_sched_get_cookie(pid);
+	printf("%s: set cookie of PID %d to 0x%lx\n",
+	       program_invocation_short_name, pid, after);
+}
+
 core_sched_type_t parse_core_sched_type(char *str)
 {
 	if (!strncmp(str, "pid\0", 4)) {
@@ -133,156 +162,96 @@ core_sched_type_t parse_core_sched_type(char *str)
 		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
 	}
 
-	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
-	     str);
+	bad_usage("'%s' is an invalid option. Must be one of pid/tgid/pgid",
+		  str);
 	__builtin_unreachable();
 }
 
 static void __attribute__((__noreturn__)) usage(void)
 {
 	fputs(USAGE_HEADER, stdout);
-	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
-	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
+	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
 		program_invocation_short_name);
-	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
 		program_invocation_short_name);
-	fprintf(stdout, _(" %s [-s <PID>] -- PROGRAM ARGS... \n"),
+	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
 		program_invocation_short_name);
 
 	fputs(USAGE_SEPARATOR, stdout);
 	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
 
 	fputs(USAGE_FUNCTIONS, stdout);
-	fputsln(_(" -g, --get <PID>         get the core scheduling cookie of a PID"),
+	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
+		  "                      execute a program with a new cookie."),
 		stdout);
-	fputsln(_(" -n, --new <PID>         assign a new core scheduling cookie to PID"),
+	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
+		  "                      either another PID, or copy it to a new program"),
 		stdout);
-	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
-		  "                           another PID, requires the --source and --dest option"),
+	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
+		  "   the PID provided via --pid.\n"),
 		stdout);
 
 	fputs(USAGE_OPTIONS, stdout);
-	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
-		stdout);
-	fputsln(_(" -d, --dest <PID>        where to copy the core scheduling cookie to."),
+	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
+	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
+		  "                      the destination PID where to copy the cookie to."),
 		stdout);
-	fputsln(_(" -t, --type              type of the destination PID, or the type of\n"
-		  "                           the PID when a new core scheduling cookie\n"
-		  "                           is created. Can be one of the following:\n"
-		  "                           pid, tgid or pgid. Defaults to tgid."),
+	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
+		  "                      when a new core scheduling cookie is created.\n"
+		  "                      Can be one of the following: pid, tgid or pgid.\n"
+		  "                      The default is tgid."),
 		stdout);
 	fputs(USAGE_SEPARATOR, stdout);
 	fprintf(stdout,
 		USAGE_HELP_OPTIONS(
-			25)); /* char offset to align option descriptions */
+			20)); /* char offset to align option descriptions */
 	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
 	exit(EXIT_SUCCESS);
 }
 
-bool verify_arguments(struct args *args)
-{
-	// Check if the value of args->cmd is a power of 2
-	// In that case, only a single function option was set.
-	if (args->cmd & (args->cmd - 1)) {
-		errx(EINVAL,
-		     "Cannot do more than one function at a time. See %s --help",
-		     program_invocation_short_name);
-	}
-
-	switch (args->cmd) {
-	case SCHED_CORE_CMD_GET:
-		if (args->to_pid) {
-			errx(EINVAL,
-			     "Cannot use -d/--dest with this -g/--get. See %s --help",
-			     program_invocation_short_name);
-		}
-		break;
-	case SCHED_CORE_CMD_CREATE:
-		if (args->from_pid) {
-			errx(EINVAL,
-			     "Cannot use -s/--source with this -n/--new. See %s --help",
-			     program_invocation_short_name);
-		}
-		break;
-	case SCHED_CORE_CMD_COPY:
-		if (!args->from_pid) {
-			errx(EINVAL,
-			     "-s/--source PID is required when copying");
-		}
-		if (!args->to_pid) {
-			errx(EINVAL, "-d/--dest PID is required when copying");
-		}
-		break;
-	case SCHED_CORE_CMD_EXEC:
-		if (args->to_pid) {
-			errx(EINVAL,
-			     "Cannot use -d/--dest when spawning a program. See %s --help",
-			     program_invocation_short_name);
-		}
-		break;
-	}
-	return true;
-}
-
-void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg)
-{
-	if (*dest) {
-		errx(EINVAL, "Ambigious usage: %s", err_msg);
-	} else {
-		*dest = src;
-	}
-}
-
-static const char *ERR_MSG_MULTIPLE_SOURCE_PIDS =
-	"Multiple source PIDs defined";
 void parse_arguments(int argc, char **argv, struct args *args)
 {
 	int c;
-	pid_t tmp;
 
 	static const struct option longopts[] = {
-		{ "get", required_argument, NULL, 'g' },
-		{ "new", required_argument, NULL, 'n' },
+		{ "new", no_argument, NULL, 'n' },
 		{ "copy", no_argument, NULL, 'c' },
-		{ "source", required_argument, NULL, 's' },
-		{ "destination", required_argument, NULL, 'd' },
+		{ "pid", required_argument, NULL, 'p' },
+		{ "dest", required_argument, NULL, 'd' },
 		{ "type", required_argument, NULL, 't' },
 		{ "version", no_argument, NULL, 'V' },
 		{ "help", no_argument, NULL, 'h' },
 		{ NULL, 0, NULL, 0 }
 	};
+	static const ul_excl_t excl[] = {
+		{ 'c', 'n' }, // Cannot do both --new and --copy
+		{ 'd', 'n' }, // Cannot have both --new and --dest
+		{ 0 }
+	};
 
-	while ((c = getopt_long(argc, argv, "g:n:cs:d:t:Vh", longopts, NULL)) !=
-	       -1)
+	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+	while ((c = getopt_long(argc, argv, "ncp:d:t:Vh", longopts, NULL)) !=
+	       -1) {
+		err_exclusive_options(c, longopts, excl, excl_st);
 		switch (c) {
-		case 'g':
-			args->cmd |= SCHED_CORE_CMD_GET;
-			tmp = strtopid_or_err(
-				optarg, "Failed to parse PID for -g/--get");
-			set_pid_or_err(&args->from_pid, tmp,
-				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
-			break;
 		case 'n':
-			args->cmd |= SCHED_CORE_CMD_CREATE;
-			tmp = strtopid_or_err(
-				optarg, "Failed to parse PID for -n/--new");
-			set_pid_or_err(&args->to_pid, tmp,
-				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
+			args->cmd = SCHED_CORE_CMD_NEW;
 			break;
 		case 'c':
-			args->cmd |= SCHED_CORE_CMD_COPY;
+			args->cmd = SCHED_CORE_CMD_COPY;
 			break;
-		case 's':
-			tmp = strtopid_or_err(
-				optarg, "Failed to parse PID for -s/--source");
-			set_pid_or_err(&args->from_pid, tmp,
-				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
+		case 'p':
+			args->pid = strtopid_or_err(
+				optarg, "Failed to parse PID for -p/--pid");
 			break;
 		case 'd':
-			tmp = strtopid_or_err(
+			args->dest = strtopid_or_err(
 				optarg, "Failed to parse PID for -d/--dest");
-			set_pid_or_err(&args->to_pid, tmp,
-				       "Multiple destination PIDs defined");
 			break;
 		case 't':
 			args->type = parse_core_sched_type(optarg);
@@ -294,57 +263,98 @@ void parse_arguments(int argc, char **argv, struct args *args)
 		default:
 			errtryhelp(EXIT_FAILURE);
 		}
+	}
 
+	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
+		bad_usage("--copy: requires a -p/--pid");
+	}
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
 	if (argc > optind) {
-		if (args->cmd == SCHED_CORE_CMD_EXEC) {
-			args->exec_argv_offset = optind;
-		} else {
-			// -g, -n or -c AND a program to run is provided
-			errx(EINVAL, "bad usage, see %s --help",
-			     program_invocation_short_name);
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage("Unknown command");
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->pid) {
+				bad_usage(
+					"--new: cannot accept both a -p/--pid and a command");
+			} else {
+				args->exec_argv_offset = optind;
+			}
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest) {
+				bad_usage(
+					"--copy: cannot accept both a destination PID "
+					"-d/--dest and a command")
+			} else {
+				args->exec_argv_offset = optind;
+			}
+			break;
 		}
-	} else if (argc == optind && args->from_pid) {
-		// Neither a function (-g, -n, or -c), nor a program to
-		// run is given
-		args->cmd = SCHED_CORE_CMD_GET;
 	}
 
-	verify_arguments(args);
+	if (argc <= optind) {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
+			bad_usage(
+				"--new: requires either a -p/--pid or a command");
+		}
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
+			bad_usage(
+				"--copy: requires either a -d/--dest or a command");
+		}
+	}
 }
 
 int main(int argc, char **argv)
 {
-	struct args arguments = { 0 };
-	arguments.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	struct args args = { 0 };
+	args.cmd = SCHED_CORE_CMD_GET;
+	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
 
 	setlocale(LC_ALL, "");
 	bindtextdomain(PACKAGE, LOCALEDIR);
 	textdomain(PACKAGE);
 	close_stdout_atexit();
 
-	parse_arguments(argc, argv, &arguments);
+	parse_arguments(argc, argv, &args);
+
+	cookie_t cookie = 0;
 
-	unsigned long cookie = 0;
-	switch (arguments.cmd) {
+	switch (args.cmd) {
 	case SCHED_CORE_CMD_GET:
-		cookie = core_sched_get_cookie(&arguments);
-		if (cookie) {
-			printf("core scheduling cookie of pid %d is 0x%lx\n",
-			       arguments.from_pid, cookie);
+		if (args.pid) {
+			cookie = core_sched_get_cookie(args.pid);
+			if (cookie) {
+				printf("%s: cookie of pid %d is 0x%lx\n",
+				       program_invocation_short_name, args.pid,
+				       cookie);
+			} else {
+				errx(ENODATA,
+				     "pid %d doesn't have a core scheduling cookie",
+				     args.pid);
+			}
 		} else {
-			printf("pid %d doesn't have a core scheduling cookie\n",
-			       arguments.from_pid);
-			exit(1);
+			usage();
+			exit(0);
 		}
 		break;
-	case SCHED_CORE_CMD_CREATE:
-		core_sched_create_cookie(&arguments);
+	case SCHED_CORE_CMD_NEW:
+		if (args.pid) {
+			core_sched_create_cookie(args.pid, args.type);
+			core_sched_get_and_print_cookie(args.pid);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
 		break;
 	case SCHED_CORE_CMD_COPY:
-		core_sched_copy_cookie(&arguments);
-		break;
-	case SCHED_CORE_CMD_EXEC:
-		core_sched_exec_with_cookie(&arguments, argv);
+		if (args.dest) {
+			core_sched_copy_cookie(args.pid, args.dest, args.type);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
 		break;
 	default:
 		usage();
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PATCH v2 1/1] coresched: Manage core scheduling cookies for tasks
  2024-03-26 18:13       ` Phil Auld
  2024-03-27 12:43         ` [PATCH v2 " Thijs Raymakers
@ 2024-03-27 12:43         ` Thijs Raymakers
  2024-03-27 14:09           ` Phil Auld
  1 sibling, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-03-27 12:43 UTC (permalink / raw)
  To: pauld; +Cc: kzak, util-linux, Thijs Raymakers

Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
Thanks: Phil Auld for the valuable feedback
---
 .gitignore                  |   1 +
 bash-completion/coresched   |   0
 configure.ac                |  12 +-
 meson.build                 |  16 +-
 meson_options.txt           |   2 +-
 schedutils/Makemodule.am    |   8 +
 schedutils/coresched.1.adoc |  16 ++
 schedutils/coresched.c      | 363 ++++++++++++++++++++++++++++++++++++
 8 files changed, 412 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 9600ce49f..9a2c04e8f 100644
--- a/meson.build
+++ b/meson.build
@@ -3111,13 +3111,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+	       'schedutils/uclampset.1.adoc',
+         'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..60a21cd01
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,16 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..537281fdb
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,363 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "strutils.h"
+
+// These definitions might not be defined, even if the
+// prctl interface accepts them.
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int core_sched_type_t;
+typedef unsigned long cookie_t;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} core_sched_cmd_t;
+
+struct args {
+	pid_t pid;
+	pid_t dest;
+	core_sched_type_t type;
+	core_sched_cmd_t cmd;
+	int exec_argv_offset;
+};
+
+cookie_t core_sched_get_cookie(pid_t pid);
+void core_sched_create_cookie(pid_t pid, core_sched_type_t type);
+void core_sched_pull_cookie(pid_t from);
+void core_sched_push_cookie(pid_t to, core_sched_type_t type);
+void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type);
+void core_sched_exec_with_cookie(struct args *args, char **argv);
+void core_sched_get_and_print_cookie(pid_t pid);
+
+core_sched_type_t parse_core_sched_type(char *str);
+bool verify_arguments(struct args *args);
+void parse_arguments(int argc, char **argv, struct args *args);
+void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
+static void __attribute__((__noreturn__)) usage(void);
+
+#define bad_usage(FMT...) \
+	warnx(FMT);       \
+	errtryhelp(EINVAL);
+
+#define check_coresched_in_kernel(errno)                              \
+	if (errno == EINVAL) {                                        \
+		warnx("Does your kernel support CONFIG_SCHED_CORE?"); \
+	}
+
+cookie_t core_sched_get_cookie(pid_t pid)
+{
+	cookie_t cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
+		check_coresched_in_kernel(errno);
+		err(errno, "Failed to get cookie from PID %d", pid);
+	}
+	return cookie;
+}
+
+void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
+		check_coresched_in_kernel(errno);
+		err(errno, "Failed to create cookie for PID %d", pid);
+	}
+}
+
+void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
+		check_coresched_in_kernel(errno);
+		err(errno, "Failed to pull cookie from PID %d", from);
+	}
+}
+
+void core_sched_push_cookie(pid_t to, core_sched_type_t type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
+		check_coresched_in_kernel(errno);
+		err(errno, "Failed to push cookie to PID %d", to);
+	}
+}
+
+void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type)
+{
+	core_sched_pull_cookie(from);
+	cookie_t before = core_sched_get_cookie(from);
+	core_sched_push_cookie(to, to_type);
+	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
+	       program_invocation_short_name, before, from, to);
+}
+
+void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	if (!args->exec_argv_offset) {
+		usage();
+	}
+
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->pid) {
+		core_sched_pull_cookie(args->pid);
+		core_sched_get_and_print_cookie(args->pid);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		cookie_t after = core_sched_get_cookie(pid);
+		printf("%s: set cookie of PID %d to 0x%lx\n",
+		       program_invocation_short_name, pid, after);
+	}
+
+	if (execvp(argv[0], argv)) {
+		errexec(argv[0]);
+	}
+}
+
+void core_sched_get_and_print_cookie(pid_t pid)
+{
+	cookie_t after = core_sched_get_cookie(pid);
+	printf("%s: set cookie of PID %d to 0x%lx\n",
+	       program_invocation_short_name, pid, after);
+}
+
+core_sched_type_t parse_core_sched_type(char *str)
+{
+	if (!strncmp(str, "pid\0", 4)) {
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	} else if (!strncmp(str, "tgid\0", 5)) {
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	} else if (!strncmp(str, "pgid\0", 5)) {
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+	}
+
+	bad_usage("'%s' is an invalid option. Must be one of pid/tgid/pgid",
+		  str);
+	__builtin_unreachable();
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
+		  "                      execute a program with a new cookie."),
+		stdout);
+	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
+		  "                      either another PID, or copy it to a new program"),
+		stdout);
+	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
+		  "   the PID provided via --pid.\n"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
+	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
+		  "                      the destination PID where to copy the cookie to."),
+		stdout);
+	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
+		  "                      when a new core scheduling cookie is created.\n"
+		  "                      Can be one of the following: pid, tgid or pgid.\n"
+		  "                      The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fprintf(stdout,
+		USAGE_HELP_OPTIONS(
+			20)); /* char offset to align option descriptions */
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+void parse_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "new", no_argument, NULL, 'n' },
+		{ "copy", no_argument, NULL, 'c' },
+		{ "pid", required_argument, NULL, 'p' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "type", required_argument, NULL, 't' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+	static const ul_excl_t excl[] = {
+		{ 'c', 'n' }, // Cannot do both --new and --copy
+		{ 'd', 'n' }, // Cannot have both --new and --dest
+		{ 0 }
+	};
+
+	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+	while ((c = getopt_long(argc, argv, "ncp:d:t:Vh", longopts, NULL)) !=
+	       -1) {
+		err_exclusive_options(c, longopts, excl, excl_st);
+		switch (c) {
+		case 'n':
+			args->cmd = SCHED_CORE_CMD_NEW;
+			break;
+		case 'c':
+			args->cmd = SCHED_CORE_CMD_COPY;
+			break;
+		case 'p':
+			args->pid = strtopid_or_err(
+				optarg, "Failed to parse PID for -p/--pid");
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, "Failed to parse PID for -d/--dest");
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
+		bad_usage("--copy: requires a -p/--pid");
+	}
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage("Unknown command");
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->pid) {
+				bad_usage(
+					"--new: cannot accept both a -p/--pid and a command");
+			} else {
+				args->exec_argv_offset = optind;
+			}
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest) {
+				bad_usage(
+					"--copy: cannot accept both a destination PID "
+					"-d/--dest and a command")
+			} else {
+				args->exec_argv_offset = optind;
+			}
+			break;
+		}
+	}
+
+	if (argc <= optind) {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
+			bad_usage(
+				"--new: requires either a -p/--pid or a command");
+		}
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
+			bad_usage(
+				"--copy: requires either a -d/--dest or a command");
+		}
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { 0 };
+	args.cmd = SCHED_CORE_CMD_GET;
+	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_arguments(argc, argv, &args);
+
+	cookie_t cookie = 0;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		if (args.pid) {
+			cookie = core_sched_get_cookie(args.pid);
+			if (cookie) {
+				printf("%s: cookie of pid %d is 0x%lx\n",
+				       program_invocation_short_name, args.pid,
+				       cookie);
+			} else {
+				errx(ENODATA,
+				     "pid %d doesn't have a core scheduling cookie",
+				     args.pid);
+			}
+		} else {
+			usage();
+			exit(0);
+		}
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.pid) {
+			core_sched_create_cookie(args.pid, args.type);
+			core_sched_get_and_print_cookie(args.pid);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.dest) {
+			core_sched_copy_cookie(args.pid, args.dest, args.type);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
+		break;
+	default:
+		usage();
+		exit(1);
+	}
+}
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v2 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-27 12:43         ` [PATCH v2 " Thijs Raymakers
@ 2024-03-27 13:21           ` Phil Auld
  2024-03-27 14:18             ` Phil Auld
  0 siblings, 1 reply; 42+ messages in thread
From: Phil Auld @ 2024-03-27 13:21 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux


Hi Thijs,

On Wed, Mar 27, 2024 at 01:43:20PM +0100 Thijs Raymakers wrote:
> Hi Phil,
> 
> Op 26-03-2024 om 19:13 schreef Phil Auld:
> > On Tue, Mar 26, 2024 at 06:49:07PM +0100 Thijs Raymakers wrote:
> >> Op 26-03-2024 om 15:41 schreef Phil Auld:
> >>> On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
> >>>> - How should the program behave if the prctl core scheduling API is not
> >>>>   available? It has been in Linus' tree since november 2021
> >>>>   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
> >>>>   disabled with CONFIG_SCHED_CORE=n
> >>> This one is tricky. If I recall the error you get is ambiguous in this case
> >>> (EINVAL, which can be returned for other reasons). It would be nice to be
> >>> able to report that specifically. 
> >> In general, `prctl` does indeed return EINVAL if the operation is not
> >> recognized, or not supported on the system. The `PR_SCHED_CORE`
> >> operation itself only returns EINVAL if it is called with
> >>   - an invalid operation
> >>   - an invalid type
> >>   - a negative PID
> >>   - an invalid cookie store address (for PR_SCHED_CORE_GET)
> >> Assuming that all these cases are prevented by the util, we could
> >> interpret a EINVAL as a sign that PR_SCHED_CORE is not supported on
> >> the system.
> > Fair enough. Could say something like "got EINVAL. Does your kernel
> > support CONFIG_SCHED_CORE?". 
> 
> I've added this as a warning in case a prctl call returns EINVAL. I
> haven't tested it on a kernel with CONFIG_SCHED_CORE=n yet but I will do
> that later.

Thanks. I'll refresh and try it again. My build machine does not have
CONFIG_CORE_SCHED so that's easy for me to test :)

> 
> > There is also
> >        err_exclusive_options(c, longopts, excl, excl_st);
> > 
> > in the optuils.h code which can handle the tests for mutually exclusive
> > arguments.
> > 
> > Thomas pointed me to that. It works nicely and can remove some of the extra
> > checks (once you get it setup). And the error then looks the same as other
> > util-linux progs. 
> 
> Good suggestion, I replaced some of the earlier mutually exclusive
> checks with a call to err_exclusive_options.
> 
> > You might consider "errtryhelp(EXIT_FAILURE);"  in usage failures. A lot of
> > the progs (incl taskset which is my model for this) do that. Then you only
> > do the full usage when given -h/--help.
> 
> This does indeed feel a slightly nicer to use. I've added this in case a
> of a usage failure. In case no options are given, it defaults to
> printing the --help page. This matches the behavior of some other utils like
> uclampset, and saves some time.
> 
> 
> Op 26-03-2024 om 21:16 schreef Phil Auld:
> > So "-n/--new" creates a new cookie and so does "". Just one on an existing task
> > and one on the exec'd task.  Seems inconsistent, no?
> 
> Hmmm, I didn't intially see this as inconsistent behavior but I get why
> you say this. I've modified this now to have -n/--new deal both with
> assigning cookies to existing processes and to spawn new processes.
> 
> 
> Op 26-03-2024 om 21:17 schreef Phil Auld:
> > Btw, did you try coreset to see if it does what you need?
> 
> I did take a look at it. Thanks for adding the functionality of copying
> a cookie from/to an existing PID. The commands take a bit of getting
> used to for me, so I guess it is just a matter of preference of what feels
> natural to use.

Yeah, fair enough.  I wrote it so it makes sense to me ;)

> Some things that weren't directly clear to me:
> - Both `coreset ls` and `coreset -n ls` spawn a new program. If I
>   understood it correctly, the former spawns the program without
>   a cookie, and the latter spawns it with a cookie. I'm not entirely
>   sure what the point of the former would be, as the cookie would either
>   be 0 or equal to the cookie of the `coreset` process itself since the
>   cookie in inherited across execs. It is not necessary to run the
>   provided program in that case.

It's more informational. Since "get" is essentially the default, coreset ls
would run ls and report its cookie. Not totally useful but more for
consistency and it tells you that your shell does or does not have a
cookie set.

> - Not entirely sure what the difference between --dest and --to is.
>   The names of these options are similar in meaning and do almost the
>   same thing.

That's the one I added to cover your use case. Dest is specifically only
used with --copy. It makes copy into a pull_then_push rather than a
 pull_then_exec. 

--to is just a push.  It will push the current task's cookie to
<pid>.  This is also useful to clear a tasks cookie if your current shell does
not have a cookie set.

With your coresched tool I think you have to pick a random pid that you
know does not have a cookie and use that to clear a different pid's cookie.

> - It is not clear in what cases --scope is ignored and in which
>   cases it is necessary.

Fair enough.  Also I have not yet cribbed your string version of scope.

> - What does `coreset -c -s 0 -d 2 -p 1 ls` do? Does it first copy the
>   cookie from PID 1 to PID 2, and then run `ls` with the same or
>   different cookie? Or the other way around?
>

It should copy pid 1's cookie to pid 2 and report that it's ignoring the
extra input.  It would not run ls in this case. 

-d is the destination pid in the case of copy.  So --pid is copy from (in
both exec and --dest cases). 

-s 0 is redundant since that's the default in coreset.

> Now I do see what you meant earlier with imitating the behavior of the
> `taskset` command. I don't think this is strictly necessary since it is
> a different utility program, but I can understand that it can be useful
> for people if the command behaves similar to a program that they already
> know and have certain expectations of.
> 
> I've modified the options of coresched to be more in line with these
> expectations, while also addressing the things I mentioned above.
> 
> Usage:
>  coresched [-p PID]
>  coresched --new [-t <TYPE>] -p <PID>
>  coresched --new [-t <TYPE>] -- PROGRAM [ARGS...]
>  coresched --copy -p <PID> [-t <TYPE>] -d <PID>
>  coresched --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]
> 
> This follows the taskset's behavior (i.e. perform the same operation
> on either an existing PID or on a new program) and taskset's arguments
> (-p for PID instead of the -s I had earlier) more closely than before.
>

I think I like the above, nice.

> I've attached an interdiff of the util compared to the previous version
> that I sent yesterday. I think that this version encompases both our use
> cases quite nicely.

Let me give it a try. I may or may not have time to get to it today.
Other priorities arise. We'll see.

I do still prefer coreset as a name but I won't die on that hill :)


Cheers,
Phil

> 
> Thijs
> 
> Thijs Raymakers (1):
>   coresched: Manage core scheduling cookies for tasks
> 
>  .gitignore                  |   1 +
>  bash-completion/coresched   |   0
>  configure.ac                |  12 +-
>  meson.build                 |  16 +-
>  meson_options.txt           |   2 +-
>  schedutils/Makemodule.am    |   8 +
>  schedutils/coresched.1.adoc |  16 ++
>  schedutils/coresched.c      | 363 ++++++++++++++++++++++++++++++++++++
>  8 files changed, 412 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
> 
> Interdiff against v1:
> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> index 17d775f2d..537281fdb 100644
> --- a/schedutils/coresched.c
> +++ b/schedutils/coresched.c
> @@ -12,10 +12,12 @@
>  #include <stdio.h>
>  #include <sys/prctl.h>
>  #include <sys/wait.h>
> +#include <unistd.h>
>  
>  #include "c.h"
>  #include "closestream.h"
>  #include "nls.h"
> +#include "optutils.h"
>  #include "strutils.h"
>  
>  // These definitions might not be defined, even if the
> @@ -26,32 +28,34 @@
>  #ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
>  #define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
>  #endif
> +
>  #ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
>  #define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
>  #endif
>  
>  typedef int core_sched_type_t;
> +typedef unsigned long cookie_t;
>  typedef enum {
> -	SCHED_CORE_CMD_EXEC = 0,
> -	SCHED_CORE_CMD_GET = 1,
> -	SCHED_CORE_CMD_CREATE = 2,
> -	SCHED_CORE_CMD_COPY = 4,
> +	SCHED_CORE_CMD_GET,
> +	SCHED_CORE_CMD_NEW,
> +	SCHED_CORE_CMD_COPY,
>  } core_sched_cmd_t;
>  
>  struct args {
> -	pid_t from_pid;
> -	pid_t to_pid;
> +	pid_t pid;
> +	pid_t dest;
>  	core_sched_type_t type;
>  	core_sched_cmd_t cmd;
>  	int exec_argv_offset;
>  };
>  
> -unsigned long core_sched_get_cookie(struct args *args);
> -void core_sched_create_cookie(struct args *args);
> +cookie_t core_sched_get_cookie(pid_t pid);
> +void core_sched_create_cookie(pid_t pid, core_sched_type_t type);
>  void core_sched_pull_cookie(pid_t from);
>  void core_sched_push_cookie(pid_t to, core_sched_type_t type);
> -void core_sched_copy_cookie(struct args *args);
> +void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type);
>  void core_sched_exec_with_cookie(struct args *args, char **argv);
> +void core_sched_get_and_print_cookie(pid_t pid);
>  
>  core_sched_type_t parse_core_sched_type(char *str);
>  bool verify_arguments(struct args *args);
> @@ -59,22 +63,31 @@ void parse_arguments(int argc, char **argv, struct args *args);
>  void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
>  static void __attribute__((__noreturn__)) usage(void);
>  
> -unsigned long core_sched_get_cookie(struct args *args)
> +#define bad_usage(FMT...) \
> +	warnx(FMT);       \
> +	errtryhelp(EINVAL);
> +
> +#define check_coresched_in_kernel(errno)                              \
> +	if (errno == EINVAL) {                                        \
> +		warnx("Does your kernel support CONFIG_SCHED_CORE?"); \
> +	}
> +
> +cookie_t core_sched_get_cookie(pid_t pid)
>  {
> -	unsigned long cookie = 0;
> -	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, args->from_pid,
> +	cookie_t cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
>  		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> -		err(errno, "Failed to get cookie from PID %d", args->from_pid);
> +		check_coresched_in_kernel(errno);
> +		err(errno, "Failed to get cookie from PID %d", pid);
>  	}
>  	return cookie;
>  }
>  
> -void core_sched_create_cookie(struct args *args)
> +void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
>  {
> -	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, args->to_pid, args->type,
> -		  0)) {
> -		err(errno, "Failed to create cookie for PID %d",
> -		    args->from_pid);
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
> +		check_coresched_in_kernel(errno);
> +		err(errno, "Failed to create cookie for PID %d", pid);
>  	}
>  }
>  
> @@ -82,6 +95,7 @@ void core_sched_pull_cookie(pid_t from)
>  {
>  	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
>  		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
> +		check_coresched_in_kernel(errno);
>  		err(errno, "Failed to pull cookie from PID %d", from);
>  	}
>  }
> @@ -89,14 +103,18 @@ void core_sched_pull_cookie(pid_t from)
>  void core_sched_push_cookie(pid_t to, core_sched_type_t type)
>  {
>  	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
> +		check_coresched_in_kernel(errno);
>  		err(errno, "Failed to push cookie to PID %d", to);
>  	}
>  }
>  
> -void core_sched_copy_cookie(struct args *args)
> +void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type)
>  {
> -	core_sched_pull_cookie(args->from_pid);
> -	core_sched_push_cookie(args->to_pid, args->type);
> +	core_sched_pull_cookie(from);
> +	cookie_t before = core_sched_get_cookie(from);
> +	core_sched_push_cookie(to, to_type);
> +	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
> +	       program_invocation_short_name, before, from, to);
>  }
>  
>  void core_sched_exec_with_cookie(struct args *args, char **argv)
> @@ -111,11 +129,15 @@ void core_sched_exec_with_cookie(struct args *args, char **argv)
>  	// If a source PID is provided, try to copy the cookie from
>  	// that PID. Otherwise, create a brand new cookie with the
>  	// provided type.
> -	if (args->from_pid) {
> -		core_sched_pull_cookie(args->from_pid);
> +	if (args->pid) {
> +		core_sched_pull_cookie(args->pid);
> +		core_sched_get_and_print_cookie(args->pid);
>  	} else {
> -		args->to_pid = getpid();
> -		core_sched_create_cookie(args);
> +		pid_t pid = getpid();
> +		core_sched_create_cookie(pid, args->type);
> +		cookie_t after = core_sched_get_cookie(pid);
> +		printf("%s: set cookie of PID %d to 0x%lx\n",
> +		       program_invocation_short_name, pid, after);
>  	}
>  
>  	if (execvp(argv[0], argv)) {
> @@ -123,6 +145,13 @@ void core_sched_exec_with_cookie(struct args *args, char **argv)
>  	}
>  }
>  
> +void core_sched_get_and_print_cookie(pid_t pid)
> +{
> +	cookie_t after = core_sched_get_cookie(pid);
> +	printf("%s: set cookie of PID %d to 0x%lx\n",
> +	       program_invocation_short_name, pid, after);
> +}
> +
>  core_sched_type_t parse_core_sched_type(char *str)
>  {
>  	if (!strncmp(str, "pid\0", 4)) {
> @@ -133,156 +162,96 @@ core_sched_type_t parse_core_sched_type(char *str)
>  		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
>  	}
>  
> -	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
> -	     str);
> +	bad_usage("'%s' is an invalid option. Must be one of pid/tgid/pgid",
> +		  str);
>  	__builtin_unreachable();
>  }
>  
>  static void __attribute__((__noreturn__)) usage(void)
>  {
>  	fputs(USAGE_HEADER, stdout);
> -	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
> -	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
> +	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
>  		program_invocation_short_name);
> -	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
>  		program_invocation_short_name);
> -	fprintf(stdout, _(" %s [-s <PID>] -- PROGRAM ARGS... \n"),
> +	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
>  		program_invocation_short_name);
>  
>  	fputs(USAGE_SEPARATOR, stdout);
>  	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
>  
>  	fputs(USAGE_FUNCTIONS, stdout);
> -	fputsln(_(" -g, --get <PID>         get the core scheduling cookie of a PID"),
> +	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
> +		  "                      execute a program with a new cookie."),
>  		stdout);
> -	fputsln(_(" -n, --new <PID>         assign a new core scheduling cookie to PID"),
> +	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
> +		  "                      either another PID, or copy it to a new program"),
>  		stdout);
> -	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
> -		  "                           another PID, requires the --source and --dest option"),
> +	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
> +		  "   the PID provided via --pid.\n"),
>  		stdout);
>  
>  	fputs(USAGE_OPTIONS, stdout);
> -	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
> -		stdout);
> -	fputsln(_(" -d, --dest <PID>        where to copy the core scheduling cookie to."),
> +	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
> +	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
> +		  "                      the destination PID where to copy the cookie to."),
>  		stdout);
> -	fputsln(_(" -t, --type              type of the destination PID, or the type of\n"
> -		  "                           the PID when a new core scheduling cookie\n"
> -		  "                           is created. Can be one of the following:\n"
> -		  "                           pid, tgid or pgid. Defaults to tgid."),
> +	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
> +		  "                      when a new core scheduling cookie is created.\n"
> +		  "                      Can be one of the following: pid, tgid or pgid.\n"
> +		  "                      The default is tgid."),
>  		stdout);
>  	fputs(USAGE_SEPARATOR, stdout);
>  	fprintf(stdout,
>  		USAGE_HELP_OPTIONS(
> -			25)); /* char offset to align option descriptions */
> +			20)); /* char offset to align option descriptions */
>  	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
>  	exit(EXIT_SUCCESS);
>  }
>  
> -bool verify_arguments(struct args *args)
> -{
> -	// Check if the value of args->cmd is a power of 2
> -	// In that case, only a single function option was set.
> -	if (args->cmd & (args->cmd - 1)) {
> -		errx(EINVAL,
> -		     "Cannot do more than one function at a time. See %s --help",
> -		     program_invocation_short_name);
> -	}
> -
> -	switch (args->cmd) {
> -	case SCHED_CORE_CMD_GET:
> -		if (args->to_pid) {
> -			errx(EINVAL,
> -			     "Cannot use -d/--dest with this -g/--get. See %s --help",
> -			     program_invocation_short_name);
> -		}
> -		break;
> -	case SCHED_CORE_CMD_CREATE:
> -		if (args->from_pid) {
> -			errx(EINVAL,
> -			     "Cannot use -s/--source with this -n/--new. See %s --help",
> -			     program_invocation_short_name);
> -		}
> -		break;
> -	case SCHED_CORE_CMD_COPY:
> -		if (!args->from_pid) {
> -			errx(EINVAL,
> -			     "-s/--source PID is required when copying");
> -		}
> -		if (!args->to_pid) {
> -			errx(EINVAL, "-d/--dest PID is required when copying");
> -		}
> -		break;
> -	case SCHED_CORE_CMD_EXEC:
> -		if (args->to_pid) {
> -			errx(EINVAL,
> -			     "Cannot use -d/--dest when spawning a program. See %s --help",
> -			     program_invocation_short_name);
> -		}
> -		break;
> -	}
> -	return true;
> -}
> -
> -void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg)
> -{
> -	if (*dest) {
> -		errx(EINVAL, "Ambigious usage: %s", err_msg);
> -	} else {
> -		*dest = src;
> -	}
> -}
> -
> -static const char *ERR_MSG_MULTIPLE_SOURCE_PIDS =
> -	"Multiple source PIDs defined";
>  void parse_arguments(int argc, char **argv, struct args *args)
>  {
>  	int c;
> -	pid_t tmp;
>  
>  	static const struct option longopts[] = {
> -		{ "get", required_argument, NULL, 'g' },
> -		{ "new", required_argument, NULL, 'n' },
> +		{ "new", no_argument, NULL, 'n' },
>  		{ "copy", no_argument, NULL, 'c' },
> -		{ "source", required_argument, NULL, 's' },
> -		{ "destination", required_argument, NULL, 'd' },
> +		{ "pid", required_argument, NULL, 'p' },
> +		{ "dest", required_argument, NULL, 'd' },
>  		{ "type", required_argument, NULL, 't' },
>  		{ "version", no_argument, NULL, 'V' },
>  		{ "help", no_argument, NULL, 'h' },
>  		{ NULL, 0, NULL, 0 }
>  	};
> +	static const ul_excl_t excl[] = {
> +		{ 'c', 'n' }, // Cannot do both --new and --copy
> +		{ 'd', 'n' }, // Cannot have both --new and --dest
> +		{ 0 }
> +	};
>  
> -	while ((c = getopt_long(argc, argv, "g:n:cs:d:t:Vh", longopts, NULL)) !=
> -	       -1)
> +	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
> +
> +	while ((c = getopt_long(argc, argv, "ncp:d:t:Vh", longopts, NULL)) !=
> +	       -1) {
> +		err_exclusive_options(c, longopts, excl, excl_st);
>  		switch (c) {
> -		case 'g':
> -			args->cmd |= SCHED_CORE_CMD_GET;
> -			tmp = strtopid_or_err(
> -				optarg, "Failed to parse PID for -g/--get");
> -			set_pid_or_err(&args->from_pid, tmp,
> -				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
> -			break;
>  		case 'n':
> -			args->cmd |= SCHED_CORE_CMD_CREATE;
> -			tmp = strtopid_or_err(
> -				optarg, "Failed to parse PID for -n/--new");
> -			set_pid_or_err(&args->to_pid, tmp,
> -				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
> +			args->cmd = SCHED_CORE_CMD_NEW;
>  			break;
>  		case 'c':
> -			args->cmd |= SCHED_CORE_CMD_COPY;
> +			args->cmd = SCHED_CORE_CMD_COPY;
>  			break;
> -		case 's':
> -			tmp = strtopid_or_err(
> -				optarg, "Failed to parse PID for -s/--source");
> -			set_pid_or_err(&args->from_pid, tmp,
> -				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
> +		case 'p':
> +			args->pid = strtopid_or_err(
> +				optarg, "Failed to parse PID for -p/--pid");
>  			break;
>  		case 'd':
> -			tmp = strtopid_or_err(
> +			args->dest = strtopid_or_err(
>  				optarg, "Failed to parse PID for -d/--dest");
> -			set_pid_or_err(&args->to_pid, tmp,
> -				       "Multiple destination PIDs defined");
>  			break;
>  		case 't':
>  			args->type = parse_core_sched_type(optarg);
> @@ -294,57 +263,98 @@ void parse_arguments(int argc, char **argv, struct args *args)
>  		default:
>  			errtryhelp(EXIT_FAILURE);
>  		}
> +	}
>  
> +	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
> +		bad_usage("--copy: requires a -p/--pid");
> +	}
> +
> +	// More arguments have been passed, which means that the user wants to run
> +	// another program with a core scheduling cookie.
>  	if (argc > optind) {
> -		if (args->cmd == SCHED_CORE_CMD_EXEC) {
> -			args->exec_argv_offset = optind;
> -		} else {
> -			// -g, -n or -c AND a program to run is provided
> -			errx(EINVAL, "bad usage, see %s --help",
> -			     program_invocation_short_name);
> +		switch (args->cmd) {
> +		case SCHED_CORE_CMD_GET:
> +			bad_usage("Unknown command");
> +			break;
> +		case SCHED_CORE_CMD_NEW:
> +			if (args->pid) {
> +				bad_usage(
> +					"--new: cannot accept both a -p/--pid and a command");
> +			} else {
> +				args->exec_argv_offset = optind;
> +			}
> +			break;
> +		case SCHED_CORE_CMD_COPY:
> +			if (args->dest) {
> +				bad_usage(
> +					"--copy: cannot accept both a destination PID "
> +					"-d/--dest and a command")
> +			} else {
> +				args->exec_argv_offset = optind;
> +			}
> +			break;
>  		}
> -	} else if (argc == optind && args->from_pid) {
> -		// Neither a function (-g, -n, or -c), nor a program to
> -		// run is given
> -		args->cmd = SCHED_CORE_CMD_GET;
>  	}
>  
> -	verify_arguments(args);
> +	if (argc <= optind) {
> +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
> +			bad_usage(
> +				"--new: requires either a -p/--pid or a command");
> +		}
> +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
> +			bad_usage(
> +				"--copy: requires either a -d/--dest or a command");
> +		}
> +	}
>  }
>  
>  int main(int argc, char **argv)
>  {
> -	struct args arguments = { 0 };
> -	arguments.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +	struct args args = { 0 };
> +	args.cmd = SCHED_CORE_CMD_GET;
> +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
>  
>  	setlocale(LC_ALL, "");
>  	bindtextdomain(PACKAGE, LOCALEDIR);
>  	textdomain(PACKAGE);
>  	close_stdout_atexit();
>  
> -	parse_arguments(argc, argv, &arguments);
> +	parse_arguments(argc, argv, &args);
> +
> +	cookie_t cookie = 0;
>  
> -	unsigned long cookie = 0;
> -	switch (arguments.cmd) {
> +	switch (args.cmd) {
>  	case SCHED_CORE_CMD_GET:
> -		cookie = core_sched_get_cookie(&arguments);
> -		if (cookie) {
> -			printf("core scheduling cookie of pid %d is 0x%lx\n",
> -			       arguments.from_pid, cookie);
> +		if (args.pid) {
> +			cookie = core_sched_get_cookie(args.pid);
> +			if (cookie) {
> +				printf("%s: cookie of pid %d is 0x%lx\n",
> +				       program_invocation_short_name, args.pid,
> +				       cookie);
> +			} else {
> +				errx(ENODATA,
> +				     "pid %d doesn't have a core scheduling cookie",
> +				     args.pid);
> +			}
>  		} else {
> -			printf("pid %d doesn't have a core scheduling cookie\n",
> -			       arguments.from_pid);
> -			exit(1);
> +			usage();
> +			exit(0);
>  		}
>  		break;
> -	case SCHED_CORE_CMD_CREATE:
> -		core_sched_create_cookie(&arguments);
> +	case SCHED_CORE_CMD_NEW:
> +		if (args.pid) {
> +			core_sched_create_cookie(args.pid, args.type);
> +			core_sched_get_and_print_cookie(args.pid);
> +		} else {
> +			core_sched_exec_with_cookie(&args, argv);
> +		}
>  		break;
>  	case SCHED_CORE_CMD_COPY:
> -		core_sched_copy_cookie(&arguments);
> -		break;
> -	case SCHED_CORE_CMD_EXEC:
> -		core_sched_exec_with_cookie(&arguments, argv);
> +		if (args.dest) {
> +			core_sched_copy_cookie(args.pid, args.dest, args.type);
> +		} else {
> +			core_sched_exec_with_cookie(&args, argv);
> +		}
>  		break;
>  	default:
>  		usage();
> -- 
> 2.44.0
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v2 1/1] coresched: Manage core scheduling cookies for tasks
  2024-03-27 12:43         ` [PATCH v2 1/1] " Thijs Raymakers
@ 2024-03-27 14:09           ` Phil Auld
  0 siblings, 0 replies; 42+ messages in thread
From: Phil Auld @ 2024-03-27 14:09 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux


Hi Thijs,

On Wed, Mar 27, 2024 at 01:43:21PM +0100 Thijs Raymakers wrote:
> Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> Thanks: Phil Auld for the valuable feedback
> ---
>  .gitignore                  |   1 +
>  bash-completion/coresched   |   0
>  configure.ac                |  12 +-
>  meson.build                 |  16 +-
>  meson_options.txt           |   2 +-
>  schedutils/Makemodule.am    |   8 +
>  schedutils/coresched.1.adoc |  16 ++
>  schedutils/coresched.c      | 363 ++++++++++++++++++++++++++++++++++++
>  8 files changed, 412 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
> 
> diff --git a/.gitignore b/.gitignore
> index 6ecbfa7fe..316f3cdcc 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -94,6 +94,7 @@ ylwrap
>  /colcrt
>  /colrm
>  /column
> +/coresched
>  /ctrlaltdel
>  /delpart
>  /dmesg
> diff --git a/bash-completion/coresched b/bash-completion/coresched
> new file mode 100644
> index 000000000..e69de29bb
> diff --git a/configure.ac b/configure.ac
> index ab7c98636..3a189a075 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
>  AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
>  
>  # build_schedutils= is just configure-only variable to control
> -# ionice, taskset and chrt
> +# ionice, taskset, coresched and chrt
>  AC_ARG_ENABLE([schedutils],
> -  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
> +  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
>    [], [UL_DEFAULT_ENABLE([schedutils], [check])]
>  )
>  
> @@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
>  AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
>  
>  
> +UL_ENABLE_ALIAS([coresched], [schedutils])
> +UL_BUILD_INIT([coresched])
> +UL_REQUIRES_SYSCALL_CHECK([coresched],
> +	[UL_CHECK_SYSCALL([prctl])],
> +	[prctl])
> +AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
> +
> +
>  have_schedsetter=no
>  AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
>        [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
> diff --git a/meson.build b/meson.build
> index 9600ce49f..9a2c04e8f 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -3111,13 +3111,23 @@ exe4 = executable(
>    install : opt,
>    build_by_default : opt)
>  
> +exe5 = executable(
> +  'coresched',
> +  'schedutils/coresched.c',
> +  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, exe2, exe3, exe4]
> +  exes += [exe, exe2, exe3, exe4, exe5]
>    manadocs += ['schedutils/chrt.1.adoc',
>                 'schedutils/ionice.1.adoc',
>                 'schedutils/taskset.1.adoc',
> -	       'schedutils/uclampset.1.adoc']
> -  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
> +	       'schedutils/uclampset.1.adoc',
> +         'schedutils/coresched.1.adoc']
> +  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
>  endif
>  
>  ############################################################
> diff --git a/meson_options.txt b/meson_options.txt
> index 7b8cf3f35..3405c1b73 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
>  option('build-setterm', type : 'feature',
>         description : 'build setterm')
>  option('build-schedutils', type : 'feature',
> -       description : 'build chrt, ionice, taskset')
> +       description : 'build chrt, ionice, taskset, coresched')
>  option('build-wall', type : 'feature',
>         description : 'build wall')
>  option('build-write', type : 'feature',
> diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
> index 1040da85f..0cb655401 100644
> --- a/schedutils/Makemodule.am
> +++ b/schedutils/Makemodule.am
> @@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
>  uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
>  uclampset_LDADD = $(LDADD) libcommon.la
>  endif
> +
> +if BUILD_CORESCHED
> +usrbin_exec_PROGRAMS += coresched
> +MANPAGES += schedutils/coresched.1
> +dist_noinst_DATA += schedutils/coresched.1.adoc
> +coresched_SOURCES = schedutils/coresched.c
> +coresched_LDADD = $(LDADD) libcommon.la
> +endif
> diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
> new file mode 100644
> index 000000000..60a21cd01
> --- /dev/null
> +++ b/schedutils/coresched.1.adoc
> @@ -0,0 +1,16 @@
> +//po4a: entry man manual
> +////
> +coresched(1) manpage
> +////
> += coresched(1)
> +:doctype: manpage
> +:man manual: User Commands
> +:man source: util-linux {release-version}
> +:page-layout: base
> +:command: coresched
> +:colon: :
> +:copyright: ©
> +
> +== NAME
> +
> +coresched - manage core scheduling cookies for tasks
> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> new file mode 100644
> index 000000000..537281fdb
> --- /dev/null
> +++ b/schedutils/coresched.c
> @@ -0,0 +1,363 @@
> +/**
> + * SPDX-License-Identifier: EUPL-1.2
> + *
> + * coresched.c - manage core scheduling cookies for tasks
> + *
> + * Copyright (C) 2024 Thijs Raymakers
> + * Licensed under the EUPL v1.2
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "optutils.h"
> +#include "strutils.h"
> +

I think you need to do a similar #ifdef game
as below, with all the PR_SCHED_CORE* macros as well.

On a system with an older prctl.h this just fails to build.
There is no reason it needs to though since these
are just numbers.

Alternatively the configure system needs to check and disable
the program but that does not seem necessary to me.


Cheers,
Phil

> +// These definitions might not be defined, even if the
> +// prctl interface accepts them.
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int core_sched_type_t;
> +typedef unsigned long cookie_t;
> +typedef enum {
> +	SCHED_CORE_CMD_GET,
> +	SCHED_CORE_CMD_NEW,
> +	SCHED_CORE_CMD_COPY,
> +} core_sched_cmd_t;
> +
> +struct args {
> +	pid_t pid;
> +	pid_t dest;
> +	core_sched_type_t type;
> +	core_sched_cmd_t cmd;
> +	int exec_argv_offset;
> +};
> +
> +cookie_t core_sched_get_cookie(pid_t pid);
> +void core_sched_create_cookie(pid_t pid, core_sched_type_t type);
> +void core_sched_pull_cookie(pid_t from);
> +void core_sched_push_cookie(pid_t to, core_sched_type_t type);
> +void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type);
> +void core_sched_exec_with_cookie(struct args *args, char **argv);
> +void core_sched_get_and_print_cookie(pid_t pid);
> +
> +core_sched_type_t parse_core_sched_type(char *str);
> +bool verify_arguments(struct args *args);
> +void parse_arguments(int argc, char **argv, struct args *args);
> +void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
> +static void __attribute__((__noreturn__)) usage(void);
> +
> +#define bad_usage(FMT...) \
> +	warnx(FMT);       \
> +	errtryhelp(EINVAL);
> +
> +#define check_coresched_in_kernel(errno)                              \
> +	if (errno == EINVAL) {                                        \
> +		warnx("Does your kernel support CONFIG_SCHED_CORE?"); \
> +	}
> +
> +cookie_t core_sched_get_cookie(pid_t pid)
> +{
> +	cookie_t cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> +		check_coresched_in_kernel(errno);
> +		err(errno, "Failed to get cookie from PID %d", pid);
> +	}
> +	return cookie;
> +}
> +
> +void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
> +		check_coresched_in_kernel(errno);
> +		err(errno, "Failed to create cookie for PID %d", pid);
> +	}
> +}
> +
> +void core_sched_pull_cookie(pid_t from)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
> +		check_coresched_in_kernel(errno);
> +		err(errno, "Failed to pull cookie from PID %d", from);
> +	}
> +}
> +
> +void core_sched_push_cookie(pid_t to, core_sched_type_t type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
> +		check_coresched_in_kernel(errno);
> +		err(errno, "Failed to push cookie to PID %d", to);
> +	}
> +}
> +
> +void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type)
> +{
> +	core_sched_pull_cookie(from);
> +	cookie_t before = core_sched_get_cookie(from);
> +	core_sched_push_cookie(to, to_type);
> +	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
> +	       program_invocation_short_name, before, from, to);
> +}
> +
> +void core_sched_exec_with_cookie(struct args *args, char **argv)
> +{
> +	if (!args->exec_argv_offset) {
> +		usage();
> +	}
> +
> +	// Move the argument list to the first argument of the program
> +	argv = &argv[args->exec_argv_offset];
> +
> +	// If a source PID is provided, try to copy the cookie from
> +	// that PID. Otherwise, create a brand new cookie with the
> +	// provided type.
> +	if (args->pid) {
> +		core_sched_pull_cookie(args->pid);
> +		core_sched_get_and_print_cookie(args->pid);
> +	} else {
> +		pid_t pid = getpid();
> +		core_sched_create_cookie(pid, args->type);
> +		cookie_t after = core_sched_get_cookie(pid);
> +		printf("%s: set cookie of PID %d to 0x%lx\n",
> +		       program_invocation_short_name, pid, after);
> +	}
> +
> +	if (execvp(argv[0], argv)) {
> +		errexec(argv[0]);
> +	}
> +}
> +
> +void core_sched_get_and_print_cookie(pid_t pid)
> +{
> +	cookie_t after = core_sched_get_cookie(pid);
> +	printf("%s: set cookie of PID %d to 0x%lx\n",
> +	       program_invocation_short_name, pid, after);
> +}
> +
> +core_sched_type_t parse_core_sched_type(char *str)
> +{
> +	if (!strncmp(str, "pid\0", 4)) {
> +		return PR_SCHED_CORE_SCOPE_THREAD;
> +	} else if (!strncmp(str, "tgid\0", 5)) {
> +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +	} else if (!strncmp(str, "pgid\0", 5)) {
> +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
> +	}
> +
> +	bad_usage("'%s' is an invalid option. Must be one of pid/tgid/pgid",
> +		  str);
> +	__builtin_unreachable();
> +}
> +
> +static void __attribute__((__noreturn__)) usage(void)
> +{
> +	fputs(USAGE_HEADER, stdout);
> +	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> +
> +	fputs(USAGE_FUNCTIONS, stdout);
> +	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
> +		  "                      execute a program with a new cookie."),
> +		stdout);
> +	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
> +		  "                      either another PID, or copy it to a new program"),
> +		stdout);
> +	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
> +		  "   the PID provided via --pid.\n"),
> +		stdout);
> +
> +	fputs(USAGE_OPTIONS, stdout);
> +	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
> +	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
> +		  "                      the destination PID where to copy the cookie to."),
> +		stdout);
> +	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
> +		  "                      when a new core scheduling cookie is created.\n"
> +		  "                      Can be one of the following: pid, tgid or pgid.\n"
> +		  "                      The default is tgid."),
> +		stdout);
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fprintf(stdout,
> +		USAGE_HELP_OPTIONS(
> +			20)); /* char offset to align option descriptions */
> +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> +	exit(EXIT_SUCCESS);
> +}
> +
> +void parse_arguments(int argc, char **argv, struct args *args)
> +{
> +	int c;
> +
> +	static const struct option longopts[] = {
> +		{ "new", no_argument, NULL, 'n' },
> +		{ "copy", no_argument, NULL, 'c' },
> +		{ "pid", required_argument, NULL, 'p' },
> +		{ "dest", required_argument, NULL, 'd' },
> +		{ "type", required_argument, NULL, 't' },
> +		{ "version", no_argument, NULL, 'V' },
> +		{ "help", no_argument, NULL, 'h' },
> +		{ NULL, 0, NULL, 0 }
> +	};
> +	static const ul_excl_t excl[] = {
> +		{ 'c', 'n' }, // Cannot do both --new and --copy
> +		{ 'd', 'n' }, // Cannot have both --new and --dest
> +		{ 0 }
> +	};
> +
> +	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
> +
> +	while ((c = getopt_long(argc, argv, "ncp:d:t:Vh", longopts, NULL)) !=
> +	       -1) {
> +		err_exclusive_options(c, longopts, excl, excl_st);
> +		switch (c) {
> +		case 'n':
> +			args->cmd = SCHED_CORE_CMD_NEW;
> +			break;
> +		case 'c':
> +			args->cmd = SCHED_CORE_CMD_COPY;
> +			break;
> +		case 'p':
> +			args->pid = strtopid_or_err(
> +				optarg, "Failed to parse PID for -p/--pid");
> +			break;
> +		case 'd':
> +			args->dest = strtopid_or_err(
> +				optarg, "Failed to parse PID for -d/--dest");
> +			break;
> +		case 't':
> +			args->type = parse_core_sched_type(optarg);
> +			break;
> +		case 'V':
> +			print_version(EXIT_SUCCESS);
> +		case 'h':
> +			usage();
> +		default:
> +			errtryhelp(EXIT_FAILURE);
> +		}
> +	}
> +
> +	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
> +		bad_usage("--copy: requires a -p/--pid");
> +	}
> +
> +	// More arguments have been passed, which means that the user wants to run
> +	// another program with a core scheduling cookie.
> +	if (argc > optind) {
> +		switch (args->cmd) {
> +		case SCHED_CORE_CMD_GET:
> +			bad_usage("Unknown command");
> +			break;
> +		case SCHED_CORE_CMD_NEW:
> +			if (args->pid) {
> +				bad_usage(
> +					"--new: cannot accept both a -p/--pid and a command");
> +			} else {
> +				args->exec_argv_offset = optind;
> +			}
> +			break;
> +		case SCHED_CORE_CMD_COPY:
> +			if (args->dest) {
> +				bad_usage(
> +					"--copy: cannot accept both a destination PID "
> +					"-d/--dest and a command")
> +			} else {
> +				args->exec_argv_offset = optind;
> +			}
> +			break;
> +		}
> +	}
> +
> +	if (argc <= optind) {
> +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
> +			bad_usage(
> +				"--new: requires either a -p/--pid or a command");
> +		}
> +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
> +			bad_usage(
> +				"--copy: requires either a -d/--dest or a command");
> +		}
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct args args = { 0 };
> +	args.cmd = SCHED_CORE_CMD_GET;
> +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +
> +	setlocale(LC_ALL, "");
> +	bindtextdomain(PACKAGE, LOCALEDIR);
> +	textdomain(PACKAGE);
> +	close_stdout_atexit();
> +
> +	parse_arguments(argc, argv, &args);
> +
> +	cookie_t cookie = 0;
> +
> +	switch (args.cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		if (args.pid) {
> +			cookie = core_sched_get_cookie(args.pid);
> +			if (cookie) {
> +				printf("%s: cookie of pid %d is 0x%lx\n",
> +				       program_invocation_short_name, args.pid,
> +				       cookie);
> +			} else {
> +				errx(ENODATA,
> +				     "pid %d doesn't have a core scheduling cookie",
> +				     args.pid);
> +			}
> +		} else {
> +			usage();
> +			exit(0);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_NEW:
> +		if (args.pid) {
> +			core_sched_create_cookie(args.pid, args.type);
> +			core_sched_get_and_print_cookie(args.pid);
> +		} else {
> +			core_sched_exec_with_cookie(&args, argv);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		if (args.dest) {
> +			core_sched_copy_cookie(args.pid, args.dest, args.type);
> +		} else {
> +			core_sched_exec_with_cookie(&args, argv);
> +		}
> +		break;
> +	default:
> +		usage();
> +		exit(1);
> +	}
> +}
> -- 
> 2.44.0
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v2 0/1] coresched: Manage core scheduling cookies for tasks
  2024-03-27 13:21           ` Phil Auld
@ 2024-03-27 14:18             ` Phil Auld
  2024-03-27 15:30               ` [PATCH v3] " Thijs Raymakers
  0 siblings, 1 reply; 42+ messages in thread
From: Phil Auld @ 2024-03-27 14:18 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux

On Wed, Mar 27, 2024 at 09:21:46AM -0400 Phil Auld wrote:
> 
> Hi Thijs,
> 
> On Wed, Mar 27, 2024 at 01:43:20PM +0100 Thijs Raymakers wrote:
> > Hi Phil,
> > 
> > >> In general, `prctl` does indeed return EINVAL if the operation is not
> > >> recognized, or not supported on the system. The `PR_SCHED_CORE`
> > >> operation itself only returns EINVAL if it is called with
> > >>   - an invalid operation
> > >>   - an invalid type
> > >>   - a negative PID
> > >>   - an invalid cookie store address (for PR_SCHED_CORE_GET)
> > >> Assuming that all these cases are prevented by the util, we could
> > >> interpret a EINVAL as a sign that PR_SCHED_CORE is not supported on
> > >> the system.
> > > Fair enough. Could say something like "got EINVAL. Does your kernel
> > > support CONFIG_SCHED_CORE?". 
> > 
> > I've added this as a warning in case a prctl call returns EINVAL. I
> > haven't tested it on a kernel with CONFIG_SCHED_CORE=n yet but I will do
> > that later.
> 
> Thanks. I'll refresh and try it again. My build machine does not have
> CONFIG_CORE_SCHED so that's easy for me to test :)
>

After fixing the missing #defines I tested this. I think this looks
good but I'd do the error line first then the CONFIG_SCHED_CORE message.

i.e:

$ ./coresched -n ls
coresched: Does your kernel support CONFIG_SCHED_CORE?
coresched: Failed to create cookie for PID 3747542: Invalid argument

should be

$ ./coresched -n ls
coresched: Failed to create cookie for PID 3747542: Invalid argument
coresched: Does your kernel support CONFIG_SCHED_CORE?


It's a little harder to clear the cookie with coresched that coreset
but otherwise I think this version is really good. It's covering all
my use cases and seems to fit my expectations pretty well. 

Thanks for listening to my suggestions.



Cheers,
Phil


> > 
> > > There is also
> > >        err_exclusive_options(c, longopts, excl, excl_st);
> > > 
> > > in the optuils.h code which can handle the tests for mutually exclusive
> > > arguments.
> > > 
> > > Thomas pointed me to that. It works nicely and can remove some of the extra
> > > checks (once you get it setup). And the error then looks the same as other
> > > util-linux progs. 
> > 
> > Good suggestion, I replaced some of the earlier mutually exclusive
> > checks with a call to err_exclusive_options.
> > 
> > > You might consider "errtryhelp(EXIT_FAILURE);"  in usage failures. A lot of
> > > the progs (incl taskset which is my model for this) do that. Then you only
> > > do the full usage when given -h/--help.
> > 
> > This does indeed feel a slightly nicer to use. I've added this in case a
> > of a usage failure. In case no options are given, it defaults to
> > printing the --help page. This matches the behavior of some other utils like
> > uclampset, and saves some time.
> > 
> > 
> > Op 26-03-2024 om 21:16 schreef Phil Auld:
> > > So "-n/--new" creates a new cookie and so does "". Just one on an existing task
> > > and one on the exec'd task.  Seems inconsistent, no?
> > 
> > Hmmm, I didn't intially see this as inconsistent behavior but I get why
> > you say this. I've modified this now to have -n/--new deal both with
> > assigning cookies to existing processes and to spawn new processes.
> > 
> > 
> > Op 26-03-2024 om 21:17 schreef Phil Auld:
> > > Btw, did you try coreset to see if it does what you need?
> > 
> > I did take a look at it. Thanks for adding the functionality of copying
> > a cookie from/to an existing PID. The commands take a bit of getting
> > used to for me, so I guess it is just a matter of preference of what feels
> > natural to use.
> 
> Yeah, fair enough.  I wrote it so it makes sense to me ;)
> 
> > Some things that weren't directly clear to me:
> > - Both `coreset ls` and `coreset -n ls` spawn a new program. If I
> >   understood it correctly, the former spawns the program without
> >   a cookie, and the latter spawns it with a cookie. I'm not entirely
> >   sure what the point of the former would be, as the cookie would either
> >   be 0 or equal to the cookie of the `coreset` process itself since the
> >   cookie in inherited across execs. It is not necessary to run the
> >   provided program in that case.
> 
> It's more informational. Since "get" is essentially the default, coreset ls
> would run ls and report its cookie. Not totally useful but more for
> consistency and it tells you that your shell does or does not have a
> cookie set.
> 
> > - Not entirely sure what the difference between --dest and --to is.
> >   The names of these options are similar in meaning and do almost the
> >   same thing.
> 
> That's the one I added to cover your use case. Dest is specifically only
> used with --copy. It makes copy into a pull_then_push rather than a
>  pull_then_exec. 
> 
> --to is just a push.  It will push the current task's cookie to
> <pid>.  This is also useful to clear a tasks cookie if your current shell does
> not have a cookie set.
> 
> With your coresched tool I think you have to pick a random pid that you
> know does not have a cookie and use that to clear a different pid's cookie.
> 
> > - It is not clear in what cases --scope is ignored and in which
> >   cases it is necessary.
> 
> Fair enough.  Also I have not yet cribbed your string version of scope.
> 
> > - What does `coreset -c -s 0 -d 2 -p 1 ls` do? Does it first copy the
> >   cookie from PID 1 to PID 2, and then run `ls` with the same or
> >   different cookie? Or the other way around?
> >
> 
> It should copy pid 1's cookie to pid 2 and report that it's ignoring the
> extra input.  It would not run ls in this case. 
> 
> -d is the destination pid in the case of copy.  So --pid is copy from (in
> both exec and --dest cases). 
> 
> -s 0 is redundant since that's the default in coreset.
> 
> > Now I do see what you meant earlier with imitating the behavior of the
> > `taskset` command. I don't think this is strictly necessary since it is
> > a different utility program, but I can understand that it can be useful
> > for people if the command behaves similar to a program that they already
> > know and have certain expectations of.
> > 
> > I've modified the options of coresched to be more in line with these
> > expectations, while also addressing the things I mentioned above.
> > 
> > Usage:
> >  coresched [-p PID]
> >  coresched --new [-t <TYPE>] -p <PID>
> >  coresched --new [-t <TYPE>] -- PROGRAM [ARGS...]
> >  coresched --copy -p <PID> [-t <TYPE>] -d <PID>
> >  coresched --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]
> > 
> > This follows the taskset's behavior (i.e. perform the same operation
> > on either an existing PID or on a new program) and taskset's arguments
> > (-p for PID instead of the -s I had earlier) more closely than before.
> >
> 
> I think I like the above, nice.
> 
> > I've attached an interdiff of the util compared to the previous version
> > that I sent yesterday. I think that this version encompases both our use
> > cases quite nicely.
> 
> Let me give it a try. I may or may not have time to get to it today.
> Other priorities arise. We'll see.
> 
> I do still prefer coreset as a name but I won't die on that hill :)
> 
> 
> Cheers,
> Phil
> 
> > 
> > Thijs
> > 
> > Thijs Raymakers (1):
> >   coresched: Manage core scheduling cookies for tasks
> > 
> >  .gitignore                  |   1 +
> >  bash-completion/coresched   |   0
> >  configure.ac                |  12 +-
> >  meson.build                 |  16 +-
> >  meson_options.txt           |   2 +-
> >  schedutils/Makemodule.am    |   8 +
> >  schedutils/coresched.1.adoc |  16 ++
> >  schedutils/coresched.c      | 363 ++++++++++++++++++++++++++++++++++++
> >  8 files changed, 412 insertions(+), 6 deletions(-)
> >  create mode 100644 bash-completion/coresched
> >  create mode 100644 schedutils/coresched.1.adoc
> >  create mode 100644 schedutils/coresched.c
> > 
> > Interdiff against v1:
> > diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> > index 17d775f2d..537281fdb 100644
> > --- a/schedutils/coresched.c
> > +++ b/schedutils/coresched.c
> > @@ -12,10 +12,12 @@
> >  #include <stdio.h>
> >  #include <sys/prctl.h>
> >  #include <sys/wait.h>
> > +#include <unistd.h>
> >  
> >  #include "c.h"
> >  #include "closestream.h"
> >  #include "nls.h"
> > +#include "optutils.h"
> >  #include "strutils.h"
> >  
> >  // These definitions might not be defined, even if the
> > @@ -26,32 +28,34 @@
> >  #ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> >  #define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> >  #endif
> > +
> >  #ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> >  #define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> >  #endif
> >  
> >  typedef int core_sched_type_t;
> > +typedef unsigned long cookie_t;
> >  typedef enum {
> > -	SCHED_CORE_CMD_EXEC = 0,
> > -	SCHED_CORE_CMD_GET = 1,
> > -	SCHED_CORE_CMD_CREATE = 2,
> > -	SCHED_CORE_CMD_COPY = 4,
> > +	SCHED_CORE_CMD_GET,
> > +	SCHED_CORE_CMD_NEW,
> > +	SCHED_CORE_CMD_COPY,
> >  } core_sched_cmd_t;
> >  
> >  struct args {
> > -	pid_t from_pid;
> > -	pid_t to_pid;
> > +	pid_t pid;
> > +	pid_t dest;
> >  	core_sched_type_t type;
> >  	core_sched_cmd_t cmd;
> >  	int exec_argv_offset;
> >  };
> >  
> > -unsigned long core_sched_get_cookie(struct args *args);
> > -void core_sched_create_cookie(struct args *args);
> > +cookie_t core_sched_get_cookie(pid_t pid);
> > +void core_sched_create_cookie(pid_t pid, core_sched_type_t type);
> >  void core_sched_pull_cookie(pid_t from);
> >  void core_sched_push_cookie(pid_t to, core_sched_type_t type);
> > -void core_sched_copy_cookie(struct args *args);
> > +void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type);
> >  void core_sched_exec_with_cookie(struct args *args, char **argv);
> > +void core_sched_get_and_print_cookie(pid_t pid);
> >  
> >  core_sched_type_t parse_core_sched_type(char *str);
> >  bool verify_arguments(struct args *args);
> > @@ -59,22 +63,31 @@ void parse_arguments(int argc, char **argv, struct args *args);
> >  void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
> >  static void __attribute__((__noreturn__)) usage(void);
> >  
> > -unsigned long core_sched_get_cookie(struct args *args)
> > +#define bad_usage(FMT...) \
> > +	warnx(FMT);       \
> > +	errtryhelp(EINVAL);
> > +
> > +#define check_coresched_in_kernel(errno)                              \
> > +	if (errno == EINVAL) {                                        \
> > +		warnx("Does your kernel support CONFIG_SCHED_CORE?"); \
> > +	}
> > +
> > +cookie_t core_sched_get_cookie(pid_t pid)
> >  {
> > -	unsigned long cookie = 0;
> > -	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, args->from_pid,
> > +	cookie_t cookie = 0;
> > +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
> >  		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> > -		err(errno, "Failed to get cookie from PID %d", args->from_pid);
> > +		check_coresched_in_kernel(errno);
> > +		err(errno, "Failed to get cookie from PID %d", pid);
> >  	}
> >  	return cookie;
> >  }
> >  
> > -void core_sched_create_cookie(struct args *args)
> > +void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
> >  {
> > -	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, args->to_pid, args->type,
> > -		  0)) {
> > -		err(errno, "Failed to create cookie for PID %d",
> > -		    args->from_pid);
> > +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
> > +		check_coresched_in_kernel(errno);
> > +		err(errno, "Failed to create cookie for PID %d", pid);
> >  	}
> >  }
> >  
> > @@ -82,6 +95,7 @@ void core_sched_pull_cookie(pid_t from)
> >  {
> >  	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> >  		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
> > +		check_coresched_in_kernel(errno);
> >  		err(errno, "Failed to pull cookie from PID %d", from);
> >  	}
> >  }
> > @@ -89,14 +103,18 @@ void core_sched_pull_cookie(pid_t from)
> >  void core_sched_push_cookie(pid_t to, core_sched_type_t type)
> >  {
> >  	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
> > +		check_coresched_in_kernel(errno);
> >  		err(errno, "Failed to push cookie to PID %d", to);
> >  	}
> >  }
> >  
> > -void core_sched_copy_cookie(struct args *args)
> > +void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type)
> >  {
> > -	core_sched_pull_cookie(args->from_pid);
> > -	core_sched_push_cookie(args->to_pid, args->type);
> > +	core_sched_pull_cookie(from);
> > +	cookie_t before = core_sched_get_cookie(from);
> > +	core_sched_push_cookie(to, to_type);
> > +	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
> > +	       program_invocation_short_name, before, from, to);
> >  }
> >  
> >  void core_sched_exec_with_cookie(struct args *args, char **argv)
> > @@ -111,11 +129,15 @@ void core_sched_exec_with_cookie(struct args *args, char **argv)
> >  	// If a source PID is provided, try to copy the cookie from
> >  	// that PID. Otherwise, create a brand new cookie with the
> >  	// provided type.
> > -	if (args->from_pid) {
> > -		core_sched_pull_cookie(args->from_pid);
> > +	if (args->pid) {
> > +		core_sched_pull_cookie(args->pid);
> > +		core_sched_get_and_print_cookie(args->pid);
> >  	} else {
> > -		args->to_pid = getpid();
> > -		core_sched_create_cookie(args);
> > +		pid_t pid = getpid();
> > +		core_sched_create_cookie(pid, args->type);
> > +		cookie_t after = core_sched_get_cookie(pid);
> > +		printf("%s: set cookie of PID %d to 0x%lx\n",
> > +		       program_invocation_short_name, pid, after);
> >  	}
> >  
> >  	if (execvp(argv[0], argv)) {
> > @@ -123,6 +145,13 @@ void core_sched_exec_with_cookie(struct args *args, char **argv)
> >  	}
> >  }
> >  
> > +void core_sched_get_and_print_cookie(pid_t pid)
> > +{
> > +	cookie_t after = core_sched_get_cookie(pid);
> > +	printf("%s: set cookie of PID %d to 0x%lx\n",
> > +	       program_invocation_short_name, pid, after);
> > +}
> > +
> >  core_sched_type_t parse_core_sched_type(char *str)
> >  {
> >  	if (!strncmp(str, "pid\0", 4)) {
> > @@ -133,156 +162,96 @@ core_sched_type_t parse_core_sched_type(char *str)
> >  		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
> >  	}
> >  
> > -	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
> > -	     str);
> > +	bad_usage("'%s' is an invalid option. Must be one of pid/tgid/pgid",
> > +		  str);
> >  	__builtin_unreachable();
> >  }
> >  
> >  static void __attribute__((__noreturn__)) usage(void)
> >  {
> >  	fputs(USAGE_HEADER, stdout);
> > -	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
> > -	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
> > +	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
> > +	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
> >  		program_invocation_short_name);
> > -	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
> > +	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> >  		program_invocation_short_name);
> > -	fprintf(stdout, _(" %s [-s <PID>] -- PROGRAM ARGS... \n"),
> > +	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
> > +		program_invocation_short_name);
> > +	fprintf(stdout,
> > +		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> >  		program_invocation_short_name);
> >  
> >  	fputs(USAGE_SEPARATOR, stdout);
> >  	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> >  
> >  	fputs(USAGE_FUNCTIONS, stdout);
> > -	fputsln(_(" -g, --get <PID>         get the core scheduling cookie of a PID"),
> > +	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
> > +		  "                      execute a program with a new cookie."),
> >  		stdout);
> > -	fputsln(_(" -n, --new <PID>         assign a new core scheduling cookie to PID"),
> > +	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
> > +		  "                      either another PID, or copy it to a new program"),
> >  		stdout);
> > -	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
> > -		  "                           another PID, requires the --source and --dest option"),
> > +	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
> > +		  "   the PID provided via --pid.\n"),
> >  		stdout);
> >  
> >  	fputs(USAGE_OPTIONS, stdout);
> > -	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
> > -		stdout);
> > -	fputsln(_(" -d, --dest <PID>        where to copy the core scheduling cookie to."),
> > +	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
> > +	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
> > +		  "                      the destination PID where to copy the cookie to."),
> >  		stdout);
> > -	fputsln(_(" -t, --type              type of the destination PID, or the type of\n"
> > -		  "                           the PID when a new core scheduling cookie\n"
> > -		  "                           is created. Can be one of the following:\n"
> > -		  "                           pid, tgid or pgid. Defaults to tgid."),
> > +	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
> > +		  "                      when a new core scheduling cookie is created.\n"
> > +		  "                      Can be one of the following: pid, tgid or pgid.\n"
> > +		  "                      The default is tgid."),
> >  		stdout);
> >  	fputs(USAGE_SEPARATOR, stdout);
> >  	fprintf(stdout,
> >  		USAGE_HELP_OPTIONS(
> > -			25)); /* char offset to align option descriptions */
> > +			20)); /* char offset to align option descriptions */
> >  	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> >  	exit(EXIT_SUCCESS);
> >  }
> >  
> > -bool verify_arguments(struct args *args)
> > -{
> > -	// Check if the value of args->cmd is a power of 2
> > -	// In that case, only a single function option was set.
> > -	if (args->cmd & (args->cmd - 1)) {
> > -		errx(EINVAL,
> > -		     "Cannot do more than one function at a time. See %s --help",
> > -		     program_invocation_short_name);
> > -	}
> > -
> > -	switch (args->cmd) {
> > -	case SCHED_CORE_CMD_GET:
> > -		if (args->to_pid) {
> > -			errx(EINVAL,
> > -			     "Cannot use -d/--dest with this -g/--get. See %s --help",
> > -			     program_invocation_short_name);
> > -		}
> > -		break;
> > -	case SCHED_CORE_CMD_CREATE:
> > -		if (args->from_pid) {
> > -			errx(EINVAL,
> > -			     "Cannot use -s/--source with this -n/--new. See %s --help",
> > -			     program_invocation_short_name);
> > -		}
> > -		break;
> > -	case SCHED_CORE_CMD_COPY:
> > -		if (!args->from_pid) {
> > -			errx(EINVAL,
> > -			     "-s/--source PID is required when copying");
> > -		}
> > -		if (!args->to_pid) {
> > -			errx(EINVAL, "-d/--dest PID is required when copying");
> > -		}
> > -		break;
> > -	case SCHED_CORE_CMD_EXEC:
> > -		if (args->to_pid) {
> > -			errx(EINVAL,
> > -			     "Cannot use -d/--dest when spawning a program. See %s --help",
> > -			     program_invocation_short_name);
> > -		}
> > -		break;
> > -	}
> > -	return true;
> > -}
> > -
> > -void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg)
> > -{
> > -	if (*dest) {
> > -		errx(EINVAL, "Ambigious usage: %s", err_msg);
> > -	} else {
> > -		*dest = src;
> > -	}
> > -}
> > -
> > -static const char *ERR_MSG_MULTIPLE_SOURCE_PIDS =
> > -	"Multiple source PIDs defined";
> >  void parse_arguments(int argc, char **argv, struct args *args)
> >  {
> >  	int c;
> > -	pid_t tmp;
> >  
> >  	static const struct option longopts[] = {
> > -		{ "get", required_argument, NULL, 'g' },
> > -		{ "new", required_argument, NULL, 'n' },
> > +		{ "new", no_argument, NULL, 'n' },
> >  		{ "copy", no_argument, NULL, 'c' },
> > -		{ "source", required_argument, NULL, 's' },
> > -		{ "destination", required_argument, NULL, 'd' },
> > +		{ "pid", required_argument, NULL, 'p' },
> > +		{ "dest", required_argument, NULL, 'd' },
> >  		{ "type", required_argument, NULL, 't' },
> >  		{ "version", no_argument, NULL, 'V' },
> >  		{ "help", no_argument, NULL, 'h' },
> >  		{ NULL, 0, NULL, 0 }
> >  	};
> > +	static const ul_excl_t excl[] = {
> > +		{ 'c', 'n' }, // Cannot do both --new and --copy
> > +		{ 'd', 'n' }, // Cannot have both --new and --dest
> > +		{ 0 }
> > +	};
> >  
> > -	while ((c = getopt_long(argc, argv, "g:n:cs:d:t:Vh", longopts, NULL)) !=
> > -	       -1)
> > +	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
> > +
> > +	while ((c = getopt_long(argc, argv, "ncp:d:t:Vh", longopts, NULL)) !=
> > +	       -1) {
> > +		err_exclusive_options(c, longopts, excl, excl_st);
> >  		switch (c) {
> > -		case 'g':
> > -			args->cmd |= SCHED_CORE_CMD_GET;
> > -			tmp = strtopid_or_err(
> > -				optarg, "Failed to parse PID for -g/--get");
> > -			set_pid_or_err(&args->from_pid, tmp,
> > -				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
> > -			break;
> >  		case 'n':
> > -			args->cmd |= SCHED_CORE_CMD_CREATE;
> > -			tmp = strtopid_or_err(
> > -				optarg, "Failed to parse PID for -n/--new");
> > -			set_pid_or_err(&args->to_pid, tmp,
> > -				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
> > +			args->cmd = SCHED_CORE_CMD_NEW;
> >  			break;
> >  		case 'c':
> > -			args->cmd |= SCHED_CORE_CMD_COPY;
> > +			args->cmd = SCHED_CORE_CMD_COPY;
> >  			break;
> > -		case 's':
> > -			tmp = strtopid_or_err(
> > -				optarg, "Failed to parse PID for -s/--source");
> > -			set_pid_or_err(&args->from_pid, tmp,
> > -				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
> > +		case 'p':
> > +			args->pid = strtopid_or_err(
> > +				optarg, "Failed to parse PID for -p/--pid");
> >  			break;
> >  		case 'd':
> > -			tmp = strtopid_or_err(
> > +			args->dest = strtopid_or_err(
> >  				optarg, "Failed to parse PID for -d/--dest");
> > -			set_pid_or_err(&args->to_pid, tmp,
> > -				       "Multiple destination PIDs defined");
> >  			break;
> >  		case 't':
> >  			args->type = parse_core_sched_type(optarg);
> > @@ -294,57 +263,98 @@ void parse_arguments(int argc, char **argv, struct args *args)
> >  		default:
> >  			errtryhelp(EXIT_FAILURE);
> >  		}
> > +	}
> >  
> > +	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
> > +		bad_usage("--copy: requires a -p/--pid");
> > +	}
> > +
> > +	// More arguments have been passed, which means that the user wants to run
> > +	// another program with a core scheduling cookie.
> >  	if (argc > optind) {
> > -		if (args->cmd == SCHED_CORE_CMD_EXEC) {
> > -			args->exec_argv_offset = optind;
> > -		} else {
> > -			// -g, -n or -c AND a program to run is provided
> > -			errx(EINVAL, "bad usage, see %s --help",
> > -			     program_invocation_short_name);
> > +		switch (args->cmd) {
> > +		case SCHED_CORE_CMD_GET:
> > +			bad_usage("Unknown command");
> > +			break;
> > +		case SCHED_CORE_CMD_NEW:
> > +			if (args->pid) {
> > +				bad_usage(
> > +					"--new: cannot accept both a -p/--pid and a command");
> > +			} else {
> > +				args->exec_argv_offset = optind;
> > +			}
> > +			break;
> > +		case SCHED_CORE_CMD_COPY:
> > +			if (args->dest) {
> > +				bad_usage(
> > +					"--copy: cannot accept both a destination PID "
> > +					"-d/--dest and a command")
> > +			} else {
> > +				args->exec_argv_offset = optind;
> > +			}
> > +			break;
> >  		}
> > -	} else if (argc == optind && args->from_pid) {
> > -		// Neither a function (-g, -n, or -c), nor a program to
> > -		// run is given
> > -		args->cmd = SCHED_CORE_CMD_GET;
> >  	}
> >  
> > -	verify_arguments(args);
> > +	if (argc <= optind) {
> > +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
> > +			bad_usage(
> > +				"--new: requires either a -p/--pid or a command");
> > +		}
> > +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
> > +			bad_usage(
> > +				"--copy: requires either a -d/--dest or a command");
> > +		}
> > +	}
> >  }
> >  
> >  int main(int argc, char **argv)
> >  {
> > -	struct args arguments = { 0 };
> > -	arguments.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> > +	struct args args = { 0 };
> > +	args.cmd = SCHED_CORE_CMD_GET;
> > +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> >  
> >  	setlocale(LC_ALL, "");
> >  	bindtextdomain(PACKAGE, LOCALEDIR);
> >  	textdomain(PACKAGE);
> >  	close_stdout_atexit();
> >  
> > -	parse_arguments(argc, argv, &arguments);
> > +	parse_arguments(argc, argv, &args);
> > +
> > +	cookie_t cookie = 0;
> >  
> > -	unsigned long cookie = 0;
> > -	switch (arguments.cmd) {
> > +	switch (args.cmd) {
> >  	case SCHED_CORE_CMD_GET:
> > -		cookie = core_sched_get_cookie(&arguments);
> > -		if (cookie) {
> > -			printf("core scheduling cookie of pid %d is 0x%lx\n",
> > -			       arguments.from_pid, cookie);
> > +		if (args.pid) {
> > +			cookie = core_sched_get_cookie(args.pid);
> > +			if (cookie) {
> > +				printf("%s: cookie of pid %d is 0x%lx\n",
> > +				       program_invocation_short_name, args.pid,
> > +				       cookie);
> > +			} else {
> > +				errx(ENODATA,
> > +				     "pid %d doesn't have a core scheduling cookie",
> > +				     args.pid);
> > +			}
> >  		} else {
> > -			printf("pid %d doesn't have a core scheduling cookie\n",
> > -			       arguments.from_pid);
> > -			exit(1);
> > +			usage();
> > +			exit(0);
> >  		}
> >  		break;
> > -	case SCHED_CORE_CMD_CREATE:
> > -		core_sched_create_cookie(&arguments);
> > +	case SCHED_CORE_CMD_NEW:
> > +		if (args.pid) {
> > +			core_sched_create_cookie(args.pid, args.type);
> > +			core_sched_get_and_print_cookie(args.pid);
> > +		} else {
> > +			core_sched_exec_with_cookie(&args, argv);
> > +		}
> >  		break;
> >  	case SCHED_CORE_CMD_COPY:
> > -		core_sched_copy_cookie(&arguments);
> > -		break;
> > -	case SCHED_CORE_CMD_EXEC:
> > -		core_sched_exec_with_cookie(&arguments, argv);
> > +		if (args.dest) {
> > +			core_sched_copy_cookie(args.pid, args.dest, args.type);
> > +		} else {
> > +			core_sched_exec_with_cookie(&args, argv);
> > +		}
> >  		break;
> >  	default:
> >  		usage();
> > -- 
> > 2.44.0
> > 
> 
> -- 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v3] coresched: Manage core scheduling cookies for tasks
  2024-03-27 14:18             ` Phil Auld
@ 2024-03-27 15:30               ` Thijs Raymakers
  2024-04-01 17:18                 ` Thomas Weißschuh
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-03-27 15:30 UTC (permalink / raw)
  To: pauld; +Cc: kzak, util-linux, Thijs Raymakers

Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
Thanks: Phil Auld for the valuable feedback
---

Hi Phil,

Thank you for taking a look at it. I've added the missing definitions of
the PR_SCHED* constants so it should now compile on systems that don't
have these definitions available.

Op 27-03-2024 om 15:18 schreef Phil Auld:
>  I think this looks good but I'd do the error line first then the
>  CONFIG_SCHED_CORE message.

I agree that it looks better like that. I switched them around.

> It's a little harder to clear the cookie with coresched than coreset

Clearing the cookie of a task is not something that is explicitly supported
by the kernel. Unless there is an option like PR_SCHED_CORE_CLEAR, this
remains a bit tricky to do. One could do

$ coresched --copy -p $$ -d <PID>

to copy the cookie of current shell to a destination PID, but that
would only clear the cookie if the current shell has no cookie to begin
with. Properly clearing the cookie of a task without relying on the
existance of a cookieless task seems to require some changes to the kernel.

> but otherwise I think this version is really good. It's covering all
> my use cases and seems to fit my expectations pretty well.

That is great to hear! My main use case of copying cookies around is
also covered. Unless you or anybody else have any remaining remarks,
I think this patch is ready.

The next steps would be to write the man page and add some tests as
well. I don't know exactly what the policy surrounding this is. Do those
have to be included in the same patch, or can they come a bit later?

Thijs


Interdiff against v2:
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index 537281fdb..756e0a1a6 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -20,15 +20,29 @@
   #include "optutils.h"
   #include "strutils.h"
   
  -// These definitions might not be defined, even if the
  -// prctl interface accepts them.
  +// These definitions might not be defined in the header files, even if the
  +// prctl interface in the kernel accepts them as valid.
  +#ifndef PR_SCHED_CORE
  +#define PR_SCHED_CORE 62
  +#endif
  +#ifndef PR_SCHED_CORE_GET
  +#define PR_SCHED_CORE_GET 0
  +#endif
  +#ifndef PR_SCHED_CORE_CREATE
  +#define PR_SCHED_CORE_CREATE 1
  +#endif
  +#ifndef PR_SCHED_CORE_SHARE_TO
  +#define PR_SCHED_CORE_SHARE_TO 2
  +#endif
  +#ifndef PR_SCHED_CORE_SHARE_FROM
  +#define PR_SCHED_CORE_SHARE_FROM 3
  +#endif
   #ifndef PR_SCHED_CORE_SCOPE_THREAD
   #define PR_SCHED_CORE_SCOPE_THREAD 0
   #endif
   #ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
   #define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
   #endif
  -
   #ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
   #define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
   #endif
  @@ -67,9 +81,12 @@ static void __attribute__((__noreturn__)) usage(void);
   	warnx(FMT);       \
   	errtryhelp(EINVAL);
   
  -#define check_coresched_in_kernel(errno)                              \
  -	if (errno == EINVAL) {                                        \
  -		warnx("Does your kernel support CONFIG_SCHED_CORE?"); \
  +#define check_prctl(FMT...)                                                 \
  +	if (errno == EINVAL) {                                              \
  +		warn(FMT);                                                  \
  +		errx(errno, "Does your kernel support CONFIG_SCHED_CORE?"); \
  +	} else {                                                            \
  +		err(errno, FMT);                                            \
   	}
   
   cookie_t core_sched_get_cookie(pid_t pid)
  @@ -77,8 +94,7 @@ cookie_t core_sched_get_cookie(pid_t pid)
   	cookie_t cookie = 0;
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
   		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
  -		check_coresched_in_kernel(errno);
  -		err(errno, "Failed to get cookie from PID %d", pid);
  +		check_prctl("Failed to get cookie from PID %d", pid);
   	}
   	return cookie;
   }
  @@ -86,8 +102,7 @@ cookie_t core_sched_get_cookie(pid_t pid)
   void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
   {
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
  -		check_coresched_in_kernel(errno);
  -		err(errno, "Failed to create cookie for PID %d", pid);
  +		check_prctl("Failed to create cookie for PID %d", pid);
   	}
   }
   
  @@ -95,16 +110,14 @@ void core_sched_pull_cookie(pid_t from)
   {
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
   		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
  -		check_coresched_in_kernel(errno);
  -		err(errno, "Failed to pull cookie from PID %d", from);
  +		check_prctl("Failed to pull cookie from PID %d", from);
   	}
   }
   
   void core_sched_push_cookie(pid_t to, core_sched_type_t type)
   {
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
  -		check_coresched_in_kernel(errno);
  -		err(errno, "Failed to push cookie to PID %d", to);
  +		check_prctl("Failed to push cookie to PID %d", to);
   	}
   }
   

 .gitignore                  |   1 +
 bash-completion/coresched   |   0
 configure.ac                |  12 +-
 meson.build                 |  16 +-
 meson_options.txt           |   2 +-
 schedutils/Makemodule.am    |   8 +
 schedutils/coresched.1.adoc |  16 ++
 schedutils/coresched.c      | 376 ++++++++++++++++++++++++++++++++++++
 8 files changed, 425 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 9600ce49f..9a2c04e8f 100644
--- a/meson.build
+++ b/meson.build
@@ -3111,13 +3111,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+	       'schedutils/uclampset.1.adoc',
+         'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..60a21cd01
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,16 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..756e0a1a6
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,376 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int core_sched_type_t;
+typedef unsigned long cookie_t;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} core_sched_cmd_t;
+
+struct args {
+	pid_t pid;
+	pid_t dest;
+	core_sched_type_t type;
+	core_sched_cmd_t cmd;
+	int exec_argv_offset;
+};
+
+cookie_t core_sched_get_cookie(pid_t pid);
+void core_sched_create_cookie(pid_t pid, core_sched_type_t type);
+void core_sched_pull_cookie(pid_t from);
+void core_sched_push_cookie(pid_t to, core_sched_type_t type);
+void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type);
+void core_sched_exec_with_cookie(struct args *args, char **argv);
+void core_sched_get_and_print_cookie(pid_t pid);
+
+core_sched_type_t parse_core_sched_type(char *str);
+bool verify_arguments(struct args *args);
+void parse_arguments(int argc, char **argv, struct args *args);
+void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
+static void __attribute__((__noreturn__)) usage(void);
+
+#define bad_usage(FMT...) \
+	warnx(FMT);       \
+	errtryhelp(EINVAL);
+
+#define check_prctl(FMT...)                                                 \
+	if (errno == EINVAL) {                                              \
+		warn(FMT);                                                  \
+		errx(errno, "Does your kernel support CONFIG_SCHED_CORE?"); \
+	} else {                                                            \
+		err(errno, FMT);                                            \
+	}
+
+cookie_t core_sched_get_cookie(pid_t pid)
+{
+	cookie_t cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
+		check_prctl("Failed to get cookie from PID %d", pid);
+	}
+	return cookie;
+}
+
+void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
+		check_prctl("Failed to create cookie for PID %d", pid);
+	}
+}
+
+void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
+		check_prctl("Failed to pull cookie from PID %d", from);
+	}
+}
+
+void core_sched_push_cookie(pid_t to, core_sched_type_t type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
+		check_prctl("Failed to push cookie to PID %d", to);
+	}
+}
+
+void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type)
+{
+	core_sched_pull_cookie(from);
+	cookie_t before = core_sched_get_cookie(from);
+	core_sched_push_cookie(to, to_type);
+	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
+	       program_invocation_short_name, before, from, to);
+}
+
+void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	if (!args->exec_argv_offset) {
+		usage();
+	}
+
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->pid) {
+		core_sched_pull_cookie(args->pid);
+		core_sched_get_and_print_cookie(args->pid);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		cookie_t after = core_sched_get_cookie(pid);
+		printf("%s: set cookie of PID %d to 0x%lx\n",
+		       program_invocation_short_name, pid, after);
+	}
+
+	if (execvp(argv[0], argv)) {
+		errexec(argv[0]);
+	}
+}
+
+void core_sched_get_and_print_cookie(pid_t pid)
+{
+	cookie_t after = core_sched_get_cookie(pid);
+	printf("%s: set cookie of PID %d to 0x%lx\n",
+	       program_invocation_short_name, pid, after);
+}
+
+core_sched_type_t parse_core_sched_type(char *str)
+{
+	if (!strncmp(str, "pid\0", 4)) {
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	} else if (!strncmp(str, "tgid\0", 5)) {
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	} else if (!strncmp(str, "pgid\0", 5)) {
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+	}
+
+	bad_usage("'%s' is an invalid option. Must be one of pid/tgid/pgid",
+		  str);
+	__builtin_unreachable();
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
+		  "                      execute a program with a new cookie."),
+		stdout);
+	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
+		  "                      either another PID, or copy it to a new program"),
+		stdout);
+	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
+		  "   the PID provided via --pid.\n"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
+	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
+		  "                      the destination PID where to copy the cookie to."),
+		stdout);
+	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
+		  "                      when a new core scheduling cookie is created.\n"
+		  "                      Can be one of the following: pid, tgid or pgid.\n"
+		  "                      The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fprintf(stdout,
+		USAGE_HELP_OPTIONS(
+			20)); /* char offset to align option descriptions */
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+void parse_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "new", no_argument, NULL, 'n' },
+		{ "copy", no_argument, NULL, 'c' },
+		{ "pid", required_argument, NULL, 'p' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "type", required_argument, NULL, 't' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+	static const ul_excl_t excl[] = {
+		{ 'c', 'n' }, // Cannot do both --new and --copy
+		{ 'd', 'n' }, // Cannot have both --new and --dest
+		{ 0 }
+	};
+
+	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+	while ((c = getopt_long(argc, argv, "ncp:d:t:Vh", longopts, NULL)) !=
+	       -1) {
+		err_exclusive_options(c, longopts, excl, excl_st);
+		switch (c) {
+		case 'n':
+			args->cmd = SCHED_CORE_CMD_NEW;
+			break;
+		case 'c':
+			args->cmd = SCHED_CORE_CMD_COPY;
+			break;
+		case 'p':
+			args->pid = strtopid_or_err(
+				optarg, "Failed to parse PID for -p/--pid");
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, "Failed to parse PID for -d/--dest");
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
+		bad_usage("--copy: requires a -p/--pid");
+	}
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage("Unknown command");
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->pid) {
+				bad_usage(
+					"--new: cannot accept both a -p/--pid and a command");
+			} else {
+				args->exec_argv_offset = optind;
+			}
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest) {
+				bad_usage(
+					"--copy: cannot accept both a destination PID "
+					"-d/--dest and a command")
+			} else {
+				args->exec_argv_offset = optind;
+			}
+			break;
+		}
+	}
+
+	if (argc <= optind) {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
+			bad_usage(
+				"--new: requires either a -p/--pid or a command");
+		}
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
+			bad_usage(
+				"--copy: requires either a -d/--dest or a command");
+		}
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { 0 };
+	args.cmd = SCHED_CORE_CMD_GET;
+	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_arguments(argc, argv, &args);
+
+	cookie_t cookie = 0;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		if (args.pid) {
+			cookie = core_sched_get_cookie(args.pid);
+			if (cookie) {
+				printf("%s: cookie of pid %d is 0x%lx\n",
+				       program_invocation_short_name, args.pid,
+				       cookie);
+			} else {
+				errx(ENODATA,
+				     "pid %d doesn't have a core scheduling cookie",
+				     args.pid);
+			}
+		} else {
+			usage();
+			exit(0);
+		}
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.pid) {
+			core_sched_create_cookie(args.pid, args.type);
+			core_sched_get_and_print_cookie(args.pid);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.dest) {
+			core_sched_copy_cookie(args.pid, args.dest, args.type);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
+		break;
+	default:
+		usage();
+		exit(1);
+	}
+}
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v3] coresched: Manage core scheduling cookies for tasks
  2024-03-27 15:30               ` [PATCH v3] " Thijs Raymakers
@ 2024-04-01 17:18                 ` Thomas Weißschuh
  2024-04-04 22:03                   ` [PATCH v4] " Thijs Raymakers
  0 siblings, 1 reply; 42+ messages in thread
From: Thomas Weißschuh @ 2024-04-01 17:18 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: pauld, kzak, util-linux

On 2024-03-27 16:30:08+0100, Thijs Raymakers wrote:
> [..]

>  .gitignore                  |   1 +
>  bash-completion/coresched   |   0
>  configure.ac                |  12 +-
>  meson.build                 |  16 +-
>  meson_options.txt           |   2 +-
>  schedutils/Makemodule.am    |   8 +
>  schedutils/coresched.1.adoc |  16 ++
>  schedutils/coresched.c      | 376 ++++++++++++++++++++++++++++++++++++
>  8 files changed, 425 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
> 
> diff --git a/.gitignore b/.gitignore
> index 6ecbfa7fe..316f3cdcc 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -94,6 +94,7 @@ ylwrap
>  /colcrt
>  /colrm
>  /column
> +/coresched
>  /ctrlaltdel
>  /delpart
>  /dmesg
> diff --git a/bash-completion/coresched b/bash-completion/coresched
> new file mode 100644
> index 000000000..e69de29bb
> diff --git a/configure.ac b/configure.ac
> index ab7c98636..3a189a075 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
>  AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
>  
>  # build_schedutils= is just configure-only variable to control
> -# ionice, taskset and chrt
> +# ionice, taskset, coresched and chrt
>  AC_ARG_ENABLE([schedutils],
> -  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
> +  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
>    [], [UL_DEFAULT_ENABLE([schedutils], [check])]
>  )
>  
> @@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
>  AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
>  
>  
> +UL_ENABLE_ALIAS([coresched], [schedutils])
> +UL_BUILD_INIT([coresched])
> +UL_REQUIRES_SYSCALL_CHECK([coresched],
> +	[UL_CHECK_SYSCALL([prctl])],
> +	[prctl])
> +AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
> +
> +
>  have_schedsetter=no
>  AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
>        [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
> diff --git a/meson.build b/meson.build
> index 9600ce49f..9a2c04e8f 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -3111,13 +3111,23 @@ exe4 = executable(
>    install : opt,
>    build_by_default : opt)
>  
> +exe5 = executable(
> +  'coresched',
> +  'schedutils/coresched.c',
> +  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, exe2, exe3, exe4]
> +  exes += [exe, exe2, exe3, exe4, exe5]
>    manadocs += ['schedutils/chrt.1.adoc',
>                 'schedutils/ionice.1.adoc',
>                 'schedutils/taskset.1.adoc',
> -	       'schedutils/uclampset.1.adoc']
> -  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
> +	       'schedutils/uclampset.1.adoc',
> +         'schedutils/coresched.1.adoc']

Wrong indentation?

> +  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
>  endif
>  
>  ############################################################
> diff --git a/meson_options.txt b/meson_options.txt
> index 7b8cf3f35..3405c1b73 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
>  option('build-setterm', type : 'feature',
>         description : 'build setterm')
>  option('build-schedutils', type : 'feature',
> -       description : 'build chrt, ionice, taskset')
> +       description : 'build chrt, ionice, taskset, coresched')
>  option('build-wall', type : 'feature',
>         description : 'build wall')
>  option('build-write', type : 'feature',
> diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
> index 1040da85f..0cb655401 100644
> --- a/schedutils/Makemodule.am
> +++ b/schedutils/Makemodule.am
> @@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
>  uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
>  uclampset_LDADD = $(LDADD) libcommon.la
>  endif
> +
> +if BUILD_CORESCHED
> +usrbin_exec_PROGRAMS += coresched
> +MANPAGES += schedutils/coresched.1
> +dist_noinst_DATA += schedutils/coresched.1.adoc
> +coresched_SOURCES = schedutils/coresched.c
> +coresched_LDADD = $(LDADD) libcommon.la
> +endif
> diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
> new file mode 100644
> index 000000000..60a21cd01
> --- /dev/null
> +++ b/schedutils/coresched.1.adoc
> @@ -0,0 +1,16 @@
> +//po4a: entry man manual
> +////
> +coresched(1) manpage
> +////
> += coresched(1)
> +:doctype: manpage
> +:man manual: User Commands
> +:man source: util-linux {release-version}
> +:page-layout: base
> +:command: coresched
> +:colon: :
> +:copyright: ©
> +
> +== NAME
> +
> +coresched - manage core scheduling cookies for tasks

I guess you are aware, but the manpage is empty.

> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> new file mode 100644
> index 000000000..756e0a1a6
> --- /dev/null
> +++ b/schedutils/coresched.c
> @@ -0,0 +1,376 @@
> +/**
> + * SPDX-License-Identifier: EUPL-1.2
> + *
> + * coresched.c - manage core scheduling cookies for tasks
> + *
> + * Copyright (C) 2024 Thijs Raymakers
> + * Licensed under the EUPL v1.2
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "optutils.h"
> +#include "strutils.h"
> +
> +// These definitions might not be defined in the header files, even if the
> +// prctl interface in the kernel accepts them as valid.
> +#ifndef PR_SCHED_CORE
> +#define PR_SCHED_CORE 62
> +#endif
> +#ifndef PR_SCHED_CORE_GET
> +#define PR_SCHED_CORE_GET 0
> +#endif
> +#ifndef PR_SCHED_CORE_CREATE
> +#define PR_SCHED_CORE_CREATE 1
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_TO
> +#define PR_SCHED_CORE_SHARE_TO 2
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_FROM
> +#define PR_SCHED_CORE_SHARE_FROM 3
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int core_sched_type_t;
> +typedef unsigned long cookie_t;
> +typedef enum {
> +	SCHED_CORE_CMD_GET,
> +	SCHED_CORE_CMD_NEW,
> +	SCHED_CORE_CMD_COPY,
> +} core_sched_cmd_t;

Technically all types ending in _t are reserved for libc.
And a conflict for cookie_t doesn't even sound that improbable.

How about sched_core_scope, sched_core_cookie and enum sched_core_cmd?

> +
> +struct args {
> +	pid_t pid;
> +	pid_t dest;
> +	core_sched_type_t type;
> +	core_sched_cmd_t cmd;
> +	int exec_argv_offset;
> +};
> +
> +cookie_t core_sched_get_cookie(pid_t pid);
> +void core_sched_create_cookie(pid_t pid, core_sched_type_t type);
> +void core_sched_pull_cookie(pid_t from);
> +void core_sched_push_cookie(pid_t to, core_sched_type_t type);
> +void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type);
> +void core_sched_exec_with_cookie(struct args *args, char **argv);
> +void core_sched_get_and_print_cookie(pid_t pid);
> +
> +core_sched_type_t parse_core_sched_type(char *str);
> +bool verify_arguments(struct args *args);
> +void parse_arguments(int argc, char **argv, struct args *args);
> +void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
> +static void __attribute__((__noreturn__)) usage(void);

Some of these forward declarations don't exist and some are unused.
Can we do without forward decls?
Also all functions can be static.

> +
> +#define bad_usage(FMT...) \
> +	warnx(FMT);       \
> +	errtryhelp(EINVAL);
> +
> +#define check_prctl(FMT...)                                                 \
> +	if (errno == EINVAL) {                                              \
> +		warn(FMT);                                                  \
> +		errx(errno, "Does your kernel support CONFIG_SCHED_CORE?"); \

Would a single call to core_sched_get_cookie() not be a reliable check
that can be done and reported once?

> +	} else {                                                            \
> +		err(errno, FMT);                                            \
> +	}

I'd prefer to not exit with a random errno, but always with a fixed
one. Except one to signal that kernel support is missing.

To make clear that this works like err{,x}() maybe call it err_prctl().
It's unfortunate that this has to be a macro.
There are verrx() and verr() which could make this an inline function.
We would need fallback implementations for it in include/c.h though,
which doesn't look so hard.

> +
> +cookie_t core_sched_get_cookie(pid_t pid)
> +{
> +	cookie_t cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> +		check_prctl("Failed to get cookie from PID %d", pid);

*All* user-facing strings need to go through _() for localizations.

> +	}
> +	return cookie;
> +}
> +
> +void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
> +		check_prctl("Failed to create cookie for PID %d", pid);
> +	}
> +}
> +
> +void core_sched_pull_cookie(pid_t from)

It would be nicer if these helpers are closer named to the prctl
operations.
sched_core_share_from_thread() for example.

> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
> +		check_prctl("Failed to pull cookie from PID %d", from);
> +	}
> +}
> +
> +void core_sched_push_cookie(pid_t to, core_sched_type_t type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
> +		check_prctl("Failed to push cookie to PID %d", to);
> +	}
> +}
> +
> +void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type)
> +{
> +	core_sched_pull_cookie(from);
> +	cookie_t before = core_sched_get_cookie(from);
> +	core_sched_push_cookie(to, to_type);
> +	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
> +	       program_invocation_short_name, before, from, to);

stderr and only with --verbose?

> +}
> +
> +void core_sched_exec_with_cookie(struct args *args, char **argv)
> +{
> +	if (!args->exec_argv_offset) {
> +		usage();
> +	}
> +
> +	// Move the argument list to the first argument of the program
> +	argv = &argv[args->exec_argv_offset];
> +
> +	// If a source PID is provided, try to copy the cookie from
> +	// that PID. Otherwise, create a brand new cookie with the
> +	// provided type.
> +	if (args->pid) {
> +		core_sched_pull_cookie(args->pid);
> +		core_sched_get_and_print_cookie(args->pid);
> +	} else {
> +		pid_t pid = getpid();
> +		core_sched_create_cookie(pid, args->type);
> +		cookie_t after = core_sched_get_cookie(pid);
> +		printf("%s: set cookie of PID %d to 0x%lx\n",
> +		       program_invocation_short_name, pid, after);
> +	}
> +
> +	if (execvp(argv[0], argv)) {
> +		errexec(argv[0]);
> +	}
> +}
> +
> +void core_sched_get_and_print_cookie(pid_t pid)
> +{
> +	cookie_t after = core_sched_get_cookie(pid);
> +	printf("%s: set cookie of PID %d to 0x%lx\n",
> +	       program_invocation_short_name, pid, after);
> +}
> +
> +core_sched_type_t parse_core_sched_type(char *str)
> +{
> +	if (!strncmp(str, "pid\0", 4)) {

This additional null-byte doesn't seem to make a difference.

> +		return PR_SCHED_CORE_SCOPE_THREAD;
> +	} else if (!strncmp(str, "tgid\0", 5)) {
> +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +	} else if (!strncmp(str, "pgid\0", 5)) {
> +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
> +	}

The codestyle avoids braces around single-statement blocks.

> +
> +	bad_usage("'%s' is an invalid option. Must be one of pid/tgid/pgid",
> +		  str);
> +	__builtin_unreachable();

Why the unreachable?

> +}
> +
> +static void __attribute__((__noreturn__)) usage(void)
> +{
> +	fputs(USAGE_HEADER, stdout);
> +	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> +
> +	fputs(USAGE_FUNCTIONS, stdout);
> +	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
> +		  "                      execute a program with a new cookie."),
> +		stdout);
> +	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
> +		  "                      either another PID, or copy it to a new program"),
> +		stdout);
> +	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
> +		  "   the PID provided via --pid.\n"),
> +		stdout);
> +
> +	fputs(USAGE_OPTIONS, stdout);
> +	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
> +	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
> +		  "                      the destination PID where to copy the cookie to."),
> +		stdout);
> +	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
> +		  "                      when a new core scheduling cookie is created.\n"
> +		  "                      Can be one of the following: pid, tgid or pgid.\n"
> +		  "                      The default is tgid."),
> +		stdout);
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fprintf(stdout,
> +		USAGE_HELP_OPTIONS(
> +			20)); /* char offset to align option descriptions */
> +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> +	exit(EXIT_SUCCESS);
> +}
> +
> +void parse_arguments(int argc, char **argv, struct args *args)
> +{
> +	int c;
> +
> +	static const struct option longopts[] = {
> +		{ "new", no_argument, NULL, 'n' },
> +		{ "copy", no_argument, NULL, 'c' },
> +		{ "pid", required_argument, NULL, 'p' },
> +		{ "dest", required_argument, NULL, 'd' },
> +		{ "type", required_argument, NULL, 't' },
> +		{ "version", no_argument, NULL, 'V' },
> +		{ "help", no_argument, NULL, 'h' },
> +		{ NULL, 0, NULL, 0 }
> +	};
> +	static const ul_excl_t excl[] = {
> +		{ 'c', 'n' }, // Cannot do both --new and --copy
> +		{ 'd', 'n' }, // Cannot have both --new and --dest
> +		{ 0 }
> +	};
> +
> +	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
> +
> +	while ((c = getopt_long(argc, argv, "ncp:d:t:Vh", longopts, NULL)) !=
> +	       -1) {
> +		err_exclusive_options(c, longopts, excl, excl_st);
> +		switch (c) {
> +		case 'n':
> +			args->cmd = SCHED_CORE_CMD_NEW;
> +			break;
> +		case 'c':
> +			args->cmd = SCHED_CORE_CMD_COPY;
> +			break;
> +		case 'p':
> +			args->pid = strtopid_or_err(
> +				optarg, "Failed to parse PID for -p/--pid");
> +			break;
> +		case 'd':
> +			args->dest = strtopid_or_err(
> +				optarg, "Failed to parse PID for -d/--dest");
> +			break;
> +		case 't':
> +			args->type = parse_core_sched_type(optarg);
> +			break;
> +		case 'V':
> +			print_version(EXIT_SUCCESS);
> +		case 'h':
> +			usage();
> +		default:
> +			errtryhelp(EXIT_FAILURE);
> +		}
> +	}
> +
> +	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
> +		bad_usage("--copy: requires a -p/--pid");
> +	}
> +
> +	// More arguments have been passed, which means that the user wants to run
> +	// another program with a core scheduling cookie.
> +	if (argc > optind) {
> +		switch (args->cmd) {
> +		case SCHED_CORE_CMD_GET:
> +			bad_usage("Unknown command");
> +			break;
> +		case SCHED_CORE_CMD_NEW:
> +			if (args->pid) {
> +				bad_usage(
> +					"--new: cannot accept both a -p/--pid and a command");
> +			} else {
> +				args->exec_argv_offset = optind;
> +			}
> +			break;
> +		case SCHED_CORE_CMD_COPY:
> +			if (args->dest) {
> +				bad_usage(
> +					"--copy: cannot accept both a destination PID "
> +					"-d/--dest and a command")
> +			} else {
> +				args->exec_argv_offset = optind;
> +			}
> +			break;
> +		}
> +	}
> +
> +	if (argc <= optind) {
> +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
> +			bad_usage(
> +				"--new: requires either a -p/--pid or a command");
> +		}
> +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
> +			bad_usage(
> +				"--copy: requires either a -d/--dest or a command");
> +		}
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct args args = { 0 };
> +	args.cmd = SCHED_CORE_CMD_GET;
> +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +
> +	setlocale(LC_ALL, "");
> +	bindtextdomain(PACKAGE, LOCALEDIR);
> +	textdomain(PACKAGE);
> +	close_stdout_atexit();
> +
> +	parse_arguments(argc, argv, &args);
> +
> +	cookie_t cookie = 0;

The initial value is never used.
By not initializing it the compiler could help you avoid using it
accidentally.

> +
> +	switch (args.cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		if (args.pid) {
> +			cookie = core_sched_get_cookie(args.pid);
> +			if (cookie) {
> +				printf("%s: cookie of pid %d is 0x%lx\n",
> +				       program_invocation_short_name, args.pid,
> +				       cookie);
> +			} else {
> +				errx(ENODATA,
> +				     "pid %d doesn't have a core scheduling cookie",
> +				     args.pid);
> +			}
> +		} else {
> +			usage();
> +			exit(0);

exit(1)?

> +		}
> +		break;
> +	case SCHED_CORE_CMD_NEW:
> +		if (args.pid) {
> +			core_sched_create_cookie(args.pid, args.type);
> +			core_sched_get_and_print_cookie(args.pid);
> +		} else {
> +			core_sched_exec_with_cookie(&args, argv);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		if (args.dest) {
> +			core_sched_copy_cookie(args.pid, args.dest, args.type);
> +		} else {
> +			core_sched_exec_with_cookie(&args, argv);
> +		}
> +		break;
> +	default:
> +		usage();
> +		exit(1);
> +	}
> +}

Also, please some tests.

^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v4] coresched: Manage core scheduling cookies for tasks
  2024-04-01 17:18                 ` Thomas Weißschuh
@ 2024-04-04 22:03                   ` Thijs Raymakers
  2024-04-05  6:26                     ` Thomas Weißschuh
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-04 22:03 UTC (permalink / raw)
  To: thomas; +Cc: pauld, kzak, util-linux, Thijs Raymakers

Co-authored-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
Signed-off-by: Phil Auld <pauld@redhat.com>
---

Hi Thomas,

Thanks for your comments. This patch addresses most of your remarks. It
also adds Phil Auld as a Co-author since we went back and forth on the
design quite a lot. His feedback has been valuable and his ideas have
been integrated in the util to accomodate for both our usecases.

Op 01-04-2024 om 7:18 p.m. schreef Thomas Weißschuh:
> I guess you are aware, but the manpage is empty.

I've added the manpage in this version of the patch.

> Technically all types ending in _t are reserved for libc.
> And a conflict for cookie_t doesn't even sound that improbable.
> How about sched_core_scope, sched_core_cookie and enum sched_core_cmd?

Good suggestion, the names of these types have been changed.

> Would a single call to core_sched_get_cookie() not be a reliable check
> that can be done and reported once?
> ...
> To make clear that this works like err{,x}() maybe call it err_prctl().
> It's unfortunate that this has to be a macro.
> There are verrx() and verr() which could make this an inline function.
> We would need fallback implementations for it in include/c.h though,
> which doesn't look so hard.

I Initially wrote it like this to avoid doing more prctl calls than
necessary. All the PR_SCHED_CORE* prctl calls indicate whether or not
the kernel supports core scheduling whenever they return with errno
EINVAL.

However, if support is explicitly checked beforehand, then the error
handling becomes a bit simpler and doesn't rely on macros anymore.
Therefore, I've changed to code to explicitly check for kernel support
beforehand. This change also removes the need for the `check_prctl`
macro.

> It would be nicer if these helpers are closer named to the prctl
> operations.
> sched_core_share_from_thread() for example.

I've followed the terminology of the kernel documentation of core
scheduling (Documentation/admin-guide/hw-vuln/core-scheduling.rst) here.
There, `PR_SCHED_CORE_SHARE_TO` is described as "push core_sched cookie
to pid." and `PR_SCHED_CORE_SHARE_FROM` is described as "pull core_sched
cookie from pid"

I prefer that terminology because I find it a little less ambiguous than
SHARE_TO and SHARE_FROM.

Op 01-04-2024 om 7:18 p.m. schreef Thomas Weißschuh:
> On 2024-03-27 16:30:08+0100, Thijs Raymakers wrote:
>> +	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
>> +	       program_invocation_short_name, before, from, to);
> stderr and only with --verbose?

This has been changed now. All operations, except getting a cookie,
will no longer print this type of information unless --verbose is passed.

> Also, please some tests.

This version adds 5 tests for coresched:
- getting the cookie of itself when no cookie is set
- setting the cookie of itself
- getting the cookie of itself when the cookie is set (must be equal to
  the cookie that was set)
- spawn a child process with a new cookie (must be different than the
  cookie of the parent process)
- spawn a child process with a new cookie and push that cookie to the
  parent process (afterwards, the cookie of the parent must be different)

An interdiff between the previous version and this version is listed
below. It also address your other remarks not mentioned in this email.

Only the bash completion script is missing, but I think that is better
suited for a follow-up patch.

Thijs

Interdiff against v3:
  diff --git a/meson.build b/meson.build
  index 9a2c04e8f..a3a648753 100644
  --- a/meson.build
  +++ b/meson.build
  @@ -3125,8 +3125,8 @@ if opt and not is_disabler(exe)
     manadocs += ['schedutils/chrt.1.adoc',
                  'schedutils/ionice.1.adoc',
                  'schedutils/taskset.1.adoc',
  -	       'schedutils/uclampset.1.adoc',
  -         'schedutils/coresched.1.adoc']
  +               'schedutils/uclampset.1.adoc',
  +               'schedutils/coresched.1.adoc']
     bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
   endif
   
  diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
  index 60a21cd01..34bc352a8 100644
  --- a/schedutils/coresched.1.adoc
  +++ b/schedutils/coresched.1.adoc
  @@ -14,3 +14,103 @@ coresched(1) manpage
   == NAME
   
   coresched - manage core scheduling cookies for tasks
  +
  +== SYNOPSIS
  +
  +*{command}* [options] *-p* _pid_
  +
  +*{command}* [options] \-- _command_ [_argument_...]
  +
  +== DESCRIPTION
  +The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
  +
  +Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
  +This is done by assigning a cookie to each task.
  +Only tasks have the same cookie are allowed to be scheduled on the same physical core.
  +
  +It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
  +
  +== FUNCTIONS
  +*-n*, *--new*::
  +Assign a new cookie to an existing PID or to execute _command_ with a new cookie.
  +
  +*-c*, *--copy*::
  +Copy the cookie from an existing PID to another PID, or execute _command_ with that copied cookie.
  +
  +If no function is specified, it will retrieve and print the cookie of a task.
  +
  +== OPTIONS
  +*-p*, *--pid* _PID_::
  +Operate on an existing PID and do not launch a new task.
  +
  +*-d*, *--dest* _PID_::
  +When using *--copy*, specify a destination PID where you want to copy the cookie to.
  +
  +*-t*, *--type* _TYPE_::
  +The type of the PID whose cookie will be modified. This can be one of three values:
  +- *pid*, or process ID
  +- *tgid*, or thread group ID (default value)
  +- *pgid*, or process group ID
  +
  +*-v*, *--verbose*::
  +Show extra information when modifying cookies of tasks.
  +
  +*-h*, *--help*::
  +Display help text and exit.
  +
  +*-V*, *--version*::
  +Print version and exit.
  +
  +== EXAMPLES
  +Get the core scheduling cookie of a task with PID _123_{colon}::
  +*{command} -p* _123_
  +
  +Give a task with PID _123_ a new core scheduling cookie{colon}::
  +*{command} --new -p* _123_
  +
  +Spawn a new task with a new core scheduling cookie{colon}::
  +*{command} --new* \-- _command_ [_argument_...]
  +
  +Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
  +*{command} --copy -p* _123_ *-d* _456_
  +
  +Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
  +*{command} --copy -p* _123_ \-- _command_ [_argument_...]
  +
  +Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
  +*{command} --copy -p* _123_ *-t* pgid *-d* _456_
  +
  +== PERMISSIONS
  +When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
  +See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
  +
  +== RETURN VALUE
  +On success, *{command}* returns 0.
  +If *{command}* fails, it will print an error and return 1.
  +If core scheduling is not supported with your kernel (e.g. because *CONFIG_SCHED_CORE*=n), then it will return 95 (*ENOTSUP*).
  +
  +== AUTHORS
  +mailto:thijs@raymakers.nl[Thijs Raymakers],
  +mailto:pauld@redhat.com[Phil Auld]
  +
  +== COPYRIGHT
  +
  +Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
  +
  +== SEE ALSO
  +*chrt*(1),
  +*nice*(1),
  +*renice*(1),
  +*taskset*(1),
  +*ptrace*(2),
  +*sched*(7)
  +
  +The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
  +
  +include::man-common/bugreports.adoc[]
  +
  +include::man-common/footer.adoc[]
  +
  +ifdef::translation[]
  +include::man-common/translation.adoc[]
  +endif::[]
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index 756e0a1a6..3d9392b1a 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -3,7 +3,7 @@
    *
    * coresched.c - manage core scheduling cookies for tasks
    *
  - * Copyright (C) 2024 Thijs Raymakers
  + * Copyright (C) 2024 Thijs Raymakers, Phil Auld
    * Licensed under the EUPL v1.2
    */
   
  @@ -47,94 +47,134 @@
   #define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
   #endif
   
  -typedef int core_sched_type_t;
  -typedef unsigned long cookie_t;
  +typedef int sched_core_scope;
  +typedef unsigned long sched_core_cookie;
   typedef enum {
   	SCHED_CORE_CMD_GET,
   	SCHED_CORE_CMD_NEW,
   	SCHED_CORE_CMD_COPY,
  -} core_sched_cmd_t;
  +} sched_core_cmd;
   
   struct args {
   	pid_t pid;
   	pid_t dest;
  -	core_sched_type_t type;
  -	core_sched_cmd_t cmd;
  +	sched_core_scope type;
  +	sched_core_cmd cmd;
   	int exec_argv_offset;
   };
   
  -cookie_t core_sched_get_cookie(pid_t pid);
  -void core_sched_create_cookie(pid_t pid, core_sched_type_t type);
  -void core_sched_pull_cookie(pid_t from);
  -void core_sched_push_cookie(pid_t to, core_sched_type_t type);
  -void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type);
  -void core_sched_exec_with_cookie(struct args *args, char **argv);
  -void core_sched_get_and_print_cookie(pid_t pid);
  +static bool sched_core_verbose = false;
   
  -core_sched_type_t parse_core_sched_type(char *str);
  -bool verify_arguments(struct args *args);
  -void parse_arguments(int argc, char **argv, struct args *args);
  -void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
  -static void __attribute__((__noreturn__)) usage(void);
  +static void __attribute__((__noreturn__)) usage(void)
  +{
  +	fputs(USAGE_HEADER, stdout);
  +	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
  +	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
  +		program_invocation_short_name);
  +	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
  +		program_invocation_short_name);
  +	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
  +		program_invocation_short_name);
  +	fprintf(stdout,
  +		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
  +		program_invocation_short_name);
  +
  +	fputs(USAGE_SEPARATOR, stdout);
  +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
  +
  +	fputs(USAGE_FUNCTIONS, stdout);
  +	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
  +		  "                      execute a program with a new cookie."),
  +		stdout);
  +	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
  +		  "                      either another PID, or copy it to a new program"),
  +		stdout);
  +	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
  +		  "   the PID provided via --pid.\n"),
  +		stdout);
  +
  +	fputs(USAGE_OPTIONS, stdout);
  +	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
  +	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
  +		  "                      the destination PID where to copy the cookie to."),
  +		stdout);
  +	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
  +		  "                      when a new core scheduling cookie is created.\n"
  +		  "                      Can be one of the following: pid, tgid or pgid.\n"
  +		  "                      The default is tgid."),
  +		stdout);
  +	fputs(USAGE_SEPARATOR, stdout);
  +	fputsln(_(" -v, --verbose      verbose"), stdout);
  +	fprintf(stdout,
  +		USAGE_HELP_OPTIONS(
  +			20)); /* char offset to align option descriptions */
  +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
  +	exit(EXIT_SUCCESS);
  +}
   
   #define bad_usage(FMT...) \
   	warnx(FMT);       \
  -	errtryhelp(EINVAL);
  -
  -#define check_prctl(FMT...)                                                 \
  -	if (errno == EINVAL) {                                              \
  -		warn(FMT);                                                  \
  -		errx(errno, "Does your kernel support CONFIG_SCHED_CORE?"); \
  -	} else {                                                            \
  -		err(errno, FMT);                                            \
  -	}
  +	errtryhelp(EXIT_FAILURE);
   
  -cookie_t core_sched_get_cookie(pid_t pid)
  +static sched_core_cookie core_sched_get_cookie(pid_t pid)
   {
  -	cookie_t cookie = 0;
  +	sched_core_cookie cookie = 0;
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
   		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
  -		check_prctl("Failed to get cookie from PID %d", pid);
  +		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
   	}
   	return cookie;
   }
   
  -void core_sched_create_cookie(pid_t pid, core_sched_type_t type)
  +static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
   {
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
  -		check_prctl("Failed to create cookie for PID %d", pid);
  +		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
   	}
   }
   
  -void core_sched_pull_cookie(pid_t from)
  +static void core_sched_pull_cookie(pid_t from)
   {
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
   		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
  -		check_prctl("Failed to pull cookie from PID %d", from);
  +		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
   	}
   }
   
  -void core_sched_push_cookie(pid_t to, core_sched_type_t type)
  +static void core_sched_push_cookie(pid_t to, sched_core_scope type)
   {
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
  -		check_prctl("Failed to push cookie to PID %d", to);
  +		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
   	}
   }
   
  -void core_sched_copy_cookie(pid_t from, pid_t to, core_sched_type_t to_type)
  +static void core_sched_copy_cookie(pid_t from, pid_t to,
  +				   sched_core_scope to_type)
   {
   	core_sched_pull_cookie(from);
  -	cookie_t before = core_sched_get_cookie(from);
   	core_sched_push_cookie(to, to_type);
  -	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
  -	       program_invocation_short_name, before, from, to);
  +
  +	if (sched_core_verbose) {
  +		sched_core_cookie before = core_sched_get_cookie(from);
  +		fprintf(stderr,
  +			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
  +			program_invocation_short_name, before, from, to);
  +	}
   }
   
  -void core_sched_exec_with_cookie(struct args *args, char **argv)
  +static void core_sched_get_and_print_cookie(pid_t pid)
   {
  -	if (!args->exec_argv_offset) {
  -		usage();
  +	if (sched_core_verbose) {
  +		sched_core_cookie after = core_sched_get_cookie(pid);
  +		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
  +			program_invocation_short_name, pid, after);
   	}
  +}
  +
  +static void core_sched_exec_with_cookie(struct args *args, char **argv)
  +{
  +	if (!args->exec_argv_offset)
  +		usage();
   
   	// Move the argument list to the first argument of the program
   	argv = &argv[args->exec_argv_offset];
  @@ -148,9 +188,7 @@ void core_sched_exec_with_cookie(struct args *args, char **argv)
   	} else {
   		pid_t pid = getpid();
   		core_sched_create_cookie(pid, args->type);
  -		cookie_t after = core_sched_get_cookie(pid);
  -		printf("%s: set cookie of PID %d to 0x%lx\n",
  -		       program_invocation_short_name, pid, after);
  +		core_sched_get_and_print_cookie(pid);
   	}
   
   	if (execvp(argv[0], argv)) {
  @@ -158,75 +196,36 @@ void core_sched_exec_with_cookie(struct args *args, char **argv)
   	}
   }
   
  -void core_sched_get_and_print_cookie(pid_t pid)
  +// If PR_SCHED_CORE is not recognized, or not supported on this system,
  +// then prctl will set errno to EINVAL. Assuming all other operands of
  +// prctl are valid, we can use errno==EINVAL as a check to see whether
  +// core scheduling is available on this system.
  +static bool is_core_sched_supported(void)
   {
  -	cookie_t after = core_sched_get_cookie(pid);
  -	printf("%s: set cookie of PID %d to 0x%lx\n",
  -	       program_invocation_short_name, pid, after);
  +	sched_core_cookie cookie = 0;
  +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
  +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
  +		if (errno == EINVAL) {
  +			return false;
  +		}
  +	}
  +	return true;
   }
   
  -core_sched_type_t parse_core_sched_type(char *str)
  +static sched_core_scope parse_core_sched_type(char *str)
   {
  -	if (!strncmp(str, "pid\0", 4)) {
  +	if (!strncmp(str, "pid", 4))
   		return PR_SCHED_CORE_SCOPE_THREAD;
  -	} else if (!strncmp(str, "tgid\0", 5)) {
  +	else if (!strncmp(str, "tgid", 5))
   		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
  -	} else if (!strncmp(str, "pgid\0", 5)) {
  +	else if (!strncmp(str, "pgid", 5))
   		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
  -	}
   
  -	bad_usage("'%s' is an invalid option. Must be one of pid/tgid/pgid",
  +	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
   		  str);
  -	__builtin_unreachable();
  -}
  -
  -static void __attribute__((__noreturn__)) usage(void)
  -{
  -	fputs(USAGE_HEADER, stdout);
  -	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
  -	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
  -		program_invocation_short_name);
  -	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
  -		program_invocation_short_name);
  -	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
  -		program_invocation_short_name);
  -	fprintf(stdout,
  -		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
  -		program_invocation_short_name);
  -
  -	fputs(USAGE_SEPARATOR, stdout);
  -	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
  -
  -	fputs(USAGE_FUNCTIONS, stdout);
  -	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
  -		  "                      execute a program with a new cookie."),
  -		stdout);
  -	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
  -		  "                      either another PID, or copy it to a new program"),
  -		stdout);
  -	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
  -		  "   the PID provided via --pid.\n"),
  -		stdout);
  -
  -	fputs(USAGE_OPTIONS, stdout);
  -	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
  -	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
  -		  "                      the destination PID where to copy the cookie to."),
  -		stdout);
  -	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
  -		  "                      when a new core scheduling cookie is created.\n"
  -		  "                      Can be one of the following: pid, tgid or pgid.\n"
  -		  "                      The default is tgid."),
  -		stdout);
  -	fputs(USAGE_SEPARATOR, stdout);
  -	fprintf(stdout,
  -		USAGE_HELP_OPTIONS(
  -			20)); /* char offset to align option descriptions */
  -	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
  -	exit(EXIT_SUCCESS);
   }
   
  -void parse_arguments(int argc, char **argv, struct args *args)
  +static void parse_arguments(int argc, char **argv, struct args *args)
   {
   	int c;
   
  @@ -236,6 +235,7 @@ void parse_arguments(int argc, char **argv, struct args *args)
   		{ "pid", required_argument, NULL, 'p' },
   		{ "dest", required_argument, NULL, 'd' },
   		{ "type", required_argument, NULL, 't' },
  +		{ "verbose", no_argument, NULL, 'v' },
   		{ "version", no_argument, NULL, 'V' },
   		{ "help", no_argument, NULL, 'h' },
   		{ NULL, 0, NULL, 0 }
  @@ -248,7 +248,7 @@ void parse_arguments(int argc, char **argv, struct args *args)
   
   	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
   
  -	while ((c = getopt_long(argc, argv, "ncp:d:t:Vh", longopts, NULL)) !=
  +	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
   	       -1) {
   		err_exclusive_options(c, longopts, excl, excl_st);
   		switch (c) {
  @@ -260,15 +260,18 @@ void parse_arguments(int argc, char **argv, struct args *args)
   			break;
   		case 'p':
   			args->pid = strtopid_or_err(
  -				optarg, "Failed to parse PID for -p/--pid");
  +				optarg, _("Failed to parse PID for -p/--pid"));
   			break;
   		case 'd':
   			args->dest = strtopid_or_err(
  -				optarg, "Failed to parse PID for -d/--dest");
  +				optarg, _("Failed to parse PID for -d/--dest"));
   			break;
   		case 't':
   			args->type = parse_core_sched_type(optarg);
   			break;
  +		case 'v':
  +			sched_core_verbose = true;
  +			break;
   		case 'V':
   			print_version(EXIT_SUCCESS);
   		case 'h':
  @@ -279,7 +282,7 @@ void parse_arguments(int argc, char **argv, struct args *args)
   	}
   
   	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
  -		bad_usage("--copy: requires a -p/--pid");
  +		bad_usage(_("--copy: requires a -p/--pid"));
   	}
   
   	// More arguments have been passed, which means that the user wants to run
  @@ -287,21 +290,21 @@ void parse_arguments(int argc, char **argv, struct args *args)
   	if (argc > optind) {
   		switch (args->cmd) {
   		case SCHED_CORE_CMD_GET:
  -			bad_usage("Unknown command");
  +			bad_usage(_("Unknown command"));
   			break;
   		case SCHED_CORE_CMD_NEW:
   			if (args->pid) {
  -				bad_usage(
  -					"--new: cannot accept both a -p/--pid and a command");
  +				bad_usage(_(
  +					"--new: cannot accept both a -p/--pid and a command"));
   			} else {
   				args->exec_argv_offset = optind;
   			}
   			break;
   		case SCHED_CORE_CMD_COPY:
   			if (args->dest) {
  -				bad_usage(
  +				bad_usage(_(
   					"--copy: cannot accept both a destination PID "
  -					"-d/--dest and a command")
  +					"-d/--dest and a command"))
   			} else {
   				args->exec_argv_offset = optind;
   			}
  @@ -311,12 +314,12 @@ void parse_arguments(int argc, char **argv, struct args *args)
   
   	if (argc <= optind) {
   		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
  -			bad_usage(
  -				"--new: requires either a -p/--pid or a command");
  +			bad_usage(_(
  +				"--new: requires either a -p/--pid or a command"));
   		}
   		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
  -			bad_usage(
  -				"--copy: requires either a -d/--dest or a command");
  +			bad_usage(_(
  +				"--copy: requires either a -d/--dest or a command"));
   		}
   	}
   }
  @@ -334,24 +337,27 @@ int main(int argc, char **argv)
   
   	parse_arguments(argc, argv, &args);
   
  -	cookie_t cookie = 0;
  +	if (!is_core_sched_supported()) {
  +		errx(ENOTSUP, _("Does your kernel support CONFIG_SCHED_CORE?"));
  +	}
  +
  +	sched_core_cookie cookie;
   
   	switch (args.cmd) {
   	case SCHED_CORE_CMD_GET:
   		if (args.pid) {
   			cookie = core_sched_get_cookie(args.pid);
   			if (cookie) {
  -				printf("%s: cookie of pid %d is 0x%lx\n",
  +				printf(_("%s: cookie of pid %d is 0x%lx\n"),
   				       program_invocation_short_name, args.pid,
   				       cookie);
   			} else {
   				errx(ENODATA,
  -				     "pid %d doesn't have a core scheduling cookie",
  +				     _("pid %d doesn't have a core scheduling cookie"),
   				     args.pid);
   			}
   		} else {
   			usage();
  -			exit(0);
   		}
   		break;
   	case SCHED_CORE_CMD_NEW:
  @@ -363,14 +369,12 @@ int main(int argc, char **argv)
   		}
   		break;
   	case SCHED_CORE_CMD_COPY:
  -		if (args.dest) {
  +		if (args.dest)
   			core_sched_copy_cookie(args.pid, args.dest, args.type);
  -		} else {
  +		else
   			core_sched_exec_with_cookie(&args, argv);
  -		}
   		break;
   	default:
   		usage();
  -		exit(1);
   	}
   }
  diff --git a/tests/commands.sh b/tests/commands.sh
  index 5674c5ff0..9eef92ccb 100644
  --- a/tests/commands.sh
  +++ b/tests/commands.sh
  @@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
   TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
   TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
   TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
  +TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
   TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
   TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
   TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
  diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
  new file mode 100644
  index 000000000..97ab7ea23
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-change-cookie-of-parent
  @@ -0,0 +1 @@
  +coresched: cookie of pid OWN_PID is DIFFERENT_COOKIE
  diff --git a/tests/expected/schedutils/coresched-get-own-pid-no-cookie b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
  new file mode 100644
  index 000000000..7f64a98b3
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
  @@ -0,0 +1 @@
  +coresched: pid OWN_PID doesn't have a core scheduling cookie
  diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
  new file mode 100644
  index 000000000..556e31a65
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
  @@ -0,0 +1 @@
  +coresched: cookie of pid OWN_PID is COOKIE
  diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid b/tests/expected/schedutils/coresched-set-cookie-own-pid
  new file mode 100644
  index 000000000..1816a4fbd
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-set-cookie-own-pid
  @@ -0,0 +1 @@
  +coresched: set cookie of PID OWN_PID to COOKIE
  diff --git a/tests/expected/schedutils/coresched-spawn-child-with-new-cookie b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
  new file mode 100644
  index 000000000..5b9c40052
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
  @@ -0,0 +1 @@
  +DIFFERENT_COOKIE
  diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
  new file mode 100755
  index 000000000..5381a56f2
  --- /dev/null
  +++ b/tests/ts/schedutils/coresched
  @@ -0,0 +1,55 @@
  +#!/bin/bash
  +# SPDX-License-Identifier: EUPL-1.2
  +#
  +# This file is part of util-linux
  +#
  +# Copyright (C) 2024 Thijs Raymakers
  +# Licensed under the EUPL v1.2
  +
  +TS_TOPDIR="${0%/*}/../.."
  +TS_DESC="coresched"
  +
  +. "$TS_TOPDIR"/functions.sh
  +ts_init "$*"
  +
  +ts_check_test_command "$TS_CMD_CORESCHED"
  +
  +# The output of coresched contains PIDs and core scheduling cookies, both of which should be
  +# assumed to be random values as we have no control over them. The tests replace these values
  +# with sed before writing them to the output file, so it can match the expected output file.
  +# - The PID of this bash script is replaced with the placeholder `OWN_PID`
  +# - The core scheduling cookie of this bash script is replaced by `COOKIE`
  +# - Any other cookie is replaced by `DIFFERENT_COOKIE`
  +# The behavior of coresched does not depend on the exact values of these cookies, so using
  +# placeholder values does not change the behavior tests.
  +ts_init_subtest "get-own-pid-no-cookie"
  +$TS_CMD_CORESCHED -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g" >> "$TS_OUTPUT"
  +ts_finalize_subtest
  +
  +ts_init_subtest "set-cookie-own-pid"
  +CORESCHED_OUTPUT=$($TS_CMD_CORESCHED -v -n -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g")
  +CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
  +CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
  +echo "$CORESCHED_OUTPUT" >> "$TS_OUTPUT"
  +ts_finalize_subtest
  +
  +ts_init_subtest "get-own-pid-with-cookie"
  +$TS_CMD_CORESCHED -p $$ | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
  +ts_finalize_subtest
  +
  +ts_init_subtest "spawn-child-with-new-cookie"
  +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -p \$\$" \
  +  | sed 's/^.*\(0x.*$\)/\1/g' \
  +  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
  +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
  +ts_finalize_subtest
  +
  +ts_init_subtest "change-cookie-of-parent"
  +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
  +$TS_CMD_CORESCHED -p $$ \
  +  | sed "s/$$/OWN_PID/g" \
  +  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
  +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
  +ts_finalize_subtest
  +
  +ts_finalize

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 116 ++++++
 schedutils/coresched.c                        | 380 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-change-cookie-of-parent         |   1 +
 .../coresched-get-own-pid-no-cookie           |   1 +
 .../coresched-get-own-pid-with-cookie         |   1 +
 .../schedutils/coresched-set-cookie-own-pid   |   1 +
 .../coresched-spawn-child-with-new-cookie     |   1 +
 tests/ts/schedutils/coresched                 |  55 +++
 15 files changed, 590 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-change-cookie-of-parent
 create mode 100644 tests/expected/schedutils/coresched-get-own-pid-no-cookie
 create mode 100644 tests/expected/schedutils/coresched-get-own-pid-with-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-own-pid
 create mode 100644 tests/expected/schedutils/coresched-spawn-child-with-new-cookie
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 9600ce49f..a3a648753 100644
--- a/meson.build
+++ b/meson.build
@@ -3111,13 +3111,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..34bc352a8
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,116 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [options] *-p* _pid_
+
+*{command}* [options] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*-n*, *--new*::
+Assign a new cookie to an existing PID or to execute _command_ with a new cookie.
+
+*-c*, *--copy*::
+Copy the cookie from an existing PID to another PID, or execute _command_ with that copied cookie.
+
+If no function is specified, it will retrieve and print the cookie of a task.
+
+== OPTIONS
+*-p*, *--pid* _PID_::
+Operate on an existing PID and do not launch a new task.
+
+*-d*, *--dest* _PID_::
+When using *--copy*, specify a destination PID where you want to copy the cookie to.
+
+*-t*, *--type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} -p* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} --new -p* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} --new* \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} --copy -p* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} --copy -p* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} --copy -p* _123_ *-t* pgid *-d* _456_
+
+== PERMISSIONS
+When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+If core scheduling is not supported with your kernel (e.g. because *CONFIG_SCHED_CORE*=n), then it will return 95 (*ENOTSUP*).
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers],
+mailto:pauld@redhat.com[Phil Auld]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..3d9392b1a
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,380 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers, Phil Auld
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t pid;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
+		  "                      execute a program with a new cookie."),
+		stdout);
+	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
+		  "                      either another PID, or copy it to a new program"),
+		stdout);
+	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
+		  "   the PID provided via --pid.\n"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
+	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
+		  "                      the destination PID where to copy the cookie to."),
+		stdout);
+	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
+		  "                      when a new core scheduling cookie is created.\n"
+		  "                      Can be one of the following: pid, tgid or pgid.\n"
+		  "                      The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout,
+		USAGE_HELP_OPTIONS(
+			20)); /* char offset to align option descriptions */
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...) \
+	warnx(FMT);       \
+	errtryhelp(EXIT_FAILURE);
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	}
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+	}
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+	}
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+	}
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		fprintf(stderr,
+			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
+			program_invocation_short_name, before, from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
+			program_invocation_short_name, pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	if (!args->exec_argv_offset)
+		usage();
+
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->pid) {
+		core_sched_pull_cookie(args->pid);
+		core_sched_get_and_print_cookie(args->pid);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv)) {
+		errexec(argv[0]);
+	}
+}
+
+// If PR_SCHED_CORE is not recognized, or not supported on this system,
+// then prctl will set errno to EINVAL. Assuming all other operands of
+// prctl are valid, we can use errno==EINVAL as a check to see whether
+// core scheduling is available on this system.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
+		if (errno == EINVAL) {
+			return false;
+		}
+	}
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strncmp(str, "pid", 4))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strncmp(str, "tgid", 5))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strncmp(str, "pgid", 5))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "new", no_argument, NULL, 'n' },
+		{ "copy", no_argument, NULL, 'c' },
+		{ "pid", required_argument, NULL, 'p' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+	static const ul_excl_t excl[] = {
+		{ 'c', 'n' }, // Cannot do both --new and --copy
+		{ 'd', 'n' }, // Cannot have both --new and --dest
+		{ 0 }
+	};
+
+	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
+	       -1) {
+		err_exclusive_options(c, longopts, excl, excl_st);
+		switch (c) {
+		case 'n':
+			args->cmd = SCHED_CORE_CMD_NEW;
+			break;
+		case 'c':
+			args->cmd = SCHED_CORE_CMD_COPY;
+			break;
+		case 'p':
+			args->pid = strtopid_or_err(
+				optarg, _("Failed to parse PID for -p/--pid"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
+		bad_usage(_("--copy: requires a -p/--pid"));
+	}
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("Unknown command"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->pid) {
+				bad_usage(_(
+					"--new: cannot accept both a -p/--pid and a command"));
+			} else {
+				args->exec_argv_offset = optind;
+			}
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest) {
+				bad_usage(_(
+					"--copy: cannot accept both a destination PID "
+					"-d/--dest and a command"))
+			} else {
+				args->exec_argv_offset = optind;
+			}
+			break;
+		}
+	}
+
+	if (argc <= optind) {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
+			bad_usage(_(
+				"--new: requires either a -p/--pid or a command"));
+		}
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
+			bad_usage(_(
+				"--copy: requires either a -d/--dest or a command"));
+		}
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { 0 };
+	args.cmd = SCHED_CORE_CMD_GET;
+	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported()) {
+		errx(ENOTSUP, _("Does your kernel support CONFIG_SCHED_CORE?"));
+	}
+
+	sched_core_cookie cookie;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		if (args.pid) {
+			cookie = core_sched_get_cookie(args.pid);
+			if (cookie) {
+				printf(_("%s: cookie of pid %d is 0x%lx\n"),
+				       program_invocation_short_name, args.pid,
+				       cookie);
+			} else {
+				errx(ENODATA,
+				     _("pid %d doesn't have a core scheduling cookie"),
+				     args.pid);
+			}
+		} else {
+			usage();
+		}
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.pid) {
+			core_sched_create_cookie(args.pid, args.type);
+			core_sched_get_and_print_cookie(args.pid);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.dest)
+			core_sched_copy_cookie(args.pid, args.dest, args.type);
+		else
+			core_sched_exec_with_cookie(&args, argv);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
new file mode 100644
index 000000000..97ab7ea23
--- /dev/null
+++ b/tests/expected/schedutils/coresched-change-cookie-of-parent
@@ -0,0 +1 @@
+coresched: cookie of pid OWN_PID is DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-own-pid-no-cookie b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
new file mode 100644
index 000000000..7f64a98b3
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
@@ -0,0 +1 @@
+coresched: pid OWN_PID doesn't have a core scheduling cookie
diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
new file mode 100644
index 000000000..556e31a65
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
@@ -0,0 +1 @@
+coresched: cookie of pid OWN_PID is COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid b/tests/expected/schedutils/coresched-set-cookie-own-pid
new file mode 100644
index 000000000..1816a4fbd
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-own-pid
@@ -0,0 +1 @@
+coresched: set cookie of PID OWN_PID to COOKIE
diff --git a/tests/expected/schedutils/coresched-spawn-child-with-new-cookie b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..5381a56f2
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,55 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "get-own-pid-no-cookie"
+$TS_CMD_CORESCHED -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "set-cookie-own-pid"
+CORESCHED_OUTPUT=$($TS_CMD_CORESCHED -v -n -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g")
+CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "get-own-pid-with-cookie"
+$TS_CMD_CORESCHED -p $$ | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "spawn-child-with-new-cookie"
+$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -p \$\$" \
+  | sed 's/^.*\(0x.*$\)/\1/g' \
+  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
+  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "change-cookie-of-parent"
+$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
+$TS_CMD_CORESCHED -p $$ \
+  | sed "s/$$/OWN_PID/g" \
+  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
+  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v4] coresched: Manage core scheduling cookies for tasks
  2024-04-04 22:03                   ` [PATCH v4] " Thijs Raymakers
@ 2024-04-05  6:26                     ` Thomas Weißschuh
  2024-04-05 14:14                       ` Phil Auld
  0 siblings, 1 reply; 42+ messages in thread
From: Thomas Weißschuh @ 2024-04-05  6:26 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: pauld, kzak, util-linux

On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
> Co-authored-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> Signed-off-by: Phil Auld <pauld@redhat.com>
> ---
> 
> Hi Thomas,
> 
> Thanks for your comments. This patch addresses most of your remarks. It
> also adds Phil Auld as a Co-author since we went back and forth on the
> design quite a lot. His feedback has been valuable and his ideas have
> been integrated in the util to accomodate for both our usecases.

Thanks, some more comments below.

> Op 01-04-2024 om 7:18 p.m. schreef Thomas Weißschuh:
> > I guess you are aware, but the manpage is empty.
> 
> I've added the manpage in this version of the patch.
> 
> > Technically all types ending in _t are reserved for libc.
> > And a conflict for cookie_t doesn't even sound that improbable.
> > How about sched_core_scope, sched_core_cookie and enum sched_core_cmd?
> 
> Good suggestion, the names of these types have been changed.
> 
> > Would a single call to core_sched_get_cookie() not be a reliable check
> > that can be done and reported once?
> > ...
> > To make clear that this works like err{,x}() maybe call it err_prctl().
> > It's unfortunate that this has to be a macro.
> > There are verrx() and verr() which could make this an inline function.
> > We would need fallback implementations for it in include/c.h though,
> > which doesn't look so hard.
> 
> I Initially wrote it like this to avoid doing more prctl calls than
> necessary. All the PR_SCHED_CORE* prctl calls indicate whether or not
> the kernel supports core scheduling whenever they return with errno
> EINVAL.
> 
> However, if support is explicitly checked beforehand, then the error
> handling becomes a bit simpler and doesn't rely on macros anymore.
> Therefore, I've changed to code to explicitly check for kernel support
> beforehand. This change also removes the need for the `check_prctl`
> macro.

Thanks, would it make sense to expose this check via the CLI?
That could then used by the tests to skip themselves if the system does
not provide core scheduling on old kernels or qemu-user.

> > It would be nicer if these helpers are closer named to the prctl
> > operations.
> > sched_core_share_from_thread() for example.
> 
> I've followed the terminology of the kernel documentation of core
> scheduling (Documentation/admin-guide/hw-vuln/core-scheduling.rst) here.
> There, `PR_SCHED_CORE_SHARE_TO` is described as "push core_sched cookie
> to pid." and `PR_SCHED_CORE_SHARE_FROM` is described as "pull core_sched
> cookie from pid"
> 
> I prefer that terminology because I find it a little less ambiguous than
> SHARE_TO and SHARE_FROM.

Ok.

> Op 01-04-2024 om 7:18 p.m. schreef Thomas Weißschuh:
> > On 2024-03-27 16:30:08+0100, Thijs Raymakers wrote:
> >> +	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
> >> +	       program_invocation_short_name, before, from, to);
> > stderr and only with --verbose?
> 
> This has been changed now. All operations, except getting a cookie,
> will no longer print this type of information unless --verbose is passed.
> 
> > Also, please some tests.
> 
> This version adds 5 tests for coresched:
> - getting the cookie of itself when no cookie is set
> - setting the cookie of itself
> - getting the cookie of itself when the cookie is set (must be equal to
>   the cookie that was set)
> - spawn a child process with a new cookie (must be different than the
>   cookie of the parent process)
> - spawn a child process with a new cookie and push that cookie to the
>   parent process (afterwards, the cookie of the parent must be different)

Nice!

> An interdiff between the previous version and this version is listed
> below. It also address your other remarks not mentioned in this email.
> 
> Only the bash completion script is missing, but I think that is better
> suited for a follow-up patch.

Sounds fair.

> Thijs
> 
> Interdiff against v3:

[snip]

>  .gitignore                                    |   1 +
>  bash-completion/coresched                     |   0
>  configure.ac                                  |  12 +-
>  meson.build                                   |  16 +-
>  meson_options.txt                             |   2 +-
>  schedutils/Makemodule.am                      |   8 +
>  schedutils/coresched.1.adoc                   | 116 ++++++
>  schedutils/coresched.c                        | 380 ++++++++++++++++++
>  tests/commands.sh                             |   1 +
>  .../coresched-change-cookie-of-parent         |   1 +
>  .../coresched-get-own-pid-no-cookie           |   1 +
>  .../coresched-get-own-pid-with-cookie         |   1 +
>  .../schedutils/coresched-set-cookie-own-pid   |   1 +
>  .../coresched-spawn-child-with-new-cookie     |   1 +
>  tests/ts/schedutils/coresched                 |  55 +++
>  15 files changed, 590 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
>  create mode 100644 tests/expected/schedutils/coresched-change-cookie-of-parent
>  create mode 100644 tests/expected/schedutils/coresched-get-own-pid-no-cookie
>  create mode 100644 tests/expected/schedutils/coresched-get-own-pid-with-cookie
>  create mode 100644 tests/expected/schedutils/coresched-set-cookie-own-pid
>  create mode 100644 tests/expected/schedutils/coresched-spawn-child-with-new-cookie
>  create mode 100755 tests/ts/schedutils/coresched

[snip]

> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> new file mode 100644
> index 000000000..3d9392b1a
> --- /dev/null
> +++ b/schedutils/coresched.c
> @@ -0,0 +1,380 @@
> +/**
> + * SPDX-License-Identifier: EUPL-1.2
> + *
> + * coresched.c - manage core scheduling cookies for tasks
> + *
> + * Copyright (C) 2024 Thijs Raymakers, Phil Auld
> + * Licensed under the EUPL v1.2
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "optutils.h"
> +#include "strutils.h"
> +
> +// These definitions might not be defined in the header files, even if the
> +// prctl interface in the kernel accepts them as valid.
> +#ifndef PR_SCHED_CORE
> +#define PR_SCHED_CORE 62
> +#endif
> +#ifndef PR_SCHED_CORE_GET
> +#define PR_SCHED_CORE_GET 0
> +#endif
> +#ifndef PR_SCHED_CORE_CREATE
> +#define PR_SCHED_CORE_CREATE 1
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_TO
> +#define PR_SCHED_CORE_SHARE_TO 2
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_FROM
> +#define PR_SCHED_CORE_SHARE_FROM 3
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int sched_core_scope;
> +typedef unsigned long sched_core_cookie;
> +typedef enum {
> +	SCHED_CORE_CMD_GET,
> +	SCHED_CORE_CMD_NEW,
> +	SCHED_CORE_CMD_COPY,
> +} sched_core_cmd;
> +
> +struct args {
> +	pid_t pid;
> +	pid_t dest;
> +	sched_core_scope type;
> +	sched_core_cmd cmd;
> +	int exec_argv_offset;
> +};
> +
> +static bool sched_core_verbose = false;
> +
> +static void __attribute__((__noreturn__)) usage(void)
> +{
> +	fputs(USAGE_HEADER, stdout);
> +	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> +
> +	fputs(USAGE_FUNCTIONS, stdout);
> +	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
> +		  "                      execute a program with a new cookie."),
> +		stdout);
> +	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
> +		  "                      either another PID, or copy it to a new program"),
> +		stdout);
> +	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
> +		  "   the PID provided via --pid.\n"),
> +		stdout);
> +
> +	fputs(USAGE_OPTIONS, stdout);
> +	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
> +	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
> +		  "                      the destination PID where to copy the cookie to."),
> +		stdout);
> +	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
> +		  "                      when a new core scheduling cookie is created.\n"
> +		  "                      Can be one of the following: pid, tgid or pgid.\n"
> +		  "                      The default is tgid."),
> +		stdout);
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_(" -v, --verbose      verbose"), stdout);
> +	fprintf(stdout,
> +		USAGE_HELP_OPTIONS(
> +			20)); /* char offset to align option descriptions */
> +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> +	exit(EXIT_SUCCESS);
> +}
> +
> +#define bad_usage(FMT...) \
> +	warnx(FMT);       \
> +	errtryhelp(EXIT_FAILURE);
> +
> +static sched_core_cookie core_sched_get_cookie(pid_t pid)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> +		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
> +	}
> +	return cookie;
> +}
> +
> +static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
> +		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
> +	}

General codestyle is to leave out braces around single-statement blocks.
(I mentioned this before, if you want to keep them please mention it)

> +}
> +
> +static void core_sched_pull_cookie(pid_t from)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
> +		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
> +	}
> +}
> +
> +static void core_sched_push_cookie(pid_t to, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
> +		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
> +	}
> +}
> +
> +static void core_sched_copy_cookie(pid_t from, pid_t to,
> +				   sched_core_scope to_type)
> +{
> +	core_sched_pull_cookie(from);
> +	core_sched_push_cookie(to, to_type);
> +
> +	if (sched_core_verbose) {
> +		sched_core_cookie before = core_sched_get_cookie(from);
> +		fprintf(stderr,
> +			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
> +			program_invocation_short_name, before, from, to);
> +	}
> +}
> +
> +static void core_sched_get_and_print_cookie(pid_t pid)
> +{
> +	if (sched_core_verbose) {
> +		sched_core_cookie after = core_sched_get_cookie(pid);
> +		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
> +			program_invocation_short_name, pid, after);
> +	}
> +}
> +
> +static void core_sched_exec_with_cookie(struct args *args, char **argv)
> +{
> +	if (!args->exec_argv_offset)
> +		usage();
> +
> +	// Move the argument list to the first argument of the program
> +	argv = &argv[args->exec_argv_offset];
> +
> +	// If a source PID is provided, try to copy the cookie from
> +	// that PID. Otherwise, create a brand new cookie with the
> +	// provided type.
> +	if (args->pid) {
> +		core_sched_pull_cookie(args->pid);
> +		core_sched_get_and_print_cookie(args->pid);
> +	} else {
> +		pid_t pid = getpid();
> +		core_sched_create_cookie(pid, args->type);
> +		core_sched_get_and_print_cookie(pid);
> +	}
> +
> +	if (execvp(argv[0], argv)) {
> +		errexec(argv[0]);
> +	}
> +}
> +
> +// If PR_SCHED_CORE is not recognized, or not supported on this system,
> +// then prctl will set errno to EINVAL. Assuming all other operands of
> +// prctl are valid, we can use errno==EINVAL as a check to see whether
> +// core scheduling is available on this system.
> +static bool is_core_sched_supported(void)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> +		if (errno == EINVAL) {
> +			return false;
> +		}
> +	}
> +	return true;
> +}
> +
> +static sched_core_scope parse_core_sched_type(char *str)
> +{
> +	if (!strncmp(str, "pid", 4))

These can be plain strcmp().

> +		return PR_SCHED_CORE_SCOPE_THREAD;
> +	else if (!strncmp(str, "tgid", 5))
> +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +	else if (!strncmp(str, "pgid", 5))
> +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
> +
> +	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
> +		  str);
> +}
> +
> +static void parse_arguments(int argc, char **argv, struct args *args)
> +{
> +	int c;
> +
> +	static const struct option longopts[] = {
> +		{ "new", no_argument, NULL, 'n' },
> +		{ "copy", no_argument, NULL, 'c' },
> +		{ "pid", required_argument, NULL, 'p' },
> +		{ "dest", required_argument, NULL, 'd' },
> +		{ "type", required_argument, NULL, 't' },
> +		{ "verbose", no_argument, NULL, 'v' },
> +		{ "version", no_argument, NULL, 'V' },
> +		{ "help", no_argument, NULL, 'h' },
> +		{ NULL, 0, NULL, 0 }
> +	};
> +	static const ul_excl_t excl[] = {
> +		{ 'c', 'n' }, // Cannot do both --new and --copy
> +		{ 'd', 'n' }, // Cannot have both --new and --dest
> +		{ 0 }
> +	};
> +
> +	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
> +
> +	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
> +	       -1) {
> +		err_exclusive_options(c, longopts, excl, excl_st);
> +		switch (c) {
> +		case 'n':
> +			args->cmd = SCHED_CORE_CMD_NEW;
> +			break;
> +		case 'c':
> +			args->cmd = SCHED_CORE_CMD_COPY;
> +			break;
> +		case 'p':
> +			args->pid = strtopid_or_err(
> +				optarg, _("Failed to parse PID for -p/--pid"));
> +			break;
> +		case 'd':
> +			args->dest = strtopid_or_err(
> +				optarg, _("Failed to parse PID for -d/--dest"));
> +			break;
> +		case 't':
> +			args->type = parse_core_sched_type(optarg);
> +			break;
> +		case 'v':
> +			sched_core_verbose = true;
> +			break;
> +		case 'V':
> +			print_version(EXIT_SUCCESS);
> +		case 'h':
> +			usage();
> +		default:
> +			errtryhelp(EXIT_FAILURE);
> +		}
> +	}
> +
> +	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
> +		bad_usage(_("--copy: requires a -p/--pid"));
> +	}
> +
> +	// More arguments have been passed, which means that the user wants to run
> +	// another program with a core scheduling cookie.
> +	if (argc > optind) {
> +		switch (args->cmd) {
> +		case SCHED_CORE_CMD_GET:
> +			bad_usage(_("Unknown command"));
> +			break;
> +		case SCHED_CORE_CMD_NEW:
> +			if (args->pid) {
> +				bad_usage(_(
> +					"--new: cannot accept both a -p/--pid and a command"));
> +			} else {
> +				args->exec_argv_offset = optind;
> +			}
> +			break;
> +		case SCHED_CORE_CMD_COPY:
> +			if (args->dest) {
> +				bad_usage(_(
> +					"--copy: cannot accept both a destination PID "
> +					"-d/--dest and a command"))
> +			} else {
> +				args->exec_argv_offset = optind;
> +			}
> +			break;
> +		}
> +	}
> +
> +	if (argc <= optind) {
> +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
> +			bad_usage(_(
> +				"--new: requires either a -p/--pid or a command"));
> +		}
> +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
> +			bad_usage(_(
> +				"--copy: requires either a -d/--dest or a command"));
> +		}
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct args args = { 0 };
> +	args.cmd = SCHED_CORE_CMD_GET;
> +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +
> +	setlocale(LC_ALL, "");
> +	bindtextdomain(PACKAGE, LOCALEDIR);
> +	textdomain(PACKAGE);
> +	close_stdout_atexit();
> +
> +	parse_arguments(argc, argv, &args);
> +
> +	if (!is_core_sched_supported()) {
> +		errx(ENOTSUP, _("Does your kernel support CONFIG_SCHED_CORE?"));

Afaik errnos are not guaranteed to have the same numeric value
everywhere. If you want to signal something special, use your own
constant (and document it in the manpage).

> +	}
> +
> +	sched_core_cookie cookie;
> +
> +	switch (args.cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		if (args.pid) {
> +			cookie = core_sched_get_cookie(args.pid);
> +			if (cookie) {
> +				printf(_("%s: cookie of pid %d is 0x%lx\n"),
> +				       program_invocation_short_name, args.pid,
> +				       cookie);

program_invocation_short_name seems unnecessary.

The pid itself also seems unnecessary as the users know which cookie
they asked for.

Ah, taskset works like this. I disagree :-)

> +			} else {
> +				errx(ENODATA,
> +				     _("pid %d doesn't have a core scheduling cookie"),
> +				     args.pid);

Is this really a failure case?
I would make the print only on --verbose and otherwise succeed without
output.

> +			}
> +		} else {
> +			usage();

Why not print the current cookie here?

> +		}
> +		break;
> +	case SCHED_CORE_CMD_NEW:
> +		if (args.pid) {
> +			core_sched_create_cookie(args.pid, args.type);
> +			core_sched_get_and_print_cookie(args.pid);
> +		} else {
> +			core_sched_exec_with_cookie(&args, argv);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		if (args.dest)
> +			core_sched_copy_cookie(args.pid, args.dest, args.type);
> +		else
> +			core_sched_exec_with_cookie(&args, argv);
> +		break;
> +	default:
> +		usage();
> +	}
> +}
> diff --git a/tests/commands.sh b/tests/commands.sh
> index 5674c5ff0..9eef92ccb 100644
> --- a/tests/commands.sh
> +++ b/tests/commands.sh
> @@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
>  TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
>  TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
>  TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
> +TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
>  TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
>  TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
>  TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
> diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
> new file mode 100644
> index 000000000..97ab7ea23
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-change-cookie-of-parent
> @@ -0,0 +1 @@
> +coresched: cookie of pid OWN_PID is DIFFERENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-get-own-pid-no-cookie b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
> new file mode 100644
> index 000000000..7f64a98b3
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
> @@ -0,0 +1 @@
> +coresched: pid OWN_PID doesn't have a core scheduling cookie
> diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
> new file mode 100644
> index 000000000..556e31a65
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
> @@ -0,0 +1 @@
> +coresched: cookie of pid OWN_PID is COOKIE
> diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid b/tests/expected/schedutils/coresched-set-cookie-own-pid
> new file mode 100644
> index 000000000..1816a4fbd
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-set-cookie-own-pid
> @@ -0,0 +1 @@
> +coresched: set cookie of PID OWN_PID to COOKIE
> diff --git a/tests/expected/schedutils/coresched-spawn-child-with-new-cookie b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
> new file mode 100644
> index 000000000..5b9c40052
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
> @@ -0,0 +1 @@
> +DIFFERENT_COOKIE
> diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
> new file mode 100755
> index 000000000..5381a56f2
> --- /dev/null
> +++ b/tests/ts/schedutils/coresched
> @@ -0,0 +1,55 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: EUPL-1.2
> +#
> +# This file is part of util-linux
> +#
> +# Copyright (C) 2024 Thijs Raymakers
> +# Licensed under the EUPL v1.2
> +
> +TS_TOPDIR="${0%/*}/../.."
> +TS_DESC="coresched"
> +
> +. "$TS_TOPDIR"/functions.sh
> +ts_init "$*"
> +
> +ts_check_test_command "$TS_CMD_CORESCHED"
> +
> +# The output of coresched contains PIDs and core scheduling cookies, both of which should be
> +# assumed to be random values as we have no control over them. The tests replace these values
> +# with sed before writing them to the output file, so it can match the expected output file.
> +# - The PID of this bash script is replaced with the placeholder `OWN_PID`
> +# - The core scheduling cookie of this bash script is replaced by `COOKIE`
> +# - Any other cookie is replaced by `DIFFERENT_COOKIE`
> +# The behavior of coresched does not depend on the exact values of these cookies, so using
> +# placeholder values does not change the behavior tests.
> +ts_init_subtest "get-own-pid-no-cookie"
> +$TS_CMD_CORESCHED -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g" >> "$TS_OUTPUT"

This test fails if the process starting it already has a cookie set.
Also in that case the output is printed past the redirections.

> +ts_finalize_subtest
> +
> +ts_init_subtest "set-cookie-own-pid"
> +CORESCHED_OUTPUT=$($TS_CMD_CORESCHED -v -n -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g")
> +CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
> +CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
> +echo "$CORESCHED_OUTPUT" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "get-own-pid-with-cookie"
> +$TS_CMD_CORESCHED -p $$ | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "spawn-child-with-new-cookie"
> +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -p \$\$" \

When coresched without arguments prints the current value this bash
trickery can be removed.

> +  | sed 's/^.*\(0x.*$\)/\1/g' \
> +  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
> +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "change-cookie-of-parent"
> +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
> +$TS_CMD_CORESCHED -p $$ \
> +  | sed "s/$$/OWN_PID/g" \
> +  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
> +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_finalize
> -- 
> 2.44.0
> 

^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v4] coresched: Manage core scheduling cookies for tasks
  2024-04-05  6:26                     ` Thomas Weißschuh
@ 2024-04-05 14:14                       ` Phil Auld
  2024-04-08 21:16                         ` [PATCH v5] " Thijs Raymakers
  0 siblings, 1 reply; 42+ messages in thread
From: Phil Auld @ 2024-04-05 14:14 UTC (permalink / raw)
  To: Thomas Weißschuh; +Cc: Thijs Raymakers, kzak, util-linux

On Fri, Apr 05, 2024 at 08:26:08AM +0200 Thomas Weißschuh wrote:
> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
> > Co-authored-by: Phil Auld <pauld@redhat.com>
> > Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> > Signed-off-by: Phil Auld <pauld@redhat.com>
> > ---
> > 
> > Hi Thomas,
> > 
> > Thanks for your comments. This patch addresses most of your remarks. It
> > also adds Phil Auld as a Co-author since we went back and forth on the
> > design quite a lot. His feedback has been valuable and his ideas have
> > been integrated in the util to accomodate for both our usecases.
> 
> Thanks, some more comments below.
> 
> > Op 01-04-2024 om 7:18 p.m. schreef Thomas Weißschuh:
> > > I guess you are aware, but the manpage is empty.
> > 
> > I've added the manpage in this version of the patch.
> > 
> > > Technically all types ending in _t are reserved for libc.
> > > And a conflict for cookie_t doesn't even sound that improbable.
> > > How about sched_core_scope, sched_core_cookie and enum sched_core_cmd?
> > 
> > Good suggestion, the names of these types have been changed.
> > 
> > > Would a single call to core_sched_get_cookie() not be a reliable check
> > > that can be done and reported once?
> > > ...
> > > To make clear that this works like err{,x}() maybe call it err_prctl().
> > > It's unfortunate that this has to be a macro.
> > > There are verrx() and verr() which could make this an inline function.
> > > We would need fallback implementations for it in include/c.h though,
> > > which doesn't look so hard.
> > 
> > I Initially wrote it like this to avoid doing more prctl calls than
> > necessary. All the PR_SCHED_CORE* prctl calls indicate whether or not
> > the kernel supports core scheduling whenever they return with errno
> > EINVAL.
> > 
> > However, if support is explicitly checked beforehand, then the error
> > handling becomes a bit simpler and doesn't rely on macros anymore.
> > Therefore, I've changed to code to explicitly check for kernel support
> > beforehand. This change also removes the need for the `check_prctl`
> > macro.
> 
> Thanks, would it make sense to expose this check via the CLI?
> That could then used by the tests to skip themselves if the system does
> not provide core scheduling on old kernels or qemu-user.
>

I don't think so. It's checking by doing an operation so just do a "get"
if you really want to check first. An explicit check would just be that
anyway. 


> > > It would be nicer if these helpers are closer named to the prctl
> > > operations.
> > > sched_core_share_from_thread() for example.
> > 
> > I've followed the terminology of the kernel documentation of core
> > scheduling (Documentation/admin-guide/hw-vuln/core-scheduling.rst) here.
> > There, `PR_SCHED_CORE_SHARE_TO` is described as "push core_sched cookie
> > to pid." and `PR_SCHED_CORE_SHARE_FROM` is described as "pull core_sched
> > cookie from pid"
> > 
> > I prefer that terminology because I find it a little less ambiguous than
> > SHARE_TO and SHARE_FROM.
> 
> Ok.
> 
> > Op 01-04-2024 om 7:18 p.m. schreef Thomas Weißschuh:
> > > On 2024-03-27 16:30:08+0100, Thijs Raymakers wrote:
> > >> +	printf("%s: copied cookie 0x%lx from PID %d to PID %d\n",
> > >> +	       program_invocation_short_name, before, from, to);
> > > stderr and only with --verbose?
> > 
> > This has been changed now. All operations, except getting a cookie,
> > will no longer print this type of information unless --verbose is passed.
> > 
> > > Also, please some tests.
> > 
> > This version adds 5 tests for coresched:
> > - getting the cookie of itself when no cookie is set
> > - setting the cookie of itself
> > - getting the cookie of itself when the cookie is set (must be equal to
> >   the cookie that was set)
> > - spawn a child process with a new cookie (must be different than the
> >   cookie of the parent process)
> > - spawn a child process with a new cookie and push that cookie to the
> >   parent process (afterwards, the cookie of the parent must be different)
> 
> Nice!
> 
> > An interdiff between the previous version and this version is listed
> > below. It also address your other remarks not mentioned in this email.
> > 
> > Only the bash completion script is missing, but I think that is better
> > suited for a follow-up patch.
> 
> Sounds fair.
> 
> > Thijs
> > 
> > Interdiff against v3:
> 
> [snip]
> 
> >  .gitignore                                    |   1 +
> >  bash-completion/coresched                     |   0
> >  configure.ac                                  |  12 +-
> >  meson.build                                   |  16 +-
> >  meson_options.txt                             |   2 +-
> >  schedutils/Makemodule.am                      |   8 +
> >  schedutils/coresched.1.adoc                   | 116 ++++++
> >  schedutils/coresched.c                        | 380 ++++++++++++++++++
> >  tests/commands.sh                             |   1 +
> >  .../coresched-change-cookie-of-parent         |   1 +
> >  .../coresched-get-own-pid-no-cookie           |   1 +
> >  .../coresched-get-own-pid-with-cookie         |   1 +
> >  .../schedutils/coresched-set-cookie-own-pid   |   1 +
> >  .../coresched-spawn-child-with-new-cookie     |   1 +
> >  tests/ts/schedutils/coresched                 |  55 +++
> >  15 files changed, 590 insertions(+), 6 deletions(-)
> >  create mode 100644 bash-completion/coresched
> >  create mode 100644 schedutils/coresched.1.adoc
> >  create mode 100644 schedutils/coresched.c
> >  create mode 100644 tests/expected/schedutils/coresched-change-cookie-of-parent
> >  create mode 100644 tests/expected/schedutils/coresched-get-own-pid-no-cookie
> >  create mode 100644 tests/expected/schedutils/coresched-get-own-pid-with-cookie
> >  create mode 100644 tests/expected/schedutils/coresched-set-cookie-own-pid
> >  create mode 100644 tests/expected/schedutils/coresched-spawn-child-with-new-cookie
> >  create mode 100755 tests/ts/schedutils/coresched
> 
> [snip]
> 
> > diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> > new file mode 100644
> > index 000000000..3d9392b1a
> > --- /dev/null
> > +++ b/schedutils/coresched.c
> > @@ -0,0 +1,380 @@
> > +/**
> > + * SPDX-License-Identifier: EUPL-1.2
> > + *
> > + * coresched.c - manage core scheduling cookies for tasks
> > + *
> > + * Copyright (C) 2024 Thijs Raymakers, Phil Auld
> > + * Licensed under the EUPL v1.2
> > + */
> > +
> > +#include <getopt.h>
> > +#include <stdbool.h>
> > +#include <stdio.h>
> > +#include <sys/prctl.h>
> > +#include <sys/wait.h>
> > +#include <unistd.h>
> > +
> > +#include "c.h"
> > +#include "closestream.h"
> > +#include "nls.h"
> > +#include "optutils.h"
> > +#include "strutils.h"
> > +
> > +// These definitions might not be defined in the header files, even if the
> > +// prctl interface in the kernel accepts them as valid.
> > +#ifndef PR_SCHED_CORE
> > +#define PR_SCHED_CORE 62
> > +#endif
> > +#ifndef PR_SCHED_CORE_GET
> > +#define PR_SCHED_CORE_GET 0
> > +#endif
> > +#ifndef PR_SCHED_CORE_CREATE
> > +#define PR_SCHED_CORE_CREATE 1
> > +#endif
> > +#ifndef PR_SCHED_CORE_SHARE_TO
> > +#define PR_SCHED_CORE_SHARE_TO 2
> > +#endif
> > +#ifndef PR_SCHED_CORE_SHARE_FROM
> > +#define PR_SCHED_CORE_SHARE_FROM 3
> > +#endif
> > +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> > +#define PR_SCHED_CORE_SCOPE_THREAD 0
> > +#endif
> > +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> > +#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> > +#endif
> > +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> > +#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> > +#endif
> > +
> > +typedef int sched_core_scope;
> > +typedef unsigned long sched_core_cookie;
> > +typedef enum {
> > +	SCHED_CORE_CMD_GET,
> > +	SCHED_CORE_CMD_NEW,
> > +	SCHED_CORE_CMD_COPY,
> > +} sched_core_cmd;
> > +
> > +struct args {
> > +	pid_t pid;
> > +	pid_t dest;
> > +	sched_core_scope type;
> > +	sched_core_cmd cmd;
> > +	int exec_argv_offset;
> > +};
> > +
> > +static bool sched_core_verbose = false;
> > +
> > +static void __attribute__((__noreturn__)) usage(void)
> > +{
> > +	fputs(USAGE_HEADER, stdout);
> > +	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
> > +	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
> > +		program_invocation_short_name);
> > +	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> > +		program_invocation_short_name);
> > +	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
> > +		program_invocation_short_name);
> > +	fprintf(stdout,
> > +		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> > +		program_invocation_short_name);
> > +
> > +	fputs(USAGE_SEPARATOR, stdout);
> > +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> > +
> > +	fputs(USAGE_FUNCTIONS, stdout);
> > +	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
> > +		  "                      execute a program with a new cookie."),
> > +		stdout);
> > +	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
> > +		  "                      either another PID, or copy it to a new program"),
> > +		stdout);
> > +	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
> > +		  "   the PID provided via --pid.\n"),
> > +		stdout);
> > +
> > +	fputs(USAGE_OPTIONS, stdout);
> > +	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
> > +	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
> > +		  "                      the destination PID where to copy the cookie to."),
> > +		stdout);
> > +	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
> > +		  "                      when a new core scheduling cookie is created.\n"
> > +		  "                      Can be one of the following: pid, tgid or pgid.\n"
> > +		  "                      The default is tgid."),
> > +		stdout);
> > +	fputs(USAGE_SEPARATOR, stdout);
> > +	fputsln(_(" -v, --verbose      verbose"), stdout);
> > +	fprintf(stdout,
> > +		USAGE_HELP_OPTIONS(
> > +			20)); /* char offset to align option descriptions */
> > +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> > +	exit(EXIT_SUCCESS);
> > +}
> > +
> > +#define bad_usage(FMT...) \
> > +	warnx(FMT);       \
> > +	errtryhelp(EXIT_FAILURE);
> > +
> > +static sched_core_cookie core_sched_get_cookie(pid_t pid)
> > +{
> > +	sched_core_cookie cookie = 0;
> > +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
> > +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> > +		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
> > +	}
> > +	return cookie;
> > +}
> > +
> > +static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
> > +{
> > +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
> > +		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
> > +	}
> 
> General codestyle is to leave out braces around single-statement blocks.
> (I mentioned this before, if you want to keep them please mention it)
> 
> > +}
> > +
> > +static void core_sched_pull_cookie(pid_t from)
> > +{
> > +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> > +		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
> > +		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
> > +	}
> > +}
> > +
> > +static void core_sched_push_cookie(pid_t to, sched_core_scope type)
> > +{
> > +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
> > +		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
> > +	}
> > +}
> > +
> > +static void core_sched_copy_cookie(pid_t from, pid_t to,
> > +				   sched_core_scope to_type)
> > +{
> > +	core_sched_pull_cookie(from);
> > +	core_sched_push_cookie(to, to_type);
> > +
> > +	if (sched_core_verbose) {
> > +		sched_core_cookie before = core_sched_get_cookie(from);
> > +		fprintf(stderr,
> > +			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
> > +			program_invocation_short_name, before, from, to);
> > +	}
> > +}
> > +
> > +static void core_sched_get_and_print_cookie(pid_t pid)
> > +{
> > +	if (sched_core_verbose) {
> > +		sched_core_cookie after = core_sched_get_cookie(pid);
> > +		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
> > +			program_invocation_short_name, pid, after);
> > +	}
> > +}
> > +
> > +static void core_sched_exec_with_cookie(struct args *args, char **argv)
> > +{
> > +	if (!args->exec_argv_offset)
> > +		usage();
> > +
> > +	// Move the argument list to the first argument of the program
> > +	argv = &argv[args->exec_argv_offset];
> > +
> > +	// If a source PID is provided, try to copy the cookie from
> > +	// that PID. Otherwise, create a brand new cookie with the
> > +	// provided type.
> > +	if (args->pid) {
> > +		core_sched_pull_cookie(args->pid);
> > +		core_sched_get_and_print_cookie(args->pid);
> > +	} else {
> > +		pid_t pid = getpid();
> > +		core_sched_create_cookie(pid, args->type);
> > +		core_sched_get_and_print_cookie(pid);
> > +	}
> > +
> > +	if (execvp(argv[0], argv)) {
> > +		errexec(argv[0]);
> > +	}
> > +}
> > +
> > +// If PR_SCHED_CORE is not recognized, or not supported on this system,
> > +// then prctl will set errno to EINVAL. Assuming all other operands of
> > +// prctl are valid, we can use errno==EINVAL as a check to see whether
> > +// core scheduling is available on this system.
> > +static bool is_core_sched_supported(void)
> > +{
> > +	sched_core_cookie cookie = 0;
> > +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
> > +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
> > +		if (errno == EINVAL) {
> > +			return false;
> > +		}
> > +	}
> > +	return true;
> > +}
> > +
> > +static sched_core_scope parse_core_sched_type(char *str)
> > +{
> > +	if (!strncmp(str, "pid", 4))
> 
> These can be plain strcmp().
> 
> > +		return PR_SCHED_CORE_SCOPE_THREAD;
> > +	else if (!strncmp(str, "tgid", 5))
> > +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> > +	else if (!strncmp(str, "pgid", 5))
> > +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
> > +
> > +	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
> > +		  str);
> > +}
> > +
> > +static void parse_arguments(int argc, char **argv, struct args *args)
> > +{
> > +	int c;
> > +
> > +	static const struct option longopts[] = {
> > +		{ "new", no_argument, NULL, 'n' },
> > +		{ "copy", no_argument, NULL, 'c' },
> > +		{ "pid", required_argument, NULL, 'p' },
> > +		{ "dest", required_argument, NULL, 'd' },
> > +		{ "type", required_argument, NULL, 't' },
> > +		{ "verbose", no_argument, NULL, 'v' },
> > +		{ "version", no_argument, NULL, 'V' },
> > +		{ "help", no_argument, NULL, 'h' },
> > +		{ NULL, 0, NULL, 0 }
> > +	};
> > +	static const ul_excl_t excl[] = {
> > +		{ 'c', 'n' }, // Cannot do both --new and --copy
> > +		{ 'd', 'n' }, // Cannot have both --new and --dest
> > +		{ 0 }
> > +	};
> > +
> > +	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
> > +
> > +	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
> > +	       -1) {
> > +		err_exclusive_options(c, longopts, excl, excl_st);
> > +		switch (c) {
> > +		case 'n':
> > +			args->cmd = SCHED_CORE_CMD_NEW;
> > +			break;
> > +		case 'c':
> > +			args->cmd = SCHED_CORE_CMD_COPY;
> > +			break;
> > +		case 'p':
> > +			args->pid = strtopid_or_err(
> > +				optarg, _("Failed to parse PID for -p/--pid"));
> > +			break;
> > +		case 'd':
> > +			args->dest = strtopid_or_err(
> > +				optarg, _("Failed to parse PID for -d/--dest"));
> > +			break;
> > +		case 't':
> > +			args->type = parse_core_sched_type(optarg);
> > +			break;
> > +		case 'v':
> > +			sched_core_verbose = true;
> > +			break;
> > +		case 'V':
> > +			print_version(EXIT_SUCCESS);
> > +		case 'h':
> > +			usage();
> > +		default:
> > +			errtryhelp(EXIT_FAILURE);
> > +		}
> > +	}
> > +
> > +	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
> > +		bad_usage(_("--copy: requires a -p/--pid"));
> > +	}
> > +
> > +	// More arguments have been passed, which means that the user wants to run
> > +	// another program with a core scheduling cookie.
> > +	if (argc > optind) {
> > +		switch (args->cmd) {
> > +		case SCHED_CORE_CMD_GET:
> > +			bad_usage(_("Unknown command"));
> > +			break;
> > +		case SCHED_CORE_CMD_NEW:
> > +			if (args->pid) {
> > +				bad_usage(_(
> > +					"--new: cannot accept both a -p/--pid and a command"));
> > +			} else {
> > +				args->exec_argv_offset = optind;
> > +			}
> > +			break;
> > +		case SCHED_CORE_CMD_COPY:
> > +			if (args->dest) {
> > +				bad_usage(_(
> > +					"--copy: cannot accept both a destination PID "
> > +					"-d/--dest and a command"))
> > +			} else {
> > +				args->exec_argv_offset = optind;
> > +			}
> > +			break;
> > +		}
> > +	}
> > +
> > +	if (argc <= optind) {
> > +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
> > +			bad_usage(_(
> > +				"--new: requires either a -p/--pid or a command"));
> > +		}
> > +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
> > +			bad_usage(_(
> > +				"--copy: requires either a -d/--dest or a command"));
> > +		}
> > +	}
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	struct args args = { 0 };
> > +	args.cmd = SCHED_CORE_CMD_GET;
> > +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> > +
> > +	setlocale(LC_ALL, "");
> > +	bindtextdomain(PACKAGE, LOCALEDIR);
> > +	textdomain(PACKAGE);
> > +	close_stdout_atexit();
> > +
> > +	parse_arguments(argc, argv, &args);
> > +
> > +	if (!is_core_sched_supported()) {
> > +		errx(ENOTSUP, _("Does your kernel support CONFIG_SCHED_CORE?"));
> 
> Afaik errnos are not guaranteed to have the same numeric value
> everywhere. If you want to signal something special, use your own
> constant (and document it in the manpage).
>

I don't think we should make up an errno but use one that already exists.

If not ENOPTSUP we can just leave it as EINVAL I suppose. That should be
the same everywhere. 


> > +	}
> > +
> > +	sched_core_cookie cookie;
> > +
> > +	switch (args.cmd) {
> > +	case SCHED_CORE_CMD_GET:
> > +		if (args.pid) {
> > +			cookie = core_sched_get_cookie(args.pid);
> > +			if (cookie) {
> > +				printf(_("%s: cookie of pid %d is 0x%lx\n"),
> > +				       program_invocation_short_name, args.pid,
> > +				       cookie);
> 
> program_invocation_short_name seems unnecessary.
> 
> The pid itself also seems unnecessary as the users know which cookie
> they asked for.
> 
> Ah, taskset works like this. I disagree :-)
>

Yes, trying to make it sort of obvious for taskset users since I think
they will mostly be the same people.


> > +			} else {
> > +				errx(ENODATA,
> > +				     _("pid %d doesn't have a core scheduling cookie"),
> > +				     args.pid);
> 
> Is this really a failure case?
> I would make the print only on --verbose and otherwise succeed without
> output.

Agree. This is not an error. 

Cheers,
Phil


> 
> > +			}
> > +		} else {
> > +			usage();
> 
> Why not print the current cookie here?
> 
> > +		}
> > +		break;
> > +	case SCHED_CORE_CMD_NEW:
> > +		if (args.pid) {
> > +			core_sched_create_cookie(args.pid, args.type);
> > +			core_sched_get_and_print_cookie(args.pid);
> > +		} else {
> > +			core_sched_exec_with_cookie(&args, argv);
> > +		}
> > +		break;
> > +	case SCHED_CORE_CMD_COPY:
> > +		if (args.dest)
> > +			core_sched_copy_cookie(args.pid, args.dest, args.type);
> > +		else
> > +			core_sched_exec_with_cookie(&args, argv);
> > +		break;
> > +	default:
> > +		usage();
> > +	}
> > +}
> > diff --git a/tests/commands.sh b/tests/commands.sh
> > index 5674c5ff0..9eef92ccb 100644
> > --- a/tests/commands.sh
> > +++ b/tests/commands.sh
> > @@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
> >  TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
> >  TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
> >  TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
> > +TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
> >  TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
> >  TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
> >  TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
> > diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
> > new file mode 100644
> > index 000000000..97ab7ea23
> > --- /dev/null
> > +++ b/tests/expected/schedutils/coresched-change-cookie-of-parent
> > @@ -0,0 +1 @@
> > +coresched: cookie of pid OWN_PID is DIFFERENT_COOKIE
> > diff --git a/tests/expected/schedutils/coresched-get-own-pid-no-cookie b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
> > new file mode 100644
> > index 000000000..7f64a98b3
> > --- /dev/null
> > +++ b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
> > @@ -0,0 +1 @@
> > +coresched: pid OWN_PID doesn't have a core scheduling cookie
> > diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
> > new file mode 100644
> > index 000000000..556e31a65
> > --- /dev/null
> > +++ b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
> > @@ -0,0 +1 @@
> > +coresched: cookie of pid OWN_PID is COOKIE
> > diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid b/tests/expected/schedutils/coresched-set-cookie-own-pid
> > new file mode 100644
> > index 000000000..1816a4fbd
> > --- /dev/null
> > +++ b/tests/expected/schedutils/coresched-set-cookie-own-pid
> > @@ -0,0 +1 @@
> > +coresched: set cookie of PID OWN_PID to COOKIE
> > diff --git a/tests/expected/schedutils/coresched-spawn-child-with-new-cookie b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
> > new file mode 100644
> > index 000000000..5b9c40052
> > --- /dev/null
> > +++ b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
> > @@ -0,0 +1 @@
> > +DIFFERENT_COOKIE
> > diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
> > new file mode 100755
> > index 000000000..5381a56f2
> > --- /dev/null
> > +++ b/tests/ts/schedutils/coresched
> > @@ -0,0 +1,55 @@
> > +#!/bin/bash
> > +# SPDX-License-Identifier: EUPL-1.2
> > +#
> > +# This file is part of util-linux
> > +#
> > +# Copyright (C) 2024 Thijs Raymakers
> > +# Licensed under the EUPL v1.2
> > +
> > +TS_TOPDIR="${0%/*}/../.."
> > +TS_DESC="coresched"
> > +
> > +. "$TS_TOPDIR"/functions.sh
> > +ts_init "$*"
> > +
> > +ts_check_test_command "$TS_CMD_CORESCHED"
> > +
> > +# The output of coresched contains PIDs and core scheduling cookies, both of which should be
> > +# assumed to be random values as we have no control over them. The tests replace these values
> > +# with sed before writing them to the output file, so it can match the expected output file.
> > +# - The PID of this bash script is replaced with the placeholder `OWN_PID`
> > +# - The core scheduling cookie of this bash script is replaced by `COOKIE`
> > +# - Any other cookie is replaced by `DIFFERENT_COOKIE`
> > +# The behavior of coresched does not depend on the exact values of these cookies, so using
> > +# placeholder values does not change the behavior tests.
> > +ts_init_subtest "get-own-pid-no-cookie"
> > +$TS_CMD_CORESCHED -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g" >> "$TS_OUTPUT"
> 
> This test fails if the process starting it already has a cookie set.
> Also in that case the output is printed past the redirections.
> 
> > +ts_finalize_subtest
> > +
> > +ts_init_subtest "set-cookie-own-pid"
> > +CORESCHED_OUTPUT=$($TS_CMD_CORESCHED -v -n -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g")
> > +CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
> > +CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
> > +echo "$CORESCHED_OUTPUT" >> "$TS_OUTPUT"
> > +ts_finalize_subtest
> > +
> > +ts_init_subtest "get-own-pid-with-cookie"
> > +$TS_CMD_CORESCHED -p $$ | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
> > +ts_finalize_subtest
> > +
> > +ts_init_subtest "spawn-child-with-new-cookie"
> > +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -p \$\$" \
> 
> When coresched without arguments prints the current value this bash
> trickery can be removed.
> 
> > +  | sed 's/^.*\(0x.*$\)/\1/g' \
> > +  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
> > +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> > +ts_finalize_subtest
> > +
> > +ts_init_subtest "change-cookie-of-parent"
> > +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
> > +$TS_CMD_CORESCHED -p $$ \
> > +  | sed "s/$$/OWN_PID/g" \
> > +  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
> > +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> > +ts_finalize_subtest
> > +
> > +ts_finalize
> > -- 
> > 2.44.0
> > 
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v5] coresched: Manage core scheduling cookies for tasks
  2024-04-05 14:14                       ` Phil Auld
@ 2024-04-08 21:16                         ` Thijs Raymakers
  2024-04-09  6:12                           ` Thomas Weißschuh
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-08 21:16 UTC (permalink / raw)
  To: thomas, pauld; +Cc: kzak, util-linux, Thijs Raymakers

Co-authored-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
Signed-off-by: Phil Auld <pauld@redhat.com>
---

Hi Thomas and Phil,

Op 05-04-2024 om 4:14 p.m. schreef Phil Auld:
> On Fri, Apr 05, 2024 at 08:26:08AM +0200 Thomas Weißschuh wrote:
>> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
>>> Therefore, I've changed to code to explicitly check for kernel support
>>> beforehand. This change also removes the need for the `check_prctl`
>>> macro.
>>
>> Thanks, would it make sense to expose this check via the CLI?
>> That could then used by the tests to skip themselves if the system does
>> not provide core scheduling on old kernels or qemu-user.
>>
> 
> I don't think so. It's checking by doing an operation so just do a "get"
> if you really want to check first. An explicit check would just be that
> anyway. 

I agree with Phil, I don't think it would be necessary. I've added the check
to the tests that allows them to be skipped if core scheduling is not
available (such as on older kernels like you mentioned). This check just 
runs `coresched`, which will do the check implicitly.

Op 05-04-2024 om 8:26 a.m. schreef Thomas Weißschuh:
> General codestyle is to leave out braces around single-statement blocks.
> (I mentioned this before, if you want to keep them please mention it)

Ah, I guess something went wrong there on my side. I have changed the braces
where applicable. 

(as a sidenote, I personally try to avoid leaving out the braces around
single-statement blocks, since it makes it slightly easier to introduce
logic bugs like the infamous goto fail bug from 2014)

Op 05-04-2024 om 8:26 a.m. schreef Thomas Weißschuh:
> These can be plain strcmp().

Changed!

Op 05-04-2024 om 4:14 p.m. schreef Phil Auld:
> On Fri, Apr 05, 2024 at 08:26:08AM +0200 Thomas Weißschuh wrote:
>> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
>>> +	if (!is_core_sched_supported()) {
>>> +		errx(ENOTSUP, _("Does your kernel support CONFIG_SCHED_CORE?"));
>>
>> Afaik errnos are not guaranteed to have the same numeric value
>> everywhere. If you want to signal something special, use your own
>> constant (and document it in the manpage).
>
> I don't think we should make up an errno but use one that already exists.
> 
> If not ENOPTSUP we can just leave it as EINVAL I suppose. That should be
> the same everywhere. 

I don't think there is a reason to differentiate the error code
between "failing because the kernel doesn't support it" and "any other
fatal failure". I've changed it to just return EXIT_FAILURE (1) in case
there is no support, since all other errors do that as well. The
displayed error message can be used to differentiate the errors.

Op 05-04-2024 om 8:26 a.m. schreef Thomas Weißschuh:
> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
>> +			if (cookie) {
>> +				printf(_("%s: cookie of pid %d is 0x%lx\n"),
>> +				       program_invocation_short_name, args.pid,
>> +				       cookie);
> program_invocation_short_name seems unnecessary.

Fair point, I've removed it.

Op 05-04-2024 om 4:14 p.m. schreef Phil Auld:
>Op 05-04-2024 om 8:26 a.m. schreef Thomas Weißschuh:
>> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
>>> +			} else {
>>> +				errx(ENODATA,
>>> +				     _("pid %d doesn't have a core scheduling cookie"),
>>> +				     args.pid);
>>
>> Is this really a failure case?
>> I would make the print only on --verbose and otherwise succeed without
>> output.
>
> Agree. This is not an error. 

I've merged the two cases into one. A core scheduling cookie of 0 is
indeed not an error, as it is still a valid value.

Op 05-04-2024 om 8:26 a.m. schreef Thomas Weißschuh:
> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
>> +			}
>> +		} else {
>> +			usage();
> Why not print the current cookie here?

I initially wrote it like this to make it more obvious what the command
does on first use because it will just show the help page, and thus saves
you from typing "coresched -h".

However, I do realise that this design makes it more inconvenient
to get the current cookie. To get the cookie of the current process you
now need to do something like "coresched -p $$", which isn't very
intuitive to use.
This has been changed and "coresched" will now print the cookie
of the current process.

Op 05-04-2024 om 8:26 a.m. schreef Thomas Weißschuh:
> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
>> +$TS_CMD_CORESCHED -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g" >> "$TS_OUTPUT"
>
> This test fails if the process starting it already has a cookie set.
> Also in that case the output is printed past the redirections.

Indeed, this test does fail if the process does already have a cookie.
Since there is no way to clear the cookie of the process, this test
doesn't make a lot of sense anyway. I've removed it.

Op 05-04-2024 om 8:26 a.m. schreef Thomas Weißschuh:
> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
>> +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -p \$\$" \
>
> When coresched without arguments prints the current value this bash
> trickery can be removed.

I've removed it from the spawn-child-with-new-cookie test.

I want to thank you both the comments, an interdiff with changes compared
to the the previous version is listed below.

Thijs


Interdiff against v4:
  diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
  index 34bc352a8..c83fcdf91 100644
  --- a/schedutils/coresched.1.adoc
  +++ b/schedutils/coresched.1.adoc
  @@ -37,7 +37,7 @@ Assign a new cookie to an existing PID or to execute _command_ with a new cookie
   *-c*, *--copy*::
   Copy the cookie from an existing PID to another PID, or execute _command_ with that copied cookie.
   
  -If no function is specified, it will retrieve and print the cookie of a task.
  +If no function is specified, it will print the cookie of the current task, or of the task specified with the *-p* option.
   
   == OPTIONS
   *-p*, *--pid* _PID_::
  @@ -62,6 +62,9 @@ Display help text and exit.
   Print version and exit.
   
   == EXAMPLES
  +Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
  +*{command}*
  +
   Get the core scheduling cookie of a task with PID _123_{colon}::
   *{command} -p* _123_
   
  @@ -87,7 +90,12 @@ See the section "Ptrace access mode checking" in *ptrace*(2) for more informatio
   == RETURN VALUE
   On success, *{command}* returns 0.
   If *{command}* fails, it will print an error and return 1.
  -If core scheduling is not supported with your kernel (e.g. because *CONFIG_SCHED_CORE*=n), then it will return 95 (*ENOTSUP*).
  +
  +If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
  +
  +== NOTES
  +*{command}* requires core scheduling support in the kernel.
  +This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
   
   == AUTHORS
   mailto:thijs@raymakers.nl[Thijs Raymakers],
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index 3d9392b1a..03b50cd5e 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -112,40 +112,38 @@ static void __attribute__((__noreturn__)) usage(void)
   	exit(EXIT_SUCCESS);
   }
   
  -#define bad_usage(FMT...) \
  -	warnx(FMT);       \
  -	errtryhelp(EXIT_FAILURE);
  +#define bad_usage(FMT...)                 \
  +	do {                              \
  +		warnx(FMT);               \
  +		errtryhelp(EXIT_FAILURE); \
  +	} while (0)
   
   static sched_core_cookie core_sched_get_cookie(pid_t pid)
   {
   	sched_core_cookie cookie = 0;
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
  -		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
  +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
   		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
  -	}
   	return cookie;
   }
   
   static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
   {
  -	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0)) {
  +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
   		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
  -	}
   }
   
   static void core_sched_pull_cookie(pid_t from)
   {
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
  -		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
  +		  PR_SCHED_CORE_SCOPE_THREAD, 0))
   		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
  -	}
   }
   
   static void core_sched_push_cookie(pid_t to, sched_core_scope type)
   {
  -	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
  +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
   		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
  -	}
   }
   
   static void core_sched_copy_cookie(pid_t from, pid_t to,
  @@ -191,9 +189,8 @@ static void core_sched_exec_with_cookie(struct args *args, char **argv)
   		core_sched_get_and_print_cookie(pid);
   	}
   
  -	if (execvp(argv[0], argv)) {
  +	if (execvp(argv[0], argv))
   		errexec(argv[0]);
  -	}
   }
   
   // If PR_SCHED_CORE is not recognized, or not supported on this system,
  @@ -204,21 +201,20 @@ static bool is_core_sched_supported(void)
   {
   	sched_core_cookie cookie = 0;
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
  -		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
  -		if (errno == EINVAL) {
  +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
  +		if (errno == EINVAL)
   			return false;
  -		}
  -	}
  +
   	return true;
   }
   
   static sched_core_scope parse_core_sched_type(char *str)
   {
  -	if (!strncmp(str, "pid", 4))
  +	if (!strcmp(str, "pid"))
   		return PR_SCHED_CORE_SCOPE_THREAD;
  -	else if (!strncmp(str, "tgid", 5))
  +	else if (!strcmp(str, "tgid"))
   		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
  -	else if (!strncmp(str, "pgid", 5))
  +	else if (!strcmp(str, "pgid"))
   		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
   
   	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
  @@ -281,9 +277,8 @@ static void parse_arguments(int argc, char **argv, struct args *args)
   		}
   	}
   
  -	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid) {
  +	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid)
   		bad_usage(_("--copy: requires a -p/--pid"));
  -	}
   
   	// More arguments have been passed, which means that the user wants to run
   	// another program with a core scheduling cookie.
  @@ -293,34 +288,30 @@ static void parse_arguments(int argc, char **argv, struct args *args)
   			bad_usage(_("Unknown command"));
   			break;
   		case SCHED_CORE_CMD_NEW:
  -			if (args->pid) {
  +			if (args->pid)
   				bad_usage(_(
   					"--new: cannot accept both a -p/--pid and a command"));
  -			} else {
  +			else
   				args->exec_argv_offset = optind;
  -			}
   			break;
   		case SCHED_CORE_CMD_COPY:
  -			if (args->dest) {
  +			if (args->dest)
   				bad_usage(_(
   					"--copy: cannot accept both a destination PID "
  -					"-d/--dest and a command"))
  -			} else {
  +					"-d/--dest and a command"));
  +			else
   				args->exec_argv_offset = optind;
  -			}
   			break;
   		}
   	}
   
   	if (argc <= optind) {
  -		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid) {
  +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid)
   			bad_usage(_(
   				"--new: requires either a -p/--pid or a command"));
  -		}
  -		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest) {
  +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
   			bad_usage(_(
   				"--copy: requires either a -d/--dest or a command"));
  -		}
   	}
   }
   
  @@ -337,28 +328,18 @@ int main(int argc, char **argv)
   
   	parse_arguments(argc, argv, &args);
   
  -	if (!is_core_sched_supported()) {
  -		errx(ENOTSUP, _("Does your kernel support CONFIG_SCHED_CORE?"));
  -	}
  +	if (!is_core_sched_supported())
  +		errx(EXIT_FAILURE,
  +		     _("Does your kernel support CONFIG_SCHED_CORE?"));
   
   	sched_core_cookie cookie;
  +	pid_t pid;
   
   	switch (args.cmd) {
   	case SCHED_CORE_CMD_GET:
  -		if (args.pid) {
  -			cookie = core_sched_get_cookie(args.pid);
  -			if (cookie) {
  -				printf(_("%s: cookie of pid %d is 0x%lx\n"),
  -				       program_invocation_short_name, args.pid,
  -				       cookie);
  -			} else {
  -				errx(ENODATA,
  -				     _("pid %d doesn't have a core scheduling cookie"),
  -				     args.pid);
  -			}
  -		} else {
  -			usage();
  -		}
  +		pid = args.pid ? args.pid : getpid();
  +		cookie = core_sched_get_cookie(pid);
  +		printf(_("cookie of pid %d is 0x%lx\n"), pid, cookie);
   		break;
   	case SCHED_CORE_CMD_NEW:
   		if (args.pid) {
  diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
  index 97ab7ea23..d08118c02 100644
  --- a/tests/expected/schedutils/coresched-change-cookie-of-parent
  +++ b/tests/expected/schedutils/coresched-change-cookie-of-parent
  @@ -1 +1 @@
  -coresched: cookie of pid OWN_PID is DIFFERENT_COOKIE
  +cookie of pid OWN_PID is DIFFERENT_COOKIE
  diff --git a/tests/expected/schedutils/coresched-get-own-pid-no-cookie b/tests/expected/schedutils/coresched-get-own-pid-no-cookie
  deleted file mode 100644
  index 7f64a98b3..000000000
  --- a/tests/expected/schedutils/coresched-get-own-pid-no-cookie
  +++ /dev/null
  @@ -1 +0,0 @@
  -coresched: pid OWN_PID doesn't have a core scheduling cookie
  diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
  index 556e31a65..3d7f08040 100644
  --- a/tests/expected/schedutils/coresched-get-own-pid-with-cookie
  +++ b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
  @@ -1 +1 @@
  -coresched: cookie of pid OWN_PID is COOKIE
  +cookie of pid OWN_PID is COOKIE
  diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid b/tests/expected/schedutils/coresched-set-cookie-own-pid.err
  similarity index 100%
  rename from tests/expected/schedutils/coresched-set-cookie-own-pid
  rename to tests/expected/schedutils/coresched-set-cookie-own-pid.err
  diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
  index 5381a56f2..ed96d89de 100755
  --- a/tests/ts/schedutils/coresched
  +++ b/tests/ts/schedutils/coresched
  @@ -13,6 +13,14 @@ TS_DESC="coresched"
   ts_init "$*"
   
   ts_check_test_command "$TS_CMD_CORESCHED"
  +ts_check_test_command "tee"
  +ts_check_test_command "sed"
  +
  +# If there is no kernel support, skip the test suite
  +CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
  +if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
  +  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
  +fi
   
   # The output of coresched contains PIDs and core scheduling cookies, both of which should be
   # assumed to be random values as we have no control over them. The tests replace these values
  @@ -22,23 +30,22 @@ ts_check_test_command "$TS_CMD_CORESCHED"
   # - Any other cookie is replaced by `DIFFERENT_COOKIE`
   # The behavior of coresched does not depend on the exact values of these cookies, so using
   # placeholder values does not change the behavior tests.
  -ts_init_subtest "get-own-pid-no-cookie"
  -$TS_CMD_CORESCHED -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g" >> "$TS_OUTPUT"
  -ts_finalize_subtest
  -
   ts_init_subtest "set-cookie-own-pid"
  -CORESCHED_OUTPUT=$($TS_CMD_CORESCHED -v -n -p $$ 3>&2 2>&1 1>&3 | sed "s/$$/OWN_PID/g")
  +CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v -n -p $$ | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 | sed "s/$$/OWN_PID/g")
   CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
  +if [ -z "$CORESCHED_COOKIE" ]; then
  +  ts_failed "empty value for CORESCHED_COOKIE "
  +fi
   CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
  -echo "$CORESCHED_OUTPUT" >> "$TS_OUTPUT"
  +echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
   ts_finalize_subtest
   
   ts_init_subtest "get-own-pid-with-cookie"
  -$TS_CMD_CORESCHED -p $$ | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
  +$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
   ts_finalize_subtest
   
   ts_init_subtest "spawn-child-with-new-cookie"
  -$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -p \$\$" \
  +$TS_CMD_CORESCHED -n -- "$TS_CMD_CORESCHED" 2>> "$TS_ERRLOG" \
     | sed 's/^.*\(0x.*$\)/\1/g' \
     | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
     | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
  @@ -46,7 +53,7 @@ ts_finalize_subtest
   
   ts_init_subtest "change-cookie-of-parent"
   $TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
  -$TS_CMD_CORESCHED -p $$ \
  +$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" \
     | sed "s/$$/OWN_PID/g" \
     | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
     | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 124 ++++++
 schedutils/coresched.c                        | 361 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-change-cookie-of-parent         |   1 +
 .../coresched-get-own-pid-with-cookie         |   1 +
 .../coresched-set-cookie-own-pid.err          |   1 +
 .../coresched-spawn-child-with-new-cookie     |   1 +
 tests/ts/schedutils/coresched                 |  62 +++
 14 files changed, 585 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-change-cookie-of-parent
 create mode 100644 tests/expected/schedutils/coresched-get-own-pid-with-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-own-pid.err
 create mode 100644 tests/expected/schedutils/coresched-spawn-child-with-new-cookie
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 9600ce49f..a3a648753 100644
--- a/meson.build
+++ b/meson.build
@@ -3111,13 +3111,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..c83fcdf91
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,124 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [options] *-p* _pid_
+
+*{command}* [options] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*-n*, *--new*::
+Assign a new cookie to an existing PID or to execute _command_ with a new cookie.
+
+*-c*, *--copy*::
+Copy the cookie from an existing PID to another PID, or execute _command_ with that copied cookie.
+
+If no function is specified, it will print the cookie of the current task, or of the task specified with the *-p* option.
+
+== OPTIONS
+*-p*, *--pid* _PID_::
+Operate on an existing PID and do not launch a new task.
+
+*-d*, *--dest* _PID_::
+When using *--copy*, specify a destination PID where you want to copy the cookie to.
+
+*-t*, *--type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
+*{command}*
+
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} -p* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} --new -p* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} --new* \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} --copy -p* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} --copy -p* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} --copy -p* _123_ *-t* pgid *-d* _456_
+
+== PERMISSIONS
+When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+
+If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
+
+== NOTES
+*{command}* requires core scheduling support in the kernel.
+This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers],
+mailto:pauld@redhat.com[Phil Auld]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..03b50cd5e
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,361 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers, Phil Auld
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t pid;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
+		  "                      execute a program with a new cookie."),
+		stdout);
+	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
+		  "                      either another PID, or copy it to a new program"),
+		stdout);
+	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
+		  "   the PID provided via --pid.\n"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
+	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
+		  "                      the destination PID where to copy the cookie to."),
+		stdout);
+	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
+		  "                      when a new core scheduling cookie is created.\n"
+		  "                      Can be one of the following: pid, tgid or pgid.\n"
+		  "                      The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout,
+		USAGE_HELP_OPTIONS(
+			20)); /* char offset to align option descriptions */
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...)                 \
+	do {                              \
+		warnx(FMT);               \
+		errtryhelp(EXIT_FAILURE); \
+	} while (0)
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0))
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		fprintf(stderr,
+			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
+			program_invocation_short_name, before, from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
+			program_invocation_short_name, pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	if (!args->exec_argv_offset)
+		usage();
+
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->pid) {
+		core_sched_pull_cookie(args->pid);
+		core_sched_get_and_print_cookie(args->pid);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv))
+		errexec(argv[0]);
+}
+
+// If PR_SCHED_CORE is not recognized, or not supported on this system,
+// then prctl will set errno to EINVAL. Assuming all other operands of
+// prctl are valid, we can use errno==EINVAL as a check to see whether
+// core scheduling is available on this system.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		if (errno == EINVAL)
+			return false;
+
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strcmp(str, "pid"))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strcmp(str, "tgid"))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strcmp(str, "pgid"))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "new", no_argument, NULL, 'n' },
+		{ "copy", no_argument, NULL, 'c' },
+		{ "pid", required_argument, NULL, 'p' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+	static const ul_excl_t excl[] = {
+		{ 'c', 'n' }, // Cannot do both --new and --copy
+		{ 'd', 'n' }, // Cannot have both --new and --dest
+		{ 0 }
+	};
+
+	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
+	       -1) {
+		err_exclusive_options(c, longopts, excl, excl_st);
+		switch (c) {
+		case 'n':
+			args->cmd = SCHED_CORE_CMD_NEW;
+			break;
+		case 'c':
+			args->cmd = SCHED_CORE_CMD_COPY;
+			break;
+		case 'p':
+			args->pid = strtopid_or_err(
+				optarg, _("Failed to parse PID for -p/--pid"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid)
+		bad_usage(_("--copy: requires a -p/--pid"));
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("Unknown command"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->pid)
+				bad_usage(_(
+					"--new: cannot accept both a -p/--pid and a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest)
+				bad_usage(_(
+					"--copy: cannot accept both a destination PID "
+					"-d/--dest and a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		}
+	}
+
+	if (argc <= optind) {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid)
+			bad_usage(_(
+				"--new: requires either a -p/--pid or a command"));
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
+			bad_usage(_(
+				"--copy: requires either a -d/--dest or a command"));
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { 0 };
+	args.cmd = SCHED_CORE_CMD_GET;
+	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported())
+		errx(EXIT_FAILURE,
+		     _("Does your kernel support CONFIG_SCHED_CORE?"));
+
+	sched_core_cookie cookie;
+	pid_t pid;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		pid = args.pid ? args.pid : getpid();
+		cookie = core_sched_get_cookie(pid);
+		printf(_("cookie of pid %d is 0x%lx\n"), pid, cookie);
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.pid) {
+			core_sched_create_cookie(args.pid, args.type);
+			core_sched_get_and_print_cookie(args.pid);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.dest)
+			core_sched_copy_cookie(args.pid, args.dest, args.type);
+		else
+			core_sched_exec_with_cookie(&args, argv);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
new file mode 100644
index 000000000..d08118c02
--- /dev/null
+++ b/tests/expected/schedutils/coresched-change-cookie-of-parent
@@ -0,0 +1 @@
+cookie of pid OWN_PID is DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
new file mode 100644
index 000000000..3d7f08040
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
@@ -0,0 +1 @@
+cookie of pid OWN_PID is COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid.err b/tests/expected/schedutils/coresched-set-cookie-own-pid.err
new file mode 100644
index 000000000..1816a4fbd
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-own-pid.err
@@ -0,0 +1 @@
+coresched: set cookie of PID OWN_PID to COOKIE
diff --git a/tests/expected/schedutils/coresched-spawn-child-with-new-cookie b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..ed96d89de
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,62 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+ts_check_test_command "tee"
+ts_check_test_command "sed"
+
+# If there is no kernel support, skip the test suite
+CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
+if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
+  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
+fi
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "set-cookie-own-pid"
+CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v -n -p $$ | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 | sed "s/$$/OWN_PID/g")
+CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+if [ -z "$CORESCHED_COOKIE" ]; then
+  ts_failed "empty value for CORESCHED_COOKIE "
+fi
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
+ts_finalize_subtest
+
+ts_init_subtest "get-own-pid-with-cookie"
+$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "spawn-child-with-new-cookie"
+$TS_CMD_CORESCHED -n -- "$TS_CMD_CORESCHED" 2>> "$TS_ERRLOG" \
+  | sed 's/^.*\(0x.*$\)/\1/g' \
+  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
+  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "change-cookie-of-parent"
+$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
+$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" \
+  | sed "s/$$/OWN_PID/g" \
+  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
+  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v5] coresched: Manage core scheduling cookies for tasks
  2024-04-08 21:16                         ` [PATCH v5] " Thijs Raymakers
@ 2024-04-09  6:12                           ` Thomas Weißschuh
  2024-04-09 11:55                             ` [PATCH v6] " Thijs Raymakers
  0 siblings, 1 reply; 42+ messages in thread
From: Thomas Weißschuh @ 2024-04-09  6:12 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: pauld, kzak, util-linux

Hi Thijs,

On 2024-04-08 23:16:54+0200, Thijs Raymakers wrote:
> Op 05-04-2024 om 4:14 p.m. schreef Phil Auld:
> > On Fri, Apr 05, 2024 at 08:26:08AM +0200 Thomas Weißschuh wrote:
> >> On 2024-04-05 00:03:57+0200, Thijs Raymakers wrote:
> >>> Therefore, I've changed to code to explicitly check for kernel support
> >>> beforehand. This change also removes the need for the `check_prctl`
> >>> macro.
> >>
> >> Thanks, would it make sense to expose this check via the CLI?
> >> That could then used by the tests to skip themselves if the system does
> >> not provide core scheduling on old kernels or qemu-user.
> >>
> > 
> > I don't think so. It's checking by doing an operation so just do a "get"
> > if you really want to check first. An explicit check would just be that
> > anyway. 
> 
> I agree with Phil, I don't think it would be necessary. I've added the check
> to the tests that allows them to be skipped if core scheduling is not
> available (such as on older kernels like you mentioned). This check just 
> runs `coresched`, which will do the check implicitly.

Sounds good to me.

[snip]

> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> new file mode 100644
> index 000000000..03b50cd5e
> --- /dev/null
> +++ b/schedutils/coresched.c
> @@ -0,0 +1,361 @@
> +/**
> + * SPDX-License-Identifier: EUPL-1.2
> + *
> + * coresched.c - manage core scheduling cookies for tasks
> + *
> + * Copyright (C) 2024 Thijs Raymakers, Phil Auld
> + * Licensed under the EUPL v1.2
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <sys/wait.h>

Seems unused.

> +#include <unistd.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "optutils.h"
> +#include "strutils.h"
> +
> +// These definitions might not be defined in the header files, even if the
> +// prctl interface in the kernel accepts them as valid.
> +#ifndef PR_SCHED_CORE
> +#define PR_SCHED_CORE 62
> +#endif
> +#ifndef PR_SCHED_CORE_GET
> +#define PR_SCHED_CORE_GET 0
> +#endif
> +#ifndef PR_SCHED_CORE_CREATE
> +#define PR_SCHED_CORE_CREATE 1
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_TO
> +#define PR_SCHED_CORE_SHARE_TO 2
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_FROM
> +#define PR_SCHED_CORE_SHARE_FROM 3
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int sched_core_scope;
> +typedef unsigned long sched_core_cookie;
> +typedef enum {
> +	SCHED_CORE_CMD_GET,
> +	SCHED_CORE_CMD_NEW,
> +	SCHED_CORE_CMD_COPY,
> +} sched_core_cmd;
> +
> +struct args {
> +	pid_t pid;
> +	pid_t dest;
> +	sched_core_scope type;
> +	sched_core_cmd cmd;
> +	int exec_argv_offset;
> +};
> +
> +static bool sched_core_verbose = false;
> +
> +static void __attribute__((__noreturn__)) usage(void)
> +{
> +	fputs(USAGE_HEADER, stdout);
> +	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> +
> +	fputs(USAGE_FUNCTIONS, stdout);
> +	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
> +		  "                      execute a program with a new cookie."),
> +		stdout);
> +	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
> +		  "                      either another PID, or copy it to a new program"),
> +		stdout);
> +	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
> +		  "   the PID provided via --pid.\n"),
> +		stdout);
> +
> +	fputs(USAGE_OPTIONS, stdout);
> +	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
> +	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
> +		  "                      the destination PID where to copy the cookie to."),
> +		stdout);
> +	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
> +		  "                      when a new core scheduling cookie is created.\n"
> +		  "                      Can be one of the following: pid, tgid or pgid.\n"
> +		  "                      The default is tgid."),
> +		stdout);
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_(" -v, --verbose      verbose"), stdout);
> +	fprintf(stdout,
> +		USAGE_HELP_OPTIONS(
> +			20)); /* char offset to align option descriptions */

These are some very weird linebreaks.
In my opinion you can drop the comment.

> +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> +	exit(EXIT_SUCCESS);
> +}
> +
> +#define bad_usage(FMT...)                 \
> +	do {                              \
> +		warnx(FMT);               \
> +		errtryhelp(EXIT_FAILURE); \
> +	} while (0)
> +
> +static sched_core_cookie core_sched_get_cookie(pid_t pid)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
> +		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
> +	return cookie;
> +}
> +
> +static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
> +		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
> +}
> +
> +static void core_sched_pull_cookie(pid_t from)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +		  PR_SCHED_CORE_SCOPE_THREAD, 0))
> +		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
> +}
> +
> +static void core_sched_push_cookie(pid_t to, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
> +		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
> +}
> +
> +static void core_sched_copy_cookie(pid_t from, pid_t to,
> +				   sched_core_scope to_type)
> +{
> +	core_sched_pull_cookie(from);
> +	core_sched_push_cookie(to, to_type);
> +
> +	if (sched_core_verbose) {
> +		sched_core_cookie before = core_sched_get_cookie(from);
> +		fprintf(stderr,
> +			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
> +			program_invocation_short_name, before, from, to);

In the normal verbose prints program_invocation_short_name is still
used. Is this intentional?

> +	}
> +}
> +
> +static void core_sched_get_and_print_cookie(pid_t pid)
> +{
> +	if (sched_core_verbose) {
> +		sched_core_cookie after = core_sched_get_cookie(pid);
> +		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
> +			program_invocation_short_name, pid, after);
> +	}
> +}
> +
> +static void core_sched_exec_with_cookie(struct args *args, char **argv)
> +{
> +	if (!args->exec_argv_offset)
> +		usage();
> +
> +	// Move the argument list to the first argument of the program
> +	argv = &argv[args->exec_argv_offset];
> +
> +	// If a source PID is provided, try to copy the cookie from
> +	// that PID. Otherwise, create a brand new cookie with the
> +	// provided type.
> +	if (args->pid) {
> +		core_sched_pull_cookie(args->pid);
> +		core_sched_get_and_print_cookie(args->pid);
> +	} else {
> +		pid_t pid = getpid();
> +		core_sched_create_cookie(pid, args->type);
> +		core_sched_get_and_print_cookie(pid);
> +	}
> +
> +	if (execvp(argv[0], argv))
> +		errexec(argv[0]);
> +}
> +
> +// If PR_SCHED_CORE is not recognized, or not supported on this system,
> +// then prctl will set errno to EINVAL. Assuming all other operands of
> +// prctl are valid, we can use errno==EINVAL as a check to see whether
> +// core scheduling is available on this system.
> +static bool is_core_sched_supported(void)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
> +		if (errno == EINVAL)
> +			return false;
> +
> +	return true;
> +}
> +
> +static sched_core_scope parse_core_sched_type(char *str)
> +{
> +	if (!strcmp(str, "pid"))
> +		return PR_SCHED_CORE_SCOPE_THREAD;
> +	else if (!strcmp(str, "tgid"))
> +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +	else if (!strcmp(str, "pgid"))
> +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
> +
> +	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
> +		  str);
> +}
> +
> +static void parse_arguments(int argc, char **argv, struct args *args)
> +{
> +	int c;
> +
> +	static const struct option longopts[] = {
> +		{ "new", no_argument, NULL, 'n' },
> +		{ "copy", no_argument, NULL, 'c' },
> +		{ "pid", required_argument, NULL, 'p' },
> +		{ "dest", required_argument, NULL, 'd' },
> +		{ "type", required_argument, NULL, 't' },
> +		{ "verbose", no_argument, NULL, 'v' },
> +		{ "version", no_argument, NULL, 'V' },
> +		{ "help", no_argument, NULL, 'h' },
> +		{ NULL, 0, NULL, 0 }
> +	};
> +	static const ul_excl_t excl[] = {
> +		{ 'c', 'n' }, // Cannot do both --new and --copy
> +		{ 'd', 'n' }, // Cannot have both --new and --dest
> +		{ 0 }
> +	};
> +
> +	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
> +
> +	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
> +	       -1) {
> +		err_exclusive_options(c, longopts, excl, excl_st);
> +		switch (c) {
> +		case 'n':
> +			args->cmd = SCHED_CORE_CMD_NEW;
> +			break;
> +		case 'c':
> +			args->cmd = SCHED_CORE_CMD_COPY;
> +			break;
> +		case 'p':
> +			args->pid = strtopid_or_err(
> +				optarg, _("Failed to parse PID for -p/--pid"));
> +			break;
> +		case 'd':
> +			args->dest = strtopid_or_err(
> +				optarg, _("Failed to parse PID for -d/--dest"));
> +			break;
> +		case 't':
> +			args->type = parse_core_sched_type(optarg);
> +			break;
> +		case 'v':
> +			sched_core_verbose = true;
> +			break;
> +		case 'V':
> +			print_version(EXIT_SUCCESS);
> +		case 'h':
> +			usage();
> +		default:
> +			errtryhelp(EXIT_FAILURE);
> +		}
> +	}
> +
> +	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid)
> +		bad_usage(_("--copy: requires a -p/--pid"));
> +
> +	// More arguments have been passed, which means that the user wants to run
> +	// another program with a core scheduling cookie.
> +	if (argc > optind) {
> +		switch (args->cmd) {
> +		case SCHED_CORE_CMD_GET:
> +			bad_usage(_("Unknown command"));
> +			break;
> +		case SCHED_CORE_CMD_NEW:
> +			if (args->pid)
> +				bad_usage(_(
> +					"--new: cannot accept both a -p/--pid and a command"));
> +			else
> +				args->exec_argv_offset = optind;
> +			break;
> +		case SCHED_CORE_CMD_COPY:
> +			if (args->dest)
> +				bad_usage(_(
> +					"--copy: cannot accept both a destination PID "
> +					"-d/--dest and a command"));
> +			else
> +				args->exec_argv_offset = optind;
> +			break;
> +		}
> +	}
> +
> +	if (argc <= optind) {
> +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid)
> +			bad_usage(_(
> +				"--new: requires either a -p/--pid or a command"));
> +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
> +			bad_usage(_(
> +				"--copy: requires either a -d/--dest or a command"));
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct args args = { 0 };
> +	args.cmd = SCHED_CORE_CMD_GET;
> +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +
> +	setlocale(LC_ALL, "");
> +	bindtextdomain(PACKAGE, LOCALEDIR);
> +	textdomain(PACKAGE);
> +	close_stdout_atexit();
> +
> +	parse_arguments(argc, argv, &args);
> +
> +	if (!is_core_sched_supported())
> +		errx(EXIT_FAILURE,
> +		     _("Does your kernel support CONFIG_SCHED_CORE?"));

Could this message be extended to be clearer to the user?

"No support for core scheduling found. Does your kernel support
CONFIG_SCHED_CORE?"

> +
> +	sched_core_cookie cookie;
> +	pid_t pid;
> +
> +	switch (args.cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		pid = args.pid ? args.pid : getpid();
> +		cookie = core_sched_get_cookie(pid);
> +		printf(_("cookie of pid %d is 0x%lx\n"), pid, cookie);
> +		break;
> +	case SCHED_CORE_CMD_NEW:
> +		if (args.pid) {
> +			core_sched_create_cookie(args.pid, args.type);
> +			core_sched_get_and_print_cookie(args.pid);
> +		} else {
> +			core_sched_exec_with_cookie(&args, argv);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		if (args.dest)
> +			core_sched_copy_cookie(args.pid, args.dest, args.type);
> +		else
> +			core_sched_exec_with_cookie(&args, argv);
> +		break;
> +	default:
> +		usage();
> +	}
> +}

[snip]

Some more (non-functional) nitpicks.

With or without them fixed:
Reviewed-by: Thomas Weißschuh <thomas@t-8ch.de>

Thanks!

^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v6] coresched: Manage core scheduling cookies for tasks
  2024-04-09  6:12                           ` Thomas Weißschuh
@ 2024-04-09 11:55                             ` Thijs Raymakers
  2024-04-09 13:44                               ` Karel Zak
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-09 11:55 UTC (permalink / raw)
  To: thomas, pauld; +Cc: kzak, util-linux, Thijs Raymakers

Co-authored-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
Reviewed-by: Thomas Weißschuh <thomas@t-8ch.de>
---

Hi Thomas,

Op 09-04-2024 om 8:12 a.m. schreef Thomas Weißschuh:
> In the normal verbose prints program_invocation_short_name is still
> used. Is this intentional?

Yes, this is intentional. I should have mentioned this in the previous
email. If coresched is used to spawn a new program, then it should be
clear what part of stderr is printed by coresched and what part is
printed by the new process. I thought that this is best conveyed by
prefixing the message with program_invocation_short_name

With the prefix, this looks something like
  $ coresched -v -n ls
  coresched: set cookie of PID 17347 to 0x350cc35
  ABOUT-NLS	      disk-utils     meson_options.txt
  aclocal.m4	      Documentation  misc-utils
  ...

Without the prefix
  $ coresched -v -n ls
  set cookie of PID 17347 to 0x350cc35
  ABOUT-NLS	      disk-utils     meson_options.txt
  aclocal.m4	      Documentation  misc-utils
  ...

Yes, it is a bit redundant to prefix the message with
program_invocation_short_name, but since it only shows up in verbose
mode anyway I think that the extra bit of clarity is worth it.

Op 09-04-2024 om 8:12 a.m. schreef Thomas Weißschuh:
> On 2024-04-08 23:16:54+0200, Thijs Raymakers wrote:
>> +	fprintf(stdout,
>> +		USAGE_HELP_OPTIONS(
>> +			20)); /* char offset to align option descriptions */
> These are some very weird linebreaks.
> In my opinion you can drop the comment.

Ah this is a leftover from the boilerplate.c file. I've removed it.

Op 09-04-2024 om 8:12 a.m. schreef Thomas Weißschuh:
> On 2024-04-08 23:16:54+0200, Thijs Raymakers wrote:
>> +#include <sys/wait.h>
> Seems unused.

Indeed, this is no longer necessary. I've removed it.

Op 09-04-2024 om 8:12 a.m. schreef Thomas Weißschuh:
> On 2024-04-08 23:16:54+0200, Thijs Raymakers wrote:
>> +		     _("Does your kernel support CONFIG_SCHED_CORE?"));
> Could this message be extended to be clearer to the user?

Yes, I've changed it to your suggestion.

Thank you for your review, I've added your Reviewed-by tag to the patch!

Thijs

Interdiff against v5:
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index 03b50cd5e..b6be1717d 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -11,7 +11,6 @@
   #include <stdbool.h>
   #include <stdio.h>
   #include <sys/prctl.h>
  -#include <sys/wait.h>
   #include <unistd.h>
   
   #include "c.h"
  @@ -105,9 +104,7 @@ static void __attribute__((__noreturn__)) usage(void)
   		stdout);
   	fputs(USAGE_SEPARATOR, stdout);
   	fputsln(_(" -v, --verbose      verbose"), stdout);
  -	fprintf(stdout,
  -		USAGE_HELP_OPTIONS(
  -			20)); /* char offset to align option descriptions */
  +	fprintf(stdout, USAGE_HELP_OPTIONS(20));
   	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
   	exit(EXIT_SUCCESS);
   }
  @@ -330,7 +327,8 @@ int main(int argc, char **argv)
   
   	if (!is_core_sched_supported())
   		errx(EXIT_FAILURE,
  -		     _("Does your kernel support CONFIG_SCHED_CORE?"));
  +		     _("No support for core scheduling found. Does your kernel"
  +		       "support CONFIG_SCHED_CORE?"));
   
   	sched_core_cookie cookie;
   	pid_t pid;

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 124 ++++++
 schedutils/coresched.c                        | 359 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-change-cookie-of-parent         |   1 +
 .../coresched-get-own-pid-with-cookie         |   1 +
 .../coresched-set-cookie-own-pid.err          |   1 +
 .../coresched-spawn-child-with-new-cookie     |   1 +
 tests/ts/schedutils/coresched                 |  62 +++
 14 files changed, 583 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-change-cookie-of-parent
 create mode 100644 tests/expected/schedutils/coresched-get-own-pid-with-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-own-pid.err
 create mode 100644 tests/expected/schedutils/coresched-spawn-child-with-new-cookie
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 9600ce49f..a3a648753 100644
--- a/meson.build
+++ b/meson.build
@@ -3111,13 +3111,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..c83fcdf91
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,124 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [options] *-p* _pid_
+
+*{command}* [options] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*-n*, *--new*::
+Assign a new cookie to an existing PID or to execute _command_ with a new cookie.
+
+*-c*, *--copy*::
+Copy the cookie from an existing PID to another PID, or execute _command_ with that copied cookie.
+
+If no function is specified, it will print the cookie of the current task, or of the task specified with the *-p* option.
+
+== OPTIONS
+*-p*, *--pid* _PID_::
+Operate on an existing PID and do not launch a new task.
+
+*-d*, *--dest* _PID_::
+When using *--copy*, specify a destination PID where you want to copy the cookie to.
+
+*-t*, *--type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
+*{command}*
+
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} -p* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} --new -p* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} --new* \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} --copy -p* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} --copy -p* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} --copy -p* _123_ *-t* pgid *-d* _456_
+
+== PERMISSIONS
+When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+
+If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
+
+== NOTES
+*{command}* requires core scheduling support in the kernel.
+This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers],
+mailto:pauld@redhat.com[Phil Auld]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..b6be1717d
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,359 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers, Phil Auld
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t pid;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
+		  "                      execute a program with a new cookie."),
+		stdout);
+	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
+		  "                      either another PID, or copy it to a new program"),
+		stdout);
+	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
+		  "   the PID provided via --pid.\n"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
+	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
+		  "                      the destination PID where to copy the cookie to."),
+		stdout);
+	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
+		  "                      when a new core scheduling cookie is created.\n"
+		  "                      Can be one of the following: pid, tgid or pgid.\n"
+		  "                      The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout, USAGE_HELP_OPTIONS(20));
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...)                 \
+	do {                              \
+		warnx(FMT);               \
+		errtryhelp(EXIT_FAILURE); \
+	} while (0)
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0))
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		fprintf(stderr,
+			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
+			program_invocation_short_name, before, from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
+			program_invocation_short_name, pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	if (!args->exec_argv_offset)
+		usage();
+
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->pid) {
+		core_sched_pull_cookie(args->pid);
+		core_sched_get_and_print_cookie(args->pid);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv))
+		errexec(argv[0]);
+}
+
+// If PR_SCHED_CORE is not recognized, or not supported on this system,
+// then prctl will set errno to EINVAL. Assuming all other operands of
+// prctl are valid, we can use errno==EINVAL as a check to see whether
+// core scheduling is available on this system.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		if (errno == EINVAL)
+			return false;
+
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strcmp(str, "pid"))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strcmp(str, "tgid"))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strcmp(str, "pgid"))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "new", no_argument, NULL, 'n' },
+		{ "copy", no_argument, NULL, 'c' },
+		{ "pid", required_argument, NULL, 'p' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+	static const ul_excl_t excl[] = {
+		{ 'c', 'n' }, // Cannot do both --new and --copy
+		{ 'd', 'n' }, // Cannot have both --new and --dest
+		{ 0 }
+	};
+
+	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
+	       -1) {
+		err_exclusive_options(c, longopts, excl, excl_st);
+		switch (c) {
+		case 'n':
+			args->cmd = SCHED_CORE_CMD_NEW;
+			break;
+		case 'c':
+			args->cmd = SCHED_CORE_CMD_COPY;
+			break;
+		case 'p':
+			args->pid = strtopid_or_err(
+				optarg, _("Failed to parse PID for -p/--pid"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid)
+		bad_usage(_("--copy: requires a -p/--pid"));
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("Unknown command"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->pid)
+				bad_usage(_(
+					"--new: cannot accept both a -p/--pid and a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest)
+				bad_usage(_(
+					"--copy: cannot accept both a destination PID "
+					"-d/--dest and a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		}
+	}
+
+	if (argc <= optind) {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid)
+			bad_usage(_(
+				"--new: requires either a -p/--pid or a command"));
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
+			bad_usage(_(
+				"--copy: requires either a -d/--dest or a command"));
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { 0 };
+	args.cmd = SCHED_CORE_CMD_GET;
+	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported())
+		errx(EXIT_FAILURE,
+		     _("No support for core scheduling found. Does your kernel"
+		       "support CONFIG_SCHED_CORE?"));
+
+	sched_core_cookie cookie;
+	pid_t pid;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		pid = args.pid ? args.pid : getpid();
+		cookie = core_sched_get_cookie(pid);
+		printf(_("cookie of pid %d is 0x%lx\n"), pid, cookie);
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.pid) {
+			core_sched_create_cookie(args.pid, args.type);
+			core_sched_get_and_print_cookie(args.pid);
+		} else {
+			core_sched_exec_with_cookie(&args, argv);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.dest)
+			core_sched_copy_cookie(args.pid, args.dest, args.type);
+		else
+			core_sched_exec_with_cookie(&args, argv);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
new file mode 100644
index 000000000..d08118c02
--- /dev/null
+++ b/tests/expected/schedutils/coresched-change-cookie-of-parent
@@ -0,0 +1 @@
+cookie of pid OWN_PID is DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
new file mode 100644
index 000000000..3d7f08040
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
@@ -0,0 +1 @@
+cookie of pid OWN_PID is COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid.err b/tests/expected/schedutils/coresched-set-cookie-own-pid.err
new file mode 100644
index 000000000..1816a4fbd
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-own-pid.err
@@ -0,0 +1 @@
+coresched: set cookie of PID OWN_PID to COOKIE
diff --git a/tests/expected/schedutils/coresched-spawn-child-with-new-cookie b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-spawn-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..ed96d89de
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,62 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+ts_check_test_command "tee"
+ts_check_test_command "sed"
+
+# If there is no kernel support, skip the test suite
+CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
+if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
+  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
+fi
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "set-cookie-own-pid"
+CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v -n -p $$ | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 | sed "s/$$/OWN_PID/g")
+CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+if [ -z "$CORESCHED_COOKIE" ]; then
+  ts_failed "empty value for CORESCHED_COOKIE "
+fi
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
+ts_finalize_subtest
+
+ts_init_subtest "get-own-pid-with-cookie"
+$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "spawn-child-with-new-cookie"
+$TS_CMD_CORESCHED -n -- "$TS_CMD_CORESCHED" 2>> "$TS_ERRLOG" \
+  | sed 's/^.*\(0x.*$\)/\1/g' \
+  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
+  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "change-cookie-of-parent"
+$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
+$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" \
+  | sed "s/$$/OWN_PID/g" \
+  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
+  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v6] coresched: Manage core scheduling cookies for tasks
  2024-04-09 11:55                             ` [PATCH v6] " Thijs Raymakers
@ 2024-04-09 13:44                               ` Karel Zak
  2024-04-09 14:12                                 ` Phil Auld
  2024-04-10 21:11                                 ` [PATCH v7] " Thijs Raymakers
  0 siblings, 2 replies; 42+ messages in thread
From: Karel Zak @ 2024-04-09 13:44 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: thomas, pauld, util-linux


 Hi,

sorry for delay in my review ...

On Tue, Apr 09, 2024 at 01:55:32PM +0200, Thijs Raymakers wrote:
> +== OPTIONS
> +*-p*, *--pid* _PID_::
> +Operate on an existing PID and do not launch a new task.

"Nnot launch a new task" does not seem to be true, as you later
provide an example:

  Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
  *{command} --copy -p* _123_ \-- _command_ [_argument_...]

> +*-d*, *--dest* _PID_::
> +When using *--copy*, specify a destination PID where you want to copy the cookie to.

I find it a little confusing that the --pid is sometimes used as a
source and in other cases as a destination.

     coresched --new --pid 123              # pid is destination
     coresched --copy --pid 123 --dest 456  # pid is source

It seems --copy always requires a source PID (according to the man
page), why not require a PID as argument for --copy:

    coresched --copy 123 --pid 456

in this way --pid will be always destination (for 'copy' and 'new'
functions) and you will not need extra --dest option at all.

If you want to keep the basic functions (e.g. --copy) without
arguments, it would be better to have --source, --dest, and
--dest-type instead of using the ambiguous --pid.

I can also imagine the basic "functions" without "--".

    coresched [get] [--dest] 123
    coresched copy [--source] 123 [--dest] 456
    coresched new [--dest] 456

In my opinion, we do not have to strictly adhere to old taskset or
similar commands.

It should be noted that command line arguments are crucial, as they
are difficult to modify after release.

> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <unistd.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "optutils.h"
> +#include "strutils.h"
> +
> +// These definitions might not be defined in the header files, even if the
> +// prctl interface in the kernel accepts them as valid.
> +#ifndef PR_SCHED_CORE
> +#define PR_SCHED_CORE 62
> +#endif

pedantic note, use extra space within ifdef

#ifndef PR_SCHED_CORE
# define PR_SCHED_CORE 62
#endif

please :-)

> +static void core_sched_push_cookie(pid_t to, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
> +		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
> +}
> +
> +static void core_sched_copy_cookie(pid_t from, pid_t to,
> +				   sched_core_scope to_type)
> +{
> +	core_sched_pull_cookie(from);
> +	core_sched_push_cookie(to, to_type);
> +
> +	if (sched_core_verbose) {
> +		sched_core_cookie before = core_sched_get_cookie(from);
> +		fprintf(stderr,
> +			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
> +			program_invocation_short_name, before, from, to);

 Don't use fprintf(), use:

        warnx(_(copied cookie 0x%lx from PID %d to PID %d\n"), before, from, to);

> +static void core_sched_get_and_print_cookie(pid_t pid)
> +{
> +	if (sched_core_verbose) {
> +		sched_core_cookie after = core_sched_get_cookie(pid);
> +		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
> +			program_invocation_short_name, pid, after);
> +	}

 warnx()

> +int main(int argc, char **argv)
> +{
> +	struct args args = { 0 };
> +	args.cmd = SCHED_CORE_CMD_GET;
> +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;

 struct args args = {
        .cmd = SCHED_CORE_CMD_GET,
        .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP
 };

 (the rest is set to zero by compiler).

> +ts_check_test_command "$TS_CMD_CORESCHED"
> +ts_check_test_command "tee"
> +ts_check_test_command "sed"

ts_check_test_command is for in-tree stuff, use

    ts_check_prog "tee"
    ts_check_prog "sed"

for things in $PATH.

> +ts_init_subtest "get-own-pid-with-cookie"
> +$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"

 Let's save some forks, execute sed(1) only once:

    | sed -e "s/$$/OWN_PID/g" \
          -e "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"

> +ts_finalize_subtest
> +
> +ts_init_subtest "spawn-child-with-new-cookie"
> +$TS_CMD_CORESCHED -n -- "$TS_CMD_CORESCHED" 2>> "$TS_ERRLOG" \
> +  | sed 's/^.*\(0x.*$\)/\1/g' \
> +  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
> +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest

  | sed -e ... -e ... -e 

> +ts_init_subtest "change-cookie-of-parent"
> +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
> +$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" \
> +  | sed "s/$$/OWN_PID/g" \
> +  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
> +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest

  | sed -e ... -e ... -e 


Looks good, thanks for all the work!

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v6] coresched: Manage core scheduling cookies for tasks
  2024-04-09 13:44                               ` Karel Zak
@ 2024-04-09 14:12                                 ` Phil Auld
  2024-04-10 21:11                                 ` [PATCH v7] " Thijs Raymakers
  1 sibling, 0 replies; 42+ messages in thread
From: Phil Auld @ 2024-04-09 14:12 UTC (permalink / raw)
  To: Karel Zak; +Cc: Thijs Raymakers, thomas, util-linux

On Tue, Apr 09, 2024 at 03:44:30PM +0200 Karel Zak wrote:
> 
>  Hi,
> 
> sorry for delay in my review ...
> 
> On Tue, Apr 09, 2024 at 01:55:32PM +0200, Thijs Raymakers wrote:
> > +== OPTIONS
> > +*-p*, *--pid* _PID_::
> > +Operate on an existing PID and do not launch a new task.
> 
> "Nnot launch a new task" does not seem to be true, as you later
> provide an example:
> 
>   Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
>   *{command} --copy -p* _123_ \-- _command_ [_argument_...]
> 
> > +*-d*, *--dest* _PID_::
> > +When using *--copy*, specify a destination PID where you want to copy the cookie to.
> 
> I find it a little confusing that the --pid is sometimes used as a
> source and in other cases as a destination.
> 
>      coresched --new --pid 123              # pid is destination
>      coresched --copy --pid 123 --dest 456  # pid is source
> 
> It seems --copy always requires a source PID (according to the man
> page), why not require a PID as argument for --copy:
> 
>     coresched --copy 123 --pid 456
> 
> in this way --pid will be always destination (for 'copy' and 'new'
> functions) and you will not need extra --dest option at all.
>

Having copy take the dest pid itself could work for me.

As an aside, I just noticed we lost the ability to push the current cookie
to the new task or pid. I suppose you can just do "--copy $$" and get that
though so probably that's good enough. 


> If you want to keep the basic functions (e.g. --copy) without
> arguments, it would be better to have --source, --dest, and
> --dest-type instead of using the ambiguous --pid.

At one point we had three arguments that took a pid.

> 
> I can also imagine the basic "functions" without "--".
> 
>     coresched [get] [--dest] 123

This is is really --source since we are getting the cookie from it.


>     coresched copy [--source] 123 [--dest] 456
>     coresched new [--dest] 456
>

And using --source for "get" you'd have to change it to --dest
after you check for a cookie before you do new.  

> In my opinion, we do not have to strictly adhere to old taskset or
> similar commands.

We went around about that.  My take is that the users of coresched
will be the same people who use taskset. The idea was to keep it
familiar.

> 
> It should be noted that command line arguments are crucial, as they
> are difficult to modify after release.

Indeed. That's why we went around and around...


Cheers,
Phil

> 
> > +#include <getopt.h>
> > +#include <stdbool.h>
> > +#include <stdio.h>
> > +#include <sys/prctl.h>
> > +#include <unistd.h>
> > +
> > +#include "c.h"
> > +#include "closestream.h"
> > +#include "nls.h"
> > +#include "optutils.h"
> > +#include "strutils.h"
> > +
> > +// These definitions might not be defined in the header files, even if the
> > +// prctl interface in the kernel accepts them as valid.
> > +#ifndef PR_SCHED_CORE
> > +#define PR_SCHED_CORE 62
> > +#endif
> 
> pedantic note, use extra space within ifdef
> 
> #ifndef PR_SCHED_CORE
> # define PR_SCHED_CORE 62
> #endif
> 
> please :-)
> 
> > +static void core_sched_push_cookie(pid_t to, sched_core_scope type)
> > +{
> > +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
> > +		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
> > +}
> > +
> > +static void core_sched_copy_cookie(pid_t from, pid_t to,
> > +				   sched_core_scope to_type)
> > +{
> > +	core_sched_pull_cookie(from);
> > +	core_sched_push_cookie(to, to_type);
> > +
> > +	if (sched_core_verbose) {
> > +		sched_core_cookie before = core_sched_get_cookie(from);
> > +		fprintf(stderr,
> > +			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
> > +			program_invocation_short_name, before, from, to);
> 
>  Don't use fprintf(), use:
> 
>         warnx(_(copied cookie 0x%lx from PID %d to PID %d\n"), before, from, to);
> 
> > +static void core_sched_get_and_print_cookie(pid_t pid)
> > +{
> > +	if (sched_core_verbose) {
> > +		sched_core_cookie after = core_sched_get_cookie(pid);
> > +		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
> > +			program_invocation_short_name, pid, after);
> > +	}
> 
>  warnx()
> 
> > +int main(int argc, char **argv)
> > +{
> > +	struct args args = { 0 };
> > +	args.cmd = SCHED_CORE_CMD_GET;
> > +	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> 
>  struct args args = {
>         .cmd = SCHED_CORE_CMD_GET,
>         .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP
>  };
> 
>  (the rest is set to zero by compiler).
> 
> > +ts_check_test_command "$TS_CMD_CORESCHED"
> > +ts_check_test_command "tee"
> > +ts_check_test_command "sed"
> 
> ts_check_test_command is for in-tree stuff, use
> 
>     ts_check_prog "tee"
>     ts_check_prog "sed"
> 
> for things in $PATH.
> 
> > +ts_init_subtest "get-own-pid-with-cookie"
> > +$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
> 
>  Let's save some forks, execute sed(1) only once:
> 
>     | sed -e "s/$$/OWN_PID/g" \
>           -e "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
> 
> > +ts_finalize_subtest
> > +
> > +ts_init_subtest "spawn-child-with-new-cookie"
> > +$TS_CMD_CORESCHED -n -- "$TS_CMD_CORESCHED" 2>> "$TS_ERRLOG" \
> > +  | sed 's/^.*\(0x.*$\)/\1/g' \
> > +  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
> > +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> > +ts_finalize_subtest
> 
>   | sed -e ... -e ... -e 
> 
> > +ts_init_subtest "change-cookie-of-parent"
> > +$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
> > +$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" \
> > +  | sed "s/$$/OWN_PID/g" \
> > +  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
> > +  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> > +ts_finalize_subtest
> 
>   | sed -e ... -e ... -e 
> 
> 
> Looks good, thanks for all the work!
> 
>     Karel
> 
> -- 
>  Karel Zak  <kzak@redhat.com>
>  http://karelzak.blogspot.com
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v7] coresched: Manage core scheduling cookies for tasks
  2024-04-09 13:44                               ` Karel Zak
  2024-04-09 14:12                                 ` Phil Auld
@ 2024-04-10 21:11                                 ` Thijs Raymakers
  2024-04-10 22:15                                   ` Phil Auld
  1 sibling, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-10 21:11 UTC (permalink / raw)
  To: kzak; +Cc: thomas, pauld, util-linux, Thijs Raymakers

Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
---

Hi Karel,

Thanks for the review!

Op 09-04-2024 om 3:44 p.m. schreef Karel Zak:
> I find it a little confusing that the --pid is sometimes used as a
> source and in other cases as a destination.
> 
>      coresched --new --pid 123              # pid is destination
>      coresched --copy --pid 123 --dest 456  # pid is source
> 
> It seems --copy always requires a source PID (according to the man
> page), why not require a PID as argument for --copy:
> 
>     coresched --copy 123 --pid 456
> 
> in this way --pid will be always destination (for 'copy' and 'new'
> functions) and you will not need extra --dest option at all.
> 
> If you want to keep the basic functions (e.g. --copy) without
> arguments, it would be better to have --source, --dest, and
> --dest-type instead of using the ambiguous --pid.
> 
> I can also imagine the basic "functions" without "--".
> 
>     coresched [get] [--dest] 123
>     coresched copy [--source] 123 [--dest] 456
>     coresched new [--dest] 456
> 
> In my opinion, we do not have to strictly adhere to old taskset or
> similar commands.

The command line arguments are currently somewhat similar in meaning
to the arguments of taskset, which Phil mentioned he preferred.
taskset (and others) uses --pid to refer to the PID. This works
fine if you operate on just a single PID.

I agree that this version of coresched can be indeed confusing to
use if --pid is overloaded to mean both "source" and
"destination", depending on the context. coresched needs at most two
PIDs and can thus not use --pid for everything.

How those two options are called/used seems to be a tradeoff between
the familiarity by taskset users vs consistency within coreset.
Either you use --pid which is familiar to users because other programs
define PIDs like that, but the semantics will be inconsistent and will
change depending what function of coresched you use.
Or you use --source and --dest, which is a bit more inconvenient to
use because you cannot use the same option to refer to a PID across the
different coresed functions, but you will have consistent behavior of
each option.

Since you mention that we don't strictly have to adhere to taskset, I
do have a slight preference for --source/--dest instead of --pid/--dest.
With the help of the usage and/or man page, this difference shouldn't be
too cumbersome to users that are familiar with other tools. The benefit
of internal consistency outweights the benefit of familiarity in my
opinion.

Version 7 of the patch changes the arguments from --pid/--dest/--type
(-t/-d/-t) to --source/--dest/--dest-type (-s/-d/-t). It also has the
functions without the "--", as well as the other suggestions that you
made. I've also added two more tests.

The commands now look something like this

    coresched [get] [--source <PID>]
    coresched new [-t <TYPE>] --dest <PID>
    coresched new [-t <TYPE>] -- PROGRAM [ARGS...]
    coresched copy [--source <PID>] [-t <TYPE>] --dest <PID>
    coresched copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]

> It should be noted that command line arguments are crucial, as they
> are difficult to modify after release.

It is good that you mentioned that the arguments felt confusing to use,
because that is something that became a bit invisible to me after using
the util for some time. It is important to get the arguments right, and
making sure that they are sensible and self-consistent. These new
command line arguments work for me as well. Phil previously expressed a
preference for the --pid style so would like to know what his opinion is.

> pedantic note, use extra space within ifdef
> 
> #ifndef PR_SCHED_CORE
> # define PR_SCHED_CORE 62
> #endif

I've changed to

#ifndef PR_SCHED_CORE
	#define PR_SCHED_CORE 62
#endif

which looks a bit nicer in my opinion than what clang-format does with
the AfterHash option. (I've used IndentPPDirectives: BeforeHash instead
of IndentPPDirectives: None, which is the default in the kernel
clang-format file)


CC: Phil Auld and Thomas Weißschuh
I've removed your Signed-off-by/Reviewed-by tags since this version
differs significantly from the version that you've previously reviewed.
I did't want to just assume that you approve of this version as well.
With your permission, I can add the tags back again.


Thijs

Interdiff against v6:
  diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
  index c83fcdf91..00f2603e0 100644
  --- a/schedutils/coresched.1.adoc
  +++ b/schedutils/coresched.1.adoc
  @@ -17,9 +17,15 @@ coresched - manage core scheduling cookies for tasks
   
   == SYNOPSIS
   
  -*{command}* [options] *-p* _pid_
  +*{command}* [*get*] [*-s* _pid_]
   
  -*{command}* [options] \-- _command_ [_argument_...]
  +*{command}* *new* [*-t* _type_] *-d* _pid_
  +
  +*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
  +
  +*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
  +
  +*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
   
   == DESCRIPTION
   The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
  @@ -31,22 +37,28 @@ Only tasks have the same cookie are allowed to be scheduled on the same physical
   It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
   
   == FUNCTIONS
  -*-n*, *--new*::
  -Assign a new cookie to an existing PID or to execute _command_ with a new cookie.
  +*get*::
  +Retrieve the core scheduling cookie of the PID specified in *-s*.
  +If *-s* is omitted, it will get the cookie of the current *{command}* process.
   
  -*-c*, *--copy*::
  -Copy the cookie from an existing PID to another PID, or execute _command_ with that copied cookie.
  +*new*::
  +Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
   
  -If no function is specified, it will print the cookie of the current task, or of the task specified with the *-p* option.
  +*copy*::
  +Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
  +If *-s* is omitted, it will get the cookie of the current *{command}* process.
  +
  +If no function is specified, it will run the *get* function.
   
   == OPTIONS
  -*-p*, *--pid* _PID_::
  -Operate on an existing PID and do not launch a new task.
  +*-s*, *--source* _PID_::
  +Which _PID_ to get the cookie from.
  +If this option is omitted, it will get the cookie from the current *{command}* process.
   
   *-d*, *--dest* _PID_::
  -When using *--copy*, specify a destination PID where you want to copy the cookie to.
  +Which _PID_ to modify the cookie of.
   
  -*-t*, *--type* _TYPE_::
  +*-t*, *--dest-type* _TYPE_::
   The type of the PID whose cookie will be modified. This can be one of three values:
   - *pid*, or process ID
   - *tgid*, or thread group ID (default value)
  @@ -63,25 +75,28 @@ Print version and exit.
   
   == EXAMPLES
   Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
  -*{command}*
  +*{command} get*
   
   Get the core scheduling cookie of a task with PID _123_{colon}::
  -*{command} -p* _123_
  +*{command} get -s* _123_
   
   Give a task with PID _123_ a new core scheduling cookie{colon}::
  -*{command} --new -p* _123_
  +*{command} new -d* _123_
   
   Spawn a new task with a new core scheduling cookie{colon}::
  -*{command} --new* \-- _command_ [_argument_...]
  +*{command} new* \-- _command_ [_argument_...]
  +
  +Copy the cookie from the current {command} process another task with pid _456_{colon}::
  +*{command} copy -d* _456_
   
   Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
  -*{command} --copy -p* _123_ *-d* _456_
  +*{command} copy -s* _123_ *-d* _456_
   
   Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
  -*{command} --copy -p* _123_ \-- _command_ [_argument_...]
  +*{command} copy -s* _123_ \-- _command_ [_argument_...]
   
   Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
  -*{command} --copy -p* _123_ *-t* pgid *-d* _456_
  +*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
   
   == PERMISSIONS
   When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
  @@ -98,12 +113,11 @@ If a _command_ is being executed, the return value of *{command}* will be the re
   This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
   
   == AUTHORS
  -mailto:thijs@raymakers.nl[Thijs Raymakers],
  -mailto:pauld@redhat.com[Phil Auld]
  +mailto:thijs@raymakers.nl[Thijs Raymakers]
   
   == COPYRIGHT
   
  -Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
  +Copyright {copyright} 2024 Thijs Raymakers. This is free software licensed under the EUPL.
   
   == SEE ALSO
   *chrt*(1),
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index b6be1717d..7e3cc46d2 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -3,7 +3,7 @@
    *
    * coresched.c - manage core scheduling cookies for tasks
    *
  - * Copyright (C) 2024 Thijs Raymakers, Phil Auld
  + * Copyright (C) 2024 Thijs Raymakers
    * Licensed under the EUPL v1.2
    */
   
  @@ -16,34 +16,33 @@
   #include "c.h"
   #include "closestream.h"
   #include "nls.h"
  -#include "optutils.h"
   #include "strutils.h"
   
   // These definitions might not be defined in the header files, even if the
   // prctl interface in the kernel accepts them as valid.
   #ifndef PR_SCHED_CORE
  -#define PR_SCHED_CORE 62
  +	#define PR_SCHED_CORE 62
   #endif
   #ifndef PR_SCHED_CORE_GET
  -#define PR_SCHED_CORE_GET 0
  +	#define PR_SCHED_CORE_GET 0
   #endif
   #ifndef PR_SCHED_CORE_CREATE
  -#define PR_SCHED_CORE_CREATE 1
  +	#define PR_SCHED_CORE_CREATE 1
   #endif
   #ifndef PR_SCHED_CORE_SHARE_TO
  -#define PR_SCHED_CORE_SHARE_TO 2
  +	#define PR_SCHED_CORE_SHARE_TO 2
   #endif
   #ifndef PR_SCHED_CORE_SHARE_FROM
  -#define PR_SCHED_CORE_SHARE_FROM 3
  +	#define PR_SCHED_CORE_SHARE_FROM 3
   #endif
   #ifndef PR_SCHED_CORE_SCOPE_THREAD
  -#define PR_SCHED_CORE_SCOPE_THREAD 0
  +	#define PR_SCHED_CORE_SCOPE_THREAD 0
   #endif
   #ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
  -#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
  +	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
   #endif
   #ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
  -#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
  +	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
   #endif
   
   typedef int sched_core_scope;
  @@ -55,7 +54,7 @@ typedef enum {
   } sched_core_cmd;
   
   struct args {
  -	pid_t pid;
  +	pid_t src;
   	pid_t dest;
   	sched_core_scope type;
   	sched_core_cmd cmd;
  @@ -67,40 +66,44 @@ static bool sched_core_verbose = false;
   static void __attribute__((__noreturn__)) usage(void)
   {
   	fputs(USAGE_HEADER, stdout);
  -	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
  -	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
  +	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
   		program_invocation_short_name);
  -	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
  +	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
   		program_invocation_short_name);
  -	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
  +	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
   		program_invocation_short_name);
   	fprintf(stdout,
  -		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
  +		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
  +		program_invocation_short_name);
  +	fprintf(stdout,
  +		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
   		program_invocation_short_name);
   
   	fputs(USAGE_SEPARATOR, stdout);
   	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
   
   	fputs(USAGE_FUNCTIONS, stdout);
  -	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
  -		  "                      execute a program with a new cookie."),
  +	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
   		stdout);
  -	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
  -		  "                      either another PID, or copy it to a new program"),
  +	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
  +		  "                            PID or execute a program with a new cookie"),
   		stdout);
  -	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
  -		  "   the PID provided via --pid.\n"),
  +	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
  +		  "                            to another PID, or execute a program with that\n"
  +		  "                            copied cookie"),
   		stdout);
   
   	fputs(USAGE_OPTIONS, stdout);
  -	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
  -	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
  -		  "                      the destination PID where to copy the cookie to."),
  +	fprintf(stdout,
  +		_(" -s, --source <PID>       which PID to get the cookie from\n"
  +		  "                            If omitted, it is the PID of %s itself\n"),
  +		program_invocation_short_name);
  +	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
   		stdout);
  -	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
  -		  "                      when a new core scheduling cookie is created.\n"
  -		  "                      Can be one of the following: pid, tgid or pgid.\n"
  -		  "                      The default is tgid."),
  +	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
  +		  "                            when a new core scheduling cookie is created.\n"
  +		  "                            Can be one of the following: pid, tgid or pgid.\n"
  +		  "                            The default is tgid."),
   		stdout);
   	fputs(USAGE_SEPARATOR, stdout);
   	fputsln(_(" -v, --verbose      verbose"), stdout);
  @@ -151,9 +154,8 @@ static void core_sched_copy_cookie(pid_t from, pid_t to,
   
   	if (sched_core_verbose) {
   		sched_core_cookie before = core_sched_get_cookie(from);
  -		fprintf(stderr,
  -			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
  -			program_invocation_short_name, before, from, to);
  +		warnx(_("copied cookie 0x%lx from PID %d to PID %d"), before,
  +		      from, to);
   	}
   }
   
  @@ -161,25 +163,21 @@ static void core_sched_get_and_print_cookie(pid_t pid)
   {
   	if (sched_core_verbose) {
   		sched_core_cookie after = core_sched_get_cookie(pid);
  -		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
  -			program_invocation_short_name, pid, after);
  +		warnx(_("set cookie of PID %d to 0x%lx"), pid, after);
   	}
   }
   
   static void core_sched_exec_with_cookie(struct args *args, char **argv)
   {
  -	if (!args->exec_argv_offset)
  -		usage();
  -
   	// Move the argument list to the first argument of the program
   	argv = &argv[args->exec_argv_offset];
   
   	// If a source PID is provided, try to copy the cookie from
   	// that PID. Otherwise, create a brand new cookie with the
   	// provided type.
  -	if (args->pid) {
  -		core_sched_pull_cookie(args->pid);
  -		core_sched_get_and_print_cookie(args->pid);
  +	if (args->src) {
  +		core_sched_pull_cookie(args->src);
  +		core_sched_get_and_print_cookie(args->src);
   	} else {
   		pid_t pid = getpid();
   		core_sched_create_cookie(pid, args->type);
  @@ -218,42 +216,26 @@ static sched_core_scope parse_core_sched_type(char *str)
   		  str);
   }
   
  -static void parse_arguments(int argc, char **argv, struct args *args)
  +static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
   {
   	int c;
   
   	static const struct option longopts[] = {
  -		{ "new", no_argument, NULL, 'n' },
  -		{ "copy", no_argument, NULL, 'c' },
  -		{ "pid", required_argument, NULL, 'p' },
  +		{ "source", required_argument, NULL, 's' },
   		{ "dest", required_argument, NULL, 'd' },
  -		{ "type", required_argument, NULL, 't' },
  +		{ "dest-type", required_argument, NULL, 't' },
   		{ "verbose", no_argument, NULL, 'v' },
   		{ "version", no_argument, NULL, 'V' },
   		{ "help", no_argument, NULL, 'h' },
   		{ NULL, 0, NULL, 0 }
   	};
  -	static const ul_excl_t excl[] = {
  -		{ 'c', 'n' }, // Cannot do both --new and --copy
  -		{ 'd', 'n' }, // Cannot have both --new and --dest
  -		{ 0 }
  -	};
  -
  -	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
   
  -	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
  -	       -1) {
  -		err_exclusive_options(c, longopts, excl, excl_st);
  +	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
   		switch (c) {
  -		case 'n':
  -			args->cmd = SCHED_CORE_CMD_NEW;
  -			break;
  -		case 'c':
  -			args->cmd = SCHED_CORE_CMD_COPY;
  -			break;
  -		case 'p':
  -			args->pid = strtopid_or_err(
  -				optarg, _("Failed to parse PID for -p/--pid"));
  +		case 's':
  +			args->src = strtopid_or_err(
  +				optarg,
  +				_("Failed to parse PID for -s/--source"));
   			break;
   		case 'd':
   			args->dest = strtopid_or_err(
  @@ -272,58 +254,77 @@ static void parse_arguments(int argc, char **argv, struct args *args)
   		default:
   			errtryhelp(EXIT_FAILURE);
   		}
  +
  +	if (argc <= optind) {
  +		args->cmd = SCHED_CORE_CMD_GET;
  +	} else {
  +		if (!strcmp(argv[optind], "get"))
  +			args->cmd = SCHED_CORE_CMD_GET;
  +		else if (!strcmp(argv[optind], "new"))
  +			args->cmd = SCHED_CORE_CMD_NEW;
  +		else if (!strcmp(argv[optind], "copy"))
  +			args->cmd = SCHED_CORE_CMD_COPY;
  +		else
  +			bad_usage(_("Unknown function"));
  +
  +		// Since we parsed an extra "option" outside of getopt_long, we have to
  +		// increment optind manually.
  +		++optind;
   	}
   
  -	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid)
  -		bad_usage(_("--copy: requires a -p/--pid"));
  +	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
  +		bad_usage(_("get does not accept the --dest option"));
  +
  +	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
  +		bad_usage(_("new does not accept the --source option"));
  +
  +	// If the -s/--source option is not specified, it defaults to the PID
  +	// of the current coresched process
  +	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
  +		args->src = getpid();
   
   	// More arguments have been passed, which means that the user wants to run
   	// another program with a core scheduling cookie.
   	if (argc > optind) {
   		switch (args->cmd) {
   		case SCHED_CORE_CMD_GET:
  -			bad_usage(_("Unknown command"));
  +			bad_usage(_("bad usage of the get function"));
   			break;
   		case SCHED_CORE_CMD_NEW:
  -			if (args->pid)
  +			if (args->dest)
   				bad_usage(_(
  -					"--new: cannot accept both a -p/--pid and a command"));
  +					"new requires either a -d/--dest or a command"));
   			else
   				args->exec_argv_offset = optind;
   			break;
   		case SCHED_CORE_CMD_COPY:
   			if (args->dest)
   				bad_usage(_(
  -					"--copy: cannot accept both a destination PID "
  -					"-d/--dest and a command"));
  +					"copy requires either a -d/--dest or a command"));
   			else
   				args->exec_argv_offset = optind;
   			break;
   		}
  -	}
  -
  -	if (argc <= optind) {
  -		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid)
  +	} else {
  +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
   			bad_usage(_(
  -				"--new: requires either a -p/--pid or a command"));
  +				"new requires either a -d/--dest or a command"));
   		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
   			bad_usage(_(
  -				"--copy: requires either a -d/--dest or a command"));
  +				"copy requires either a -d/--dest or a command"));
   	}
   }
   
   int main(int argc, char **argv)
   {
  -	struct args args = { 0 };
  -	args.cmd = SCHED_CORE_CMD_GET;
  -	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
  +	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
   
   	setlocale(LC_ALL, "");
   	bindtextdomain(PACKAGE, LOCALEDIR);
   	textdomain(PACKAGE);
   	close_stdout_atexit();
   
  -	parse_arguments(argc, argv, &args);
  +	parse_and_verify_arguments(argc, argv, &args);
   
   	if (!is_core_sched_supported())
   		errx(EXIT_FAILURE,
  @@ -331,27 +332,25 @@ int main(int argc, char **argv)
   		       "support CONFIG_SCHED_CORE?"));
   
   	sched_core_cookie cookie;
  -	pid_t pid;
   
   	switch (args.cmd) {
   	case SCHED_CORE_CMD_GET:
  -		pid = args.pid ? args.pid : getpid();
  -		cookie = core_sched_get_cookie(pid);
  -		printf(_("cookie of pid %d is 0x%lx\n"), pid, cookie);
  +		cookie = core_sched_get_cookie(args.src);
  +		printf(_("cookie of pid %d is 0x%lx\n"), args.src, cookie);
   		break;
   	case SCHED_CORE_CMD_NEW:
  -		if (args.pid) {
  -			core_sched_create_cookie(args.pid, args.type);
  -			core_sched_get_and_print_cookie(args.pid);
  -		} else {
  +		if (args.exec_argv_offset) {
   			core_sched_exec_with_cookie(&args, argv);
  +		} else {
  +			core_sched_create_cookie(args.dest, args.type);
  +			core_sched_get_and_print_cookie(args.dest);
   		}
   		break;
   	case SCHED_CORE_CMD_COPY:
  -		if (args.dest)
  -			core_sched_copy_cookie(args.pid, args.dest, args.type);
  -		else
  +		if (args.exec_argv_offset)
   			core_sched_exec_with_cookie(&args, argv);
  +		else
  +			core_sched_copy_cookie(args.src, args.dest, args.type);
   		break;
   	default:
   		usage();
  diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
  deleted file mode 100644
  index d08118c02..000000000
  --- a/tests/expected/schedutils/coresched-change-cookie-of-parent
  +++ /dev/null
  @@ -1 +0,0 @@
  -cookie of pid OWN_PID is DIFFERENT_COOKIE
  diff --git a/tests/expected/schedutils/coresched-spawn-child-with-new-cookie b/tests/expected/schedutils/coresched-copy-from-child-to-parent
  similarity index 100%
  rename from tests/expected/schedutils/coresched-spawn-child-with-new-cookie
  rename to tests/expected/schedutils/coresched-copy-from-child-to-parent
  diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
  new file mode 100644
  index 000000000..ecfc41142
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
  @@ -0,0 +1 @@
  +SAME_COOKIE
  diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
  new file mode 100644
  index 000000000..84f182cbe
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
  @@ -0,0 +1 @@
  +cookie of pid OWN_PID is PARENT_COOKIE
  diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
  new file mode 100644
  index 000000000..e183e0402
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
  @@ -0,0 +1 @@
  +cookie of pid PARENT_PID is PARENT_COOKIE
  diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
  deleted file mode 100644
  index 3d7f08040..000000000
  --- a/tests/expected/schedutils/coresched-get-own-pid-with-cookie
  +++ /dev/null
  @@ -1 +0,0 @@
  -cookie of pid OWN_PID is COOKIE
  diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
  new file mode 100644
  index 000000000..5b9c40052
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
  @@ -0,0 +1 @@
  +DIFFERENT_COOKIE
  diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid.err b/tests/expected/schedutils/coresched-set-cookie-own-pid.err
  deleted file mode 100644
  index 1816a4fbd..000000000
  --- a/tests/expected/schedutils/coresched-set-cookie-own-pid.err
  +++ /dev/null
  @@ -1 +0,0 @@
  -coresched: set cookie of PID OWN_PID to COOKIE
  diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
  new file mode 100644
  index 000000000..e7318ffc2
  --- /dev/null
  +++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
  @@ -0,0 +1 @@
  +coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
  diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
  new file mode 100644
  index 000000000..e7318ffc2
  --- /dev/null
  +++ b/tests/expected/schedutils/set-cookie-parent-pid
  @@ -0,0 +1 @@
  +coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
  diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
  index ed96d89de..e34fa319f 100755
  --- a/tests/ts/schedutils/coresched
  +++ b/tests/ts/schedutils/coresched
  @@ -13,8 +13,8 @@ TS_DESC="coresched"
   ts_init "$*"
   
   ts_check_test_command "$TS_CMD_CORESCHED"
  -ts_check_test_command "tee"
  -ts_check_test_command "sed"
  +ts_check_prog "tee"
  +ts_check_prog "sed"
   
   # If there is no kernel support, skip the test suite
   CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
  @@ -30,33 +30,54 @@ fi
   # - Any other cookie is replaced by `DIFFERENT_COOKIE`
   # The behavior of coresched does not depend on the exact values of these cookies, so using
   # placeholder values does not change the behavior tests.
  -ts_init_subtest "set-cookie-own-pid"
  -CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v -n -p $$ | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 | sed "s/$$/OWN_PID/g")
  -CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
  -if [ -z "$CORESCHED_COOKIE" ]; then
  -  ts_failed "empty value for CORESCHED_COOKIE "
  +ts_init_subtest "set-cookie-parent-pid"
  +CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
  +  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
  +  | sed "s/$$/PARENT_PID/g")
  +CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
  +if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
  +  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
   fi
  -CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
  +CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
  +  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
   echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
   ts_finalize_subtest
   
  -ts_init_subtest "get-own-pid-with-cookie"
  -$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
  +ts_init_subtest "get-cookie-parent-pid"
  +$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
  +  | sed -e "s/$$/PARENT_PID/g" \
  +        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
   ts_finalize_subtest
   
  -ts_init_subtest "spawn-child-with-new-cookie"
  -$TS_CMD_CORESCHED -n -- "$TS_CMD_CORESCHED" 2>> "$TS_ERRLOG" \
  -  | sed 's/^.*\(0x.*$\)/\1/g' \
  -  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
  -  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
  +ts_init_subtest "get-cookie-own-pid"
  +$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
  +  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
  +        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
   ts_finalize_subtest
   
  -ts_init_subtest "change-cookie-of-parent"
  -$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
  -$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" \
  -  | sed "s/$$/OWN_PID/g" \
  -  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
  -  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
  +ts_init_subtest "new-child-with-new-cookie"
  +$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
  +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
  +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
  +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
  +ts_finalize_subtest
  +
  +ts_init_subtest "copy-from-parent-to-nested-child"
  +$TS_CMD_CORESCHED new -- /bin/bash -c \
  +  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
  +2>> "$TS_ERRLOG" \
  +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
  +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
  +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
  +ts_finalize_subtest
  +
  +ts_init_subtest "copy-from-child-to-parent"
  +$TS_CMD_CORESCHED new -- /bin/bash -c \
  +  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
  +$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
  +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
  +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
  +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
   ts_finalize_subtest
   
   ts_finalize

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 138 +++++++
 schedutils/coresched.c                        | 358 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-copy-from-child-to-parent       |   1 +
 ...coresched-copy-from-parent-to-nested-child |   1 +
 .../schedutils/coresched-get-cookie-own-pid   |   1 +
 .../coresched-get-cookie-parent-pid           |   1 +
 .../coresched-new-child-with-new-cookie       |   1 +
 .../coresched-set-cookie-parent-pid.err       |   1 +
 .../expected/schedutils/set-cookie-parent-pid |   1 +
 tests/ts/schedutils/coresched                 |  83 ++++
 17 files changed, 620 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
 create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
 create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
 create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 9600ce49f..a3a648753 100644
--- a/meson.build
+++ b/meson.build
@@ -3111,13 +3111,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..00f2603e0
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,138 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [*get*] [*-s* _pid_]
+
+*{command}* *new* [*-t* _type_] *-d* _pid_
+
+*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*get*::
+Retrieve the core scheduling cookie of the PID specified in *-s*.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+*new*::
+Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
+
+*copy*::
+Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+If no function is specified, it will run the *get* function.
+
+== OPTIONS
+*-s*, *--source* _PID_::
+Which _PID_ to get the cookie from.
+If this option is omitted, it will get the cookie from the current *{command}* process.
+
+*-d*, *--dest* _PID_::
+Which _PID_ to modify the cookie of.
+
+*-t*, *--dest-type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
+*{command} get*
+
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} get -s* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} new -d* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} new* \-- _command_ [_argument_...]
+
+Copy the cookie from the current {command} process another task with pid _456_{colon}::
+*{command} copy -d* _456_
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} copy -s* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} copy -s* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
+
+== PERMISSIONS
+When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+
+If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
+
+== NOTES
+*{command}* requires core scheduling support in the kernel.
+This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..7e3cc46d2
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,358 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+	#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+	#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+	#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+	#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+	#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+	#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t src;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
+		stdout);
+	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
+		  "                            PID or execute a program with a new cookie"),
+		stdout);
+	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
+		  "                            to another PID, or execute a program with that\n"
+		  "                            copied cookie"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fprintf(stdout,
+		_(" -s, --source <PID>       which PID to get the cookie from\n"
+		  "                            If omitted, it is the PID of %s itself\n"),
+		program_invocation_short_name);
+	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
+		stdout);
+	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
+		  "                            when a new core scheduling cookie is created.\n"
+		  "                            Can be one of the following: pid, tgid or pgid.\n"
+		  "                            The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout, USAGE_HELP_OPTIONS(20));
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...)                 \
+	do {                              \
+		warnx(FMT);               \
+		errtryhelp(EXIT_FAILURE); \
+	} while (0)
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0))
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		warnx(_("copied cookie 0x%lx from PID %d to PID %d"), before,
+		      from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		warnx(_("set cookie of PID %d to 0x%lx"), pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->src) {
+		core_sched_pull_cookie(args->src);
+		core_sched_get_and_print_cookie(args->src);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv))
+		errexec(argv[0]);
+}
+
+// If PR_SCHED_CORE is not recognized, or not supported on this system,
+// then prctl will set errno to EINVAL. Assuming all other operands of
+// prctl are valid, we can use errno==EINVAL as a check to see whether
+// core scheduling is available on this system.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		if (errno == EINVAL)
+			return false;
+
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strcmp(str, "pid"))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strcmp(str, "tgid"))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strcmp(str, "pgid"))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "source", required_argument, NULL, 's' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "dest-type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
+		switch (c) {
+		case 's':
+			args->src = strtopid_or_err(
+				optarg,
+				_("Failed to parse PID for -s/--source"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+
+	if (argc <= optind) {
+		args->cmd = SCHED_CORE_CMD_GET;
+	} else {
+		if (!strcmp(argv[optind], "get"))
+			args->cmd = SCHED_CORE_CMD_GET;
+		else if (!strcmp(argv[optind], "new"))
+			args->cmd = SCHED_CORE_CMD_NEW;
+		else if (!strcmp(argv[optind], "copy"))
+			args->cmd = SCHED_CORE_CMD_COPY;
+		else
+			bad_usage(_("Unknown function"));
+
+		// Since we parsed an extra "option" outside of getopt_long, we have to
+		// increment optind manually.
+		++optind;
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
+		bad_usage(_("get does not accept the --dest option"));
+
+	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
+		bad_usage(_("new does not accept the --source option"));
+
+	// If the -s/--source option is not specified, it defaults to the PID
+	// of the current coresched process
+	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
+		args->src = getpid();
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("bad usage of the get function"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->dest)
+				bad_usage(_(
+					"new requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest)
+				bad_usage(_(
+					"copy requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		}
+	} else {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
+			bad_usage(_(
+				"new requires either a -d/--dest or a command"));
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
+			bad_usage(_(
+				"copy requires either a -d/--dest or a command"));
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_and_verify_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported())
+		errx(EXIT_FAILURE,
+		     _("No support for core scheduling found. Does your kernel"
+		       "support CONFIG_SCHED_CORE?"));
+
+	sched_core_cookie cookie;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		cookie = core_sched_get_cookie(args.src);
+		printf(_("cookie of pid %d is 0x%lx\n"), args.src, cookie);
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.exec_argv_offset) {
+			core_sched_exec_with_cookie(&args, argv);
+		} else {
+			core_sched_create_cookie(args.dest, args.type);
+			core_sched_get_and_print_cookie(args.dest);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.exec_argv_offset)
+			core_sched_exec_with_cookie(&args, argv);
+		else
+			core_sched_copy_cookie(args.src, args.dest, args.type);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-copy-from-child-to-parent b/tests/expected/schedutils/coresched-copy-from-child-to-parent
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-child-to-parent
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
new file mode 100644
index 000000000..ecfc41142
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
@@ -0,0 +1 @@
+SAME_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
new file mode 100644
index 000000000..84f182cbe
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
@@ -0,0 +1 @@
+cookie of pid OWN_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
new file mode 100644
index 000000000..e183e0402
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
@@ -0,0 +1 @@
+cookie of pid PARENT_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/set-cookie-parent-pid
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..e34fa319f
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,83 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+ts_check_prog "tee"
+ts_check_prog "sed"
+
+# If there is no kernel support, skip the test suite
+CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
+if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
+  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
+fi
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "set-cookie-parent-pid"
+CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
+  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
+  | sed "s/$$/PARENT_PID/g")
+CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
+  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
+fi
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
+  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-parent-pid"
+$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
+  | sed -e "s/$$/PARENT_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-own-pid"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "new-child-with-new-cookie"
+$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-parent-to-nested-child"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
+2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-child-to-parent"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v7] coresched: Manage core scheduling cookies for tasks
  2024-04-10 21:11                                 ` [PATCH v7] " Thijs Raymakers
@ 2024-04-10 22:15                                   ` Phil Auld
  2024-04-11 11:02                                     ` [PATCH v8] " Thijs Raymakers
  0 siblings, 1 reply; 42+ messages in thread
From: Phil Auld @ 2024-04-10 22:15 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, thomas, util-linux

Hi Thijs,

On Wed, Apr 10, 2024 at 11:11:41PM +0200 Thijs Raymakers wrote:
> Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>

You took me out throughout.  I'm not sure that was needed :)

The new version looks fine. I'm not responsible for merging so if
Karel prefers the new arguments that works for me.

I'll update and test it out tomorrow. But I think you can put me back
if that works for you.


Cheers,
Phil


> ---
> 
> Hi Karel,
> 
> Thanks for the review!
> 
> Op 09-04-2024 om 3:44 p.m. schreef Karel Zak:
> > I find it a little confusing that the --pid is sometimes used as a
> > source and in other cases as a destination.
> > 
> >      coresched --new --pid 123              # pid is destination
> >      coresched --copy --pid 123 --dest 456  # pid is source
> > 
> > It seems --copy always requires a source PID (according to the man
> > page), why not require a PID as argument for --copy:
> > 
> >     coresched --copy 123 --pid 456
> > 
> > in this way --pid will be always destination (for 'copy' and 'new'
> > functions) and you will not need extra --dest option at all.
> > 
> > If you want to keep the basic functions (e.g. --copy) without
> > arguments, it would be better to have --source, --dest, and
> > --dest-type instead of using the ambiguous --pid.
> > 
> > I can also imagine the basic "functions" without "--".
> > 
> >     coresched [get] [--dest] 123
> >     coresched copy [--source] 123 [--dest] 456
> >     coresched new [--dest] 456
> > 
> > In my opinion, we do not have to strictly adhere to old taskset or
> > similar commands.
> 
> The command line arguments are currently somewhat similar in meaning
> to the arguments of taskset, which Phil mentioned he preferred.
> taskset (and others) uses --pid to refer to the PID. This works
> fine if you operate on just a single PID.
> 
> I agree that this version of coresched can be indeed confusing to
> use if --pid is overloaded to mean both "source" and
> "destination", depending on the context. coresched needs at most two
> PIDs and can thus not use --pid for everything.
> 
> How those two options are called/used seems to be a tradeoff between
> the familiarity by taskset users vs consistency within coreset.
> Either you use --pid which is familiar to users because other programs
> define PIDs like that, but the semantics will be inconsistent and will
> change depending what function of coresched you use.
> Or you use --source and --dest, which is a bit more inconvenient to
> use because you cannot use the same option to refer to a PID across the
> different coresed functions, but you will have consistent behavior of
> each option.
> 
> Since you mention that we don't strictly have to adhere to taskset, I
> do have a slight preference for --source/--dest instead of --pid/--dest.
> With the help of the usage and/or man page, this difference shouldn't be
> too cumbersome to users that are familiar with other tools. The benefit
> of internal consistency outweights the benefit of familiarity in my
> opinion.
> 
> Version 7 of the patch changes the arguments from --pid/--dest/--type
> (-t/-d/-t) to --source/--dest/--dest-type (-s/-d/-t). It also has the
> functions without the "--", as well as the other suggestions that you
> made. I've also added two more tests.
> 
> The commands now look something like this
> 
>     coresched [get] [--source <PID>]
>     coresched new [-t <TYPE>] --dest <PID>
>     coresched new [-t <TYPE>] -- PROGRAM [ARGS...]
>     coresched copy [--source <PID>] [-t <TYPE>] --dest <PID>
>     coresched copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]
> 
> > It should be noted that command line arguments are crucial, as they
> > are difficult to modify after release.
> 
> It is good that you mentioned that the arguments felt confusing to use,
> because that is something that became a bit invisible to me after using
> the util for some time. It is important to get the arguments right, and
> making sure that they are sensible and self-consistent. These new
> command line arguments work for me as well. Phil previously expressed a
> preference for the --pid style so would like to know what his opinion is.
> 
> > pedantic note, use extra space within ifdef
> > 
> > #ifndef PR_SCHED_CORE
> > # define PR_SCHED_CORE 62
> > #endif
> 
> I've changed to
> 
> #ifndef PR_SCHED_CORE
> 	#define PR_SCHED_CORE 62
> #endif
> 
> which looks a bit nicer in my opinion than what clang-format does with
> the AfterHash option. (I've used IndentPPDirectives: BeforeHash instead
> of IndentPPDirectives: None, which is the default in the kernel
> clang-format file)
> 
> 
> CC: Phil Auld and Thomas Weißschuh
> I've removed your Signed-off-by/Reviewed-by tags since this version
> differs significantly from the version that you've previously reviewed.
> I did't want to just assume that you approve of this version as well.
> With your permission, I can add the tags back again.
> 
> 
> Thijs
> 
> Interdiff against v6:
>   diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
>   index c83fcdf91..00f2603e0 100644
>   --- a/schedutils/coresched.1.adoc
>   +++ b/schedutils/coresched.1.adoc
>   @@ -17,9 +17,15 @@ coresched - manage core scheduling cookies for tasks
>    
>    == SYNOPSIS
>    
>   -*{command}* [options] *-p* _pid_
>   +*{command}* [*get*] [*-s* _pid_]
>    
>   -*{command}* [options] \-- _command_ [_argument_...]
>   +*{command}* *new* [*-t* _type_] *-d* _pid_
>   +
>   +*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
>   +
>   +*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
>   +
>   +*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
>    
>    == DESCRIPTION
>    The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
>   @@ -31,22 +37,28 @@ Only tasks have the same cookie are allowed to be scheduled on the same physical
>    It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
>    
>    == FUNCTIONS
>   -*-n*, *--new*::
>   -Assign a new cookie to an existing PID or to execute _command_ with a new cookie.
>   +*get*::
>   +Retrieve the core scheduling cookie of the PID specified in *-s*.
>   +If *-s* is omitted, it will get the cookie of the current *{command}* process.
>    
>   -*-c*, *--copy*::
>   -Copy the cookie from an existing PID to another PID, or execute _command_ with that copied cookie.
>   +*new*::
>   +Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
>    
>   -If no function is specified, it will print the cookie of the current task, or of the task specified with the *-p* option.
>   +*copy*::
>   +Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
>   +If *-s* is omitted, it will get the cookie of the current *{command}* process.
>   +
>   +If no function is specified, it will run the *get* function.
>    
>    == OPTIONS
>   -*-p*, *--pid* _PID_::
>   -Operate on an existing PID and do not launch a new task.
>   +*-s*, *--source* _PID_::
>   +Which _PID_ to get the cookie from.
>   +If this option is omitted, it will get the cookie from the current *{command}* process.
>    
>    *-d*, *--dest* _PID_::
>   -When using *--copy*, specify a destination PID where you want to copy the cookie to.
>   +Which _PID_ to modify the cookie of.
>    
>   -*-t*, *--type* _TYPE_::
>   +*-t*, *--dest-type* _TYPE_::
>    The type of the PID whose cookie will be modified. This can be one of three values:
>    - *pid*, or process ID
>    - *tgid*, or thread group ID (default value)
>   @@ -63,25 +75,28 @@ Print version and exit.
>    
>    == EXAMPLES
>    Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
>   -*{command}*
>   +*{command} get*
>    
>    Get the core scheduling cookie of a task with PID _123_{colon}::
>   -*{command} -p* _123_
>   +*{command} get -s* _123_
>    
>    Give a task with PID _123_ a new core scheduling cookie{colon}::
>   -*{command} --new -p* _123_
>   +*{command} new -d* _123_
>    
>    Spawn a new task with a new core scheduling cookie{colon}::
>   -*{command} --new* \-- _command_ [_argument_...]
>   +*{command} new* \-- _command_ [_argument_...]
>   +
>   +Copy the cookie from the current {command} process another task with pid _456_{colon}::
>   +*{command} copy -d* _456_
>    
>    Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
>   -*{command} --copy -p* _123_ *-d* _456_
>   +*{command} copy -s* _123_ *-d* _456_
>    
>    Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
>   -*{command} --copy -p* _123_ \-- _command_ [_argument_...]
>   +*{command} copy -s* _123_ \-- _command_ [_argument_...]
>    
>    Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
>   -*{command} --copy -p* _123_ *-t* pgid *-d* _456_
>   +*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
>    
>    == PERMISSIONS
>    When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
>   @@ -98,12 +113,11 @@ If a _command_ is being executed, the return value of *{command}* will be the re
>    This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
>    
>    == AUTHORS
>   -mailto:thijs@raymakers.nl[Thijs Raymakers],
>   -mailto:pauld@redhat.com[Phil Auld]
>   +mailto:thijs@raymakers.nl[Thijs Raymakers]
>    
>    == COPYRIGHT
>    
>   -Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
>   +Copyright {copyright} 2024 Thijs Raymakers. This is free software licensed under the EUPL.
>    
>    == SEE ALSO
>    *chrt*(1),
>   diff --git a/schedutils/coresched.c b/schedutils/coresched.c
>   index b6be1717d..7e3cc46d2 100644
>   --- a/schedutils/coresched.c
>   +++ b/schedutils/coresched.c
>   @@ -3,7 +3,7 @@
>     *
>     * coresched.c - manage core scheduling cookies for tasks
>     *
>   - * Copyright (C) 2024 Thijs Raymakers, Phil Auld
>   + * Copyright (C) 2024 Thijs Raymakers
>     * Licensed under the EUPL v1.2
>     */
>    
>   @@ -16,34 +16,33 @@
>    #include "c.h"
>    #include "closestream.h"
>    #include "nls.h"
>   -#include "optutils.h"
>    #include "strutils.h"
>    
>    // These definitions might not be defined in the header files, even if the
>    // prctl interface in the kernel accepts them as valid.
>    #ifndef PR_SCHED_CORE
>   -#define PR_SCHED_CORE 62
>   +	#define PR_SCHED_CORE 62
>    #endif
>    #ifndef PR_SCHED_CORE_GET
>   -#define PR_SCHED_CORE_GET 0
>   +	#define PR_SCHED_CORE_GET 0
>    #endif
>    #ifndef PR_SCHED_CORE_CREATE
>   -#define PR_SCHED_CORE_CREATE 1
>   +	#define PR_SCHED_CORE_CREATE 1
>    #endif
>    #ifndef PR_SCHED_CORE_SHARE_TO
>   -#define PR_SCHED_CORE_SHARE_TO 2
>   +	#define PR_SCHED_CORE_SHARE_TO 2
>    #endif
>    #ifndef PR_SCHED_CORE_SHARE_FROM
>   -#define PR_SCHED_CORE_SHARE_FROM 3
>   +	#define PR_SCHED_CORE_SHARE_FROM 3
>    #endif
>    #ifndef PR_SCHED_CORE_SCOPE_THREAD
>   -#define PR_SCHED_CORE_SCOPE_THREAD 0
>   +	#define PR_SCHED_CORE_SCOPE_THREAD 0
>    #endif
>    #ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
>   -#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
>   +	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
>    #endif
>    #ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
>   -#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
>   +	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
>    #endif
>    
>    typedef int sched_core_scope;
>   @@ -55,7 +54,7 @@ typedef enum {
>    } sched_core_cmd;
>    
>    struct args {
>   -	pid_t pid;
>   +	pid_t src;
>    	pid_t dest;
>    	sched_core_scope type;
>    	sched_core_cmd cmd;
>   @@ -67,40 +66,44 @@ static bool sched_core_verbose = false;
>    static void __attribute__((__noreturn__)) usage(void)
>    {
>    	fputs(USAGE_HEADER, stdout);
>   -	fprintf(stdout, _(" %s [-p PID]\n"), program_invocation_short_name);
>   -	fprintf(stdout, _(" %s --new [-t <TYPE>] -p <PID>\n"),
>   +	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
>    		program_invocation_short_name);
>   -	fprintf(stdout, _(" %s --new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
>   +	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
>    		program_invocation_short_name);
>   -	fprintf(stdout, _(" %s --copy -p <PID> [-t <TYPE>] -d <PID>\n"),
>   +	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
>    		program_invocation_short_name);
>    	fprintf(stdout,
>   -		_(" %s --copy -p <PID> [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
>   +		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
>   +		program_invocation_short_name);
>   +	fprintf(stdout,
>   +		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
>    		program_invocation_short_name);
>    
>    	fputs(USAGE_SEPARATOR, stdout);
>    	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
>    
>    	fputs(USAGE_FUNCTIONS, stdout);
>   -	fputsln(_(" -n, --new          assign a new core scheduling cookie to an existing PID or\n"
>   -		  "                      execute a program with a new cookie."),
>   +	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
>    		stdout);
>   -	fputsln(_(" -c, --copy         copy the core scheduling cookie from an existing PID to\n"
>   -		  "                      either another PID, or copy it to a new program"),
>   +	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
>   +		  "                            PID or execute a program with a new cookie"),
>    		stdout);
>   -	fputsln(_("\n If no function is provided, it will retrieve and print the cookie from\n"
>   -		  "   the PID provided via --pid.\n"),
>   +	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
>   +		  "                            to another PID, or execute a program with that\n"
>   +		  "                            copied cookie"),
>    		stdout);
>    
>    	fputs(USAGE_OPTIONS, stdout);
>   -	fputsln(_(" -p, --pid <PID>    operate on an existing PID"), stdout);
>   -	fputsln(_(" -d, --dest <PID>   when copying a cookie from an existing PID, --dest is\n"
>   -		  "                      the destination PID where to copy the cookie to."),
>   +	fprintf(stdout,
>   +		_(" -s, --source <PID>       which PID to get the cookie from\n"
>   +		  "                            If omitted, it is the PID of %s itself\n"),
>   +		program_invocation_short_name);
>   +	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
>    		stdout);
>   -	fputsln(_(" -t, --type <TYPE>  type of the destination PID, or the type of the PID\n"
>   -		  "                      when a new core scheduling cookie is created.\n"
>   -		  "                      Can be one of the following: pid, tgid or pgid.\n"
>   -		  "                      The default is tgid."),
>   +	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
>   +		  "                            when a new core scheduling cookie is created.\n"
>   +		  "                            Can be one of the following: pid, tgid or pgid.\n"
>   +		  "                            The default is tgid."),
>    		stdout);
>    	fputs(USAGE_SEPARATOR, stdout);
>    	fputsln(_(" -v, --verbose      verbose"), stdout);
>   @@ -151,9 +154,8 @@ static void core_sched_copy_cookie(pid_t from, pid_t to,
>    
>    	if (sched_core_verbose) {
>    		sched_core_cookie before = core_sched_get_cookie(from);
>   -		fprintf(stderr,
>   -			_("%s: copied cookie 0x%lx from PID %d to PID %d\n"),
>   -			program_invocation_short_name, before, from, to);
>   +		warnx(_("copied cookie 0x%lx from PID %d to PID %d"), before,
>   +		      from, to);
>    	}
>    }
>    
>   @@ -161,25 +163,21 @@ static void core_sched_get_and_print_cookie(pid_t pid)
>    {
>    	if (sched_core_verbose) {
>    		sched_core_cookie after = core_sched_get_cookie(pid);
>   -		fprintf(stderr, _("%s: set cookie of PID %d to 0x%lx\n"),
>   -			program_invocation_short_name, pid, after);
>   +		warnx(_("set cookie of PID %d to 0x%lx"), pid, after);
>    	}
>    }
>    
>    static void core_sched_exec_with_cookie(struct args *args, char **argv)
>    {
>   -	if (!args->exec_argv_offset)
>   -		usage();
>   -
>    	// Move the argument list to the first argument of the program
>    	argv = &argv[args->exec_argv_offset];
>    
>    	// If a source PID is provided, try to copy the cookie from
>    	// that PID. Otherwise, create a brand new cookie with the
>    	// provided type.
>   -	if (args->pid) {
>   -		core_sched_pull_cookie(args->pid);
>   -		core_sched_get_and_print_cookie(args->pid);
>   +	if (args->src) {
>   +		core_sched_pull_cookie(args->src);
>   +		core_sched_get_and_print_cookie(args->src);
>    	} else {
>    		pid_t pid = getpid();
>    		core_sched_create_cookie(pid, args->type);
>   @@ -218,42 +216,26 @@ static sched_core_scope parse_core_sched_type(char *str)
>    		  str);
>    }
>    
>   -static void parse_arguments(int argc, char **argv, struct args *args)
>   +static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
>    {
>    	int c;
>    
>    	static const struct option longopts[] = {
>   -		{ "new", no_argument, NULL, 'n' },
>   -		{ "copy", no_argument, NULL, 'c' },
>   -		{ "pid", required_argument, NULL, 'p' },
>   +		{ "source", required_argument, NULL, 's' },
>    		{ "dest", required_argument, NULL, 'd' },
>   -		{ "type", required_argument, NULL, 't' },
>   +		{ "dest-type", required_argument, NULL, 't' },
>    		{ "verbose", no_argument, NULL, 'v' },
>    		{ "version", no_argument, NULL, 'V' },
>    		{ "help", no_argument, NULL, 'h' },
>    		{ NULL, 0, NULL, 0 }
>    	};
>   -	static const ul_excl_t excl[] = {
>   -		{ 'c', 'n' }, // Cannot do both --new and --copy
>   -		{ 'd', 'n' }, // Cannot have both --new and --dest
>   -		{ 0 }
>   -	};
>   -
>   -	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
>    
>   -	while ((c = getopt_long(argc, argv, "ncp:d:t:vVh", longopts, NULL)) !=
>   -	       -1) {
>   -		err_exclusive_options(c, longopts, excl, excl_st);
>   +	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
>    		switch (c) {
>   -		case 'n':
>   -			args->cmd = SCHED_CORE_CMD_NEW;
>   -			break;
>   -		case 'c':
>   -			args->cmd = SCHED_CORE_CMD_COPY;
>   -			break;
>   -		case 'p':
>   -			args->pid = strtopid_or_err(
>   -				optarg, _("Failed to parse PID for -p/--pid"));
>   +		case 's':
>   +			args->src = strtopid_or_err(
>   +				optarg,
>   +				_("Failed to parse PID for -s/--source"));
>    			break;
>    		case 'd':
>    			args->dest = strtopid_or_err(
>   @@ -272,58 +254,77 @@ static void parse_arguments(int argc, char **argv, struct args *args)
>    		default:
>    			errtryhelp(EXIT_FAILURE);
>    		}
>   +
>   +	if (argc <= optind) {
>   +		args->cmd = SCHED_CORE_CMD_GET;
>   +	} else {
>   +		if (!strcmp(argv[optind], "get"))
>   +			args->cmd = SCHED_CORE_CMD_GET;
>   +		else if (!strcmp(argv[optind], "new"))
>   +			args->cmd = SCHED_CORE_CMD_NEW;
>   +		else if (!strcmp(argv[optind], "copy"))
>   +			args->cmd = SCHED_CORE_CMD_COPY;
>   +		else
>   +			bad_usage(_("Unknown function"));
>   +
>   +		// Since we parsed an extra "option" outside of getopt_long, we have to
>   +		// increment optind manually.
>   +		++optind;
>    	}
>    
>   -	if (args->cmd == SCHED_CORE_CMD_COPY && !args->pid)
>   -		bad_usage(_("--copy: requires a -p/--pid"));
>   +	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
>   +		bad_usage(_("get does not accept the --dest option"));
>   +
>   +	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
>   +		bad_usage(_("new does not accept the --source option"));
>   +
>   +	// If the -s/--source option is not specified, it defaults to the PID
>   +	// of the current coresched process
>   +	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
>   +		args->src = getpid();
>    
>    	// More arguments have been passed, which means that the user wants to run
>    	// another program with a core scheduling cookie.
>    	if (argc > optind) {
>    		switch (args->cmd) {
>    		case SCHED_CORE_CMD_GET:
>   -			bad_usage(_("Unknown command"));
>   +			bad_usage(_("bad usage of the get function"));
>    			break;
>    		case SCHED_CORE_CMD_NEW:
>   -			if (args->pid)
>   +			if (args->dest)
>    				bad_usage(_(
>   -					"--new: cannot accept both a -p/--pid and a command"));
>   +					"new requires either a -d/--dest or a command"));
>    			else
>    				args->exec_argv_offset = optind;
>    			break;
>    		case SCHED_CORE_CMD_COPY:
>    			if (args->dest)
>    				bad_usage(_(
>   -					"--copy: cannot accept both a destination PID "
>   -					"-d/--dest and a command"));
>   +					"copy requires either a -d/--dest or a command"));
>    			else
>    				args->exec_argv_offset = optind;
>    			break;
>    		}
>   -	}
>   -
>   -	if (argc <= optind) {
>   -		if (args->cmd == SCHED_CORE_CMD_NEW && !args->pid)
>   +	} else {
>   +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
>    			bad_usage(_(
>   -				"--new: requires either a -p/--pid or a command"));
>   +				"new requires either a -d/--dest or a command"));
>    		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
>    			bad_usage(_(
>   -				"--copy: requires either a -d/--dest or a command"));
>   +				"copy requires either a -d/--dest or a command"));
>    	}
>    }
>    
>    int main(int argc, char **argv)
>    {
>   -	struct args args = { 0 };
>   -	args.cmd = SCHED_CORE_CMD_GET;
>   -	args.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
>   +	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
>    
>    	setlocale(LC_ALL, "");
>    	bindtextdomain(PACKAGE, LOCALEDIR);
>    	textdomain(PACKAGE);
>    	close_stdout_atexit();
>    
>   -	parse_arguments(argc, argv, &args);
>   +	parse_and_verify_arguments(argc, argv, &args);
>    
>    	if (!is_core_sched_supported())
>    		errx(EXIT_FAILURE,
>   @@ -331,27 +332,25 @@ int main(int argc, char **argv)
>    		       "support CONFIG_SCHED_CORE?"));
>    
>    	sched_core_cookie cookie;
>   -	pid_t pid;
>    
>    	switch (args.cmd) {
>    	case SCHED_CORE_CMD_GET:
>   -		pid = args.pid ? args.pid : getpid();
>   -		cookie = core_sched_get_cookie(pid);
>   -		printf(_("cookie of pid %d is 0x%lx\n"), pid, cookie);
>   +		cookie = core_sched_get_cookie(args.src);
>   +		printf(_("cookie of pid %d is 0x%lx\n"), args.src, cookie);
>    		break;
>    	case SCHED_CORE_CMD_NEW:
>   -		if (args.pid) {
>   -			core_sched_create_cookie(args.pid, args.type);
>   -			core_sched_get_and_print_cookie(args.pid);
>   -		} else {
>   +		if (args.exec_argv_offset) {
>    			core_sched_exec_with_cookie(&args, argv);
>   +		} else {
>   +			core_sched_create_cookie(args.dest, args.type);
>   +			core_sched_get_and_print_cookie(args.dest);
>    		}
>    		break;
>    	case SCHED_CORE_CMD_COPY:
>   -		if (args.dest)
>   -			core_sched_copy_cookie(args.pid, args.dest, args.type);
>   -		else
>   +		if (args.exec_argv_offset)
>    			core_sched_exec_with_cookie(&args, argv);
>   +		else
>   +			core_sched_copy_cookie(args.src, args.dest, args.type);
>    		break;
>    	default:
>    		usage();
>   diff --git a/tests/expected/schedutils/coresched-change-cookie-of-parent b/tests/expected/schedutils/coresched-change-cookie-of-parent
>   deleted file mode 100644
>   index d08118c02..000000000
>   --- a/tests/expected/schedutils/coresched-change-cookie-of-parent
>   +++ /dev/null
>   @@ -1 +0,0 @@
>   -cookie of pid OWN_PID is DIFFERENT_COOKIE
>   diff --git a/tests/expected/schedutils/coresched-spawn-child-with-new-cookie b/tests/expected/schedutils/coresched-copy-from-child-to-parent
>   similarity index 100%
>   rename from tests/expected/schedutils/coresched-spawn-child-with-new-cookie
>   rename to tests/expected/schedutils/coresched-copy-from-child-to-parent
>   diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
>   new file mode 100644
>   index 000000000..ecfc41142
>   --- /dev/null
>   +++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
>   @@ -0,0 +1 @@
>   +SAME_COOKIE
>   diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
>   new file mode 100644
>   index 000000000..84f182cbe
>   --- /dev/null
>   +++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
>   @@ -0,0 +1 @@
>   +cookie of pid OWN_PID is PARENT_COOKIE
>   diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
>   new file mode 100644
>   index 000000000..e183e0402
>   --- /dev/null
>   +++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
>   @@ -0,0 +1 @@
>   +cookie of pid PARENT_PID is PARENT_COOKIE
>   diff --git a/tests/expected/schedutils/coresched-get-own-pid-with-cookie b/tests/expected/schedutils/coresched-get-own-pid-with-cookie
>   deleted file mode 100644
>   index 3d7f08040..000000000
>   --- a/tests/expected/schedutils/coresched-get-own-pid-with-cookie
>   +++ /dev/null
>   @@ -1 +0,0 @@
>   -cookie of pid OWN_PID is COOKIE
>   diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
>   new file mode 100644
>   index 000000000..5b9c40052
>   --- /dev/null
>   +++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
>   @@ -0,0 +1 @@
>   +DIFFERENT_COOKIE
>   diff --git a/tests/expected/schedutils/coresched-set-cookie-own-pid.err b/tests/expected/schedutils/coresched-set-cookie-own-pid.err
>   deleted file mode 100644
>   index 1816a4fbd..000000000
>   --- a/tests/expected/schedutils/coresched-set-cookie-own-pid.err
>   +++ /dev/null
>   @@ -1 +0,0 @@
>   -coresched: set cookie of PID OWN_PID to COOKIE
>   diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
>   new file mode 100644
>   index 000000000..e7318ffc2
>   --- /dev/null
>   +++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
>   @@ -0,0 +1 @@
>   +coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
>   diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
>   new file mode 100644
>   index 000000000..e7318ffc2
>   --- /dev/null
>   +++ b/tests/expected/schedutils/set-cookie-parent-pid
>   @@ -0,0 +1 @@
>   +coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
>   diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
>   index ed96d89de..e34fa319f 100755
>   --- a/tests/ts/schedutils/coresched
>   +++ b/tests/ts/schedutils/coresched
>   @@ -13,8 +13,8 @@ TS_DESC="coresched"
>    ts_init "$*"
>    
>    ts_check_test_command "$TS_CMD_CORESCHED"
>   -ts_check_test_command "tee"
>   -ts_check_test_command "sed"
>   +ts_check_prog "tee"
>   +ts_check_prog "sed"
>    
>    # If there is no kernel support, skip the test suite
>    CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
>   @@ -30,33 +30,54 @@ fi
>    # - Any other cookie is replaced by `DIFFERENT_COOKIE`
>    # The behavior of coresched does not depend on the exact values of these cookies, so using
>    # placeholder values does not change the behavior tests.
>   -ts_init_subtest "set-cookie-own-pid"
>   -CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v -n -p $$ | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 | sed "s/$$/OWN_PID/g")
>   -CORESCHED_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
>   -if [ -z "$CORESCHED_COOKIE" ]; then
>   -  ts_failed "empty value for CORESCHED_COOKIE "
>   +ts_init_subtest "set-cookie-parent-pid"
>   +CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
>   +  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
>   +  | sed "s/$$/PARENT_PID/g")
>   +CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
>   +if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
>   +  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
>    fi
>   -CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" | sed "s/$CORESCHED_COOKIE/COOKIE/g")
>   +CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
>   +  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
>    echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
>    ts_finalize_subtest
>    
>   -ts_init_subtest "get-own-pid-with-cookie"
>   -$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" | sed "s/$$/OWN_PID/g" | sed "s/$CORESCHED_COOKIE/COOKIE/g" >> "$TS_OUTPUT"
>   +ts_init_subtest "get-cookie-parent-pid"
>   +$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
>   +  | sed -e "s/$$/PARENT_PID/g" \
>   +        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
>    ts_finalize_subtest
>    
>   -ts_init_subtest "spawn-child-with-new-cookie"
>   -$TS_CMD_CORESCHED -n -- "$TS_CMD_CORESCHED" 2>> "$TS_ERRLOG" \
>   -  | sed 's/^.*\(0x.*$\)/\1/g' \
>   -  | sed "s/$CORESCHED_COOKIE/SAME_COOKIE/g" \
>   -  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
>   +ts_init_subtest "get-cookie-own-pid"
>   +$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
>   +  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
>   +        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
>    ts_finalize_subtest
>    
>   -ts_init_subtest "change-cookie-of-parent"
>   -$TS_CMD_CORESCHED -n -- /bin/bash -c "$TS_CMD_CORESCHED -c -p \$\$ -d $$"
>   -$TS_CMD_CORESCHED -p $$ 2>> "$TS_ERRLOG" \
>   -  | sed "s/$$/OWN_PID/g" \
>   -  | sed "s/$CORESCHED_COOKIE/COOKIE/g" \
>   -  | sed "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
>   +ts_init_subtest "new-child-with-new-cookie"
>   +$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
>   +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
>   +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
>   +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
>   +ts_finalize_subtest
>   +
>   +ts_init_subtest "copy-from-parent-to-nested-child"
>   +$TS_CMD_CORESCHED new -- /bin/bash -c \
>   +  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
>   +2>> "$TS_ERRLOG" \
>   +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
>   +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
>   +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
>   +ts_finalize_subtest
>   +
>   +ts_init_subtest "copy-from-child-to-parent"
>   +$TS_CMD_CORESCHED new -- /bin/bash -c \
>   +  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
>   +$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
>   +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
>   +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
>   +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
>    ts_finalize_subtest
>    
>    ts_finalize
> 
>  .gitignore                                    |   1 +
>  bash-completion/coresched                     |   0
>  configure.ac                                  |  12 +-
>  meson.build                                   |  16 +-
>  meson_options.txt                             |   2 +-
>  schedutils/Makemodule.am                      |   8 +
>  schedutils/coresched.1.adoc                   | 138 +++++++
>  schedutils/coresched.c                        | 358 ++++++++++++++++++
>  tests/commands.sh                             |   1 +
>  .../coresched-copy-from-child-to-parent       |   1 +
>  ...coresched-copy-from-parent-to-nested-child |   1 +
>  .../schedutils/coresched-get-cookie-own-pid   |   1 +
>  .../coresched-get-cookie-parent-pid           |   1 +
>  .../coresched-new-child-with-new-cookie       |   1 +
>  .../coresched-set-cookie-parent-pid.err       |   1 +
>  .../expected/schedutils/set-cookie-parent-pid |   1 +
>  tests/ts/schedutils/coresched                 |  83 ++++
>  17 files changed, 620 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
>  create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
>  create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
>  create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
>  create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
>  create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
>  create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
>  create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
>  create mode 100755 tests/ts/schedutils/coresched
> 
> diff --git a/.gitignore b/.gitignore
> index 6ecbfa7fe..316f3cdcc 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -94,6 +94,7 @@ ylwrap
>  /colcrt
>  /colrm
>  /column
> +/coresched
>  /ctrlaltdel
>  /delpart
>  /dmesg
> diff --git a/bash-completion/coresched b/bash-completion/coresched
> new file mode 100644
> index 000000000..e69de29bb
> diff --git a/configure.ac b/configure.ac
> index ab7c98636..3a189a075 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
>  AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
>  
>  # build_schedutils= is just configure-only variable to control
> -# ionice, taskset and chrt
> +# ionice, taskset, coresched and chrt
>  AC_ARG_ENABLE([schedutils],
> -  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
> +  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
>    [], [UL_DEFAULT_ENABLE([schedutils], [check])]
>  )
>  
> @@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
>  AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
>  
>  
> +UL_ENABLE_ALIAS([coresched], [schedutils])
> +UL_BUILD_INIT([coresched])
> +UL_REQUIRES_SYSCALL_CHECK([coresched],
> +	[UL_CHECK_SYSCALL([prctl])],
> +	[prctl])
> +AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
> +
> +
>  have_schedsetter=no
>  AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
>        [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
> diff --git a/meson.build b/meson.build
> index 9600ce49f..a3a648753 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -3111,13 +3111,23 @@ exe4 = executable(
>    install : opt,
>    build_by_default : opt)
>  
> +exe5 = executable(
> +  'coresched',
> +  'schedutils/coresched.c',
> +  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, exe2, exe3, exe4]
> +  exes += [exe, exe2, exe3, exe4, exe5]
>    manadocs += ['schedutils/chrt.1.adoc',
>                 'schedutils/ionice.1.adoc',
>                 'schedutils/taskset.1.adoc',
> -	       'schedutils/uclampset.1.adoc']
> -  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
> +               'schedutils/uclampset.1.adoc',
> +               'schedutils/coresched.1.adoc']
> +  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
>  endif
>  
>  ############################################################
> diff --git a/meson_options.txt b/meson_options.txt
> index 7b8cf3f35..3405c1b73 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
>  option('build-setterm', type : 'feature',
>         description : 'build setterm')
>  option('build-schedutils', type : 'feature',
> -       description : 'build chrt, ionice, taskset')
> +       description : 'build chrt, ionice, taskset, coresched')
>  option('build-wall', type : 'feature',
>         description : 'build wall')
>  option('build-write', type : 'feature',
> diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
> index 1040da85f..0cb655401 100644
> --- a/schedutils/Makemodule.am
> +++ b/schedutils/Makemodule.am
> @@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
>  uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
>  uclampset_LDADD = $(LDADD) libcommon.la
>  endif
> +
> +if BUILD_CORESCHED
> +usrbin_exec_PROGRAMS += coresched
> +MANPAGES += schedutils/coresched.1
> +dist_noinst_DATA += schedutils/coresched.1.adoc
> +coresched_SOURCES = schedutils/coresched.c
> +coresched_LDADD = $(LDADD) libcommon.la
> +endif
> diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
> new file mode 100644
> index 000000000..00f2603e0
> --- /dev/null
> +++ b/schedutils/coresched.1.adoc
> @@ -0,0 +1,138 @@
> +//po4a: entry man manual
> +////
> +coresched(1) manpage
> +////
> += coresched(1)
> +:doctype: manpage
> +:man manual: User Commands
> +:man source: util-linux {release-version}
> +:page-layout: base
> +:command: coresched
> +:colon: :
> +:copyright: ©
> +
> +== NAME
> +
> +coresched - manage core scheduling cookies for tasks
> +
> +== SYNOPSIS
> +
> +*{command}* [*get*] [*-s* _pid_]
> +
> +*{command}* *new* [*-t* _type_] *-d* _pid_
> +
> +*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
> +
> +*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
> +
> +*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
> +
> +== DESCRIPTION
> +The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
> +
> +Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
> +This is done by assigning a cookie to each task.
> +Only tasks have the same cookie are allowed to be scheduled on the same physical core.
> +
> +It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
> +
> +== FUNCTIONS
> +*get*::
> +Retrieve the core scheduling cookie of the PID specified in *-s*.
> +If *-s* is omitted, it will get the cookie of the current *{command}* process.
> +
> +*new*::
> +Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
> +
> +*copy*::
> +Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
> +If *-s* is omitted, it will get the cookie of the current *{command}* process.
> +
> +If no function is specified, it will run the *get* function.
> +
> +== OPTIONS
> +*-s*, *--source* _PID_::
> +Which _PID_ to get the cookie from.
> +If this option is omitted, it will get the cookie from the current *{command}* process.
> +
> +*-d*, *--dest* _PID_::
> +Which _PID_ to modify the cookie of.
> +
> +*-t*, *--dest-type* _TYPE_::
> +The type of the PID whose cookie will be modified. This can be one of three values:
> +- *pid*, or process ID
> +- *tgid*, or thread group ID (default value)
> +- *pgid*, or process group ID
> +
> +*-v*, *--verbose*::
> +Show extra information when modifying cookies of tasks.
> +
> +*-h*, *--help*::
> +Display help text and exit.
> +
> +*-V*, *--version*::
> +Print version and exit.
> +
> +== EXAMPLES
> +Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
> +*{command} get*
> +
> +Get the core scheduling cookie of a task with PID _123_{colon}::
> +*{command} get -s* _123_
> +
> +Give a task with PID _123_ a new core scheduling cookie{colon}::
> +*{command} new -d* _123_
> +
> +Spawn a new task with a new core scheduling cookie{colon}::
> +*{command} new* \-- _command_ [_argument_...]
> +
> +Copy the cookie from the current {command} process another task with pid _456_{colon}::
> +*{command} copy -d* _456_
> +
> +Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
> +*{command} copy -s* _123_ *-d* _456_
> +
> +Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
> +*{command} copy -s* _123_ \-- _command_ [_argument_...]
> +
> +Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
> +*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
> +
> +== PERMISSIONS
> +When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
> +See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
> +
> +== RETURN VALUE
> +On success, *{command}* returns 0.
> +If *{command}* fails, it will print an error and return 1.
> +
> +If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
> +
> +== NOTES
> +*{command}* requires core scheduling support in the kernel.
> +This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
> +
> +== AUTHORS
> +mailto:thijs@raymakers.nl[Thijs Raymakers]
> +
> +== COPYRIGHT
> +
> +Copyright {copyright} 2024 Thijs Raymakers. This is free software licensed under the EUPL.
> +
> +== SEE ALSO
> +*chrt*(1),
> +*nice*(1),
> +*renice*(1),
> +*taskset*(1),
> +*ptrace*(2),
> +*sched*(7)
> +
> +The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
> +
> +include::man-common/bugreports.adoc[]
> +
> +include::man-common/footer.adoc[]
> +
> +ifdef::translation[]
> +include::man-common/translation.adoc[]
> +endif::[]
> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> new file mode 100644
> index 000000000..7e3cc46d2
> --- /dev/null
> +++ b/schedutils/coresched.c
> @@ -0,0 +1,358 @@
> +/**
> + * SPDX-License-Identifier: EUPL-1.2
> + *
> + * coresched.c - manage core scheduling cookies for tasks
> + *
> + * Copyright (C) 2024 Thijs Raymakers
> + * Licensed under the EUPL v1.2
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <unistd.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "strutils.h"
> +
> +// These definitions might not be defined in the header files, even if the
> +// prctl interface in the kernel accepts them as valid.
> +#ifndef PR_SCHED_CORE
> +	#define PR_SCHED_CORE 62
> +#endif
> +#ifndef PR_SCHED_CORE_GET
> +	#define PR_SCHED_CORE_GET 0
> +#endif
> +#ifndef PR_SCHED_CORE_CREATE
> +	#define PR_SCHED_CORE_CREATE 1
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_TO
> +	#define PR_SCHED_CORE_SHARE_TO 2
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_FROM
> +	#define PR_SCHED_CORE_SHARE_FROM 3
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +	#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int sched_core_scope;
> +typedef unsigned long sched_core_cookie;
> +typedef enum {
> +	SCHED_CORE_CMD_GET,
> +	SCHED_CORE_CMD_NEW,
> +	SCHED_CORE_CMD_COPY,
> +} sched_core_cmd;
> +
> +struct args {
> +	pid_t src;
> +	pid_t dest;
> +	sched_core_scope type;
> +	sched_core_cmd cmd;
> +	int exec_argv_offset;
> +};
> +
> +static bool sched_core_verbose = false;
> +
> +static void __attribute__((__noreturn__)) usage(void)
> +{
> +	fputs(USAGE_HEADER, stdout);
> +	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> +
> +	fputs(USAGE_FUNCTIONS, stdout);
> +	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
> +		stdout);
> +	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
> +		  "                            PID or execute a program with a new cookie"),
> +		stdout);
> +	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
> +		  "                            to another PID, or execute a program with that\n"
> +		  "                            copied cookie"),
> +		stdout);
> +
> +	fputs(USAGE_OPTIONS, stdout);
> +	fprintf(stdout,
> +		_(" -s, --source <PID>       which PID to get the cookie from\n"
> +		  "                            If omitted, it is the PID of %s itself\n"),
> +		program_invocation_short_name);
> +	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
> +		stdout);
> +	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
> +		  "                            when a new core scheduling cookie is created.\n"
> +		  "                            Can be one of the following: pid, tgid or pgid.\n"
> +		  "                            The default is tgid."),
> +		stdout);
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_(" -v, --verbose      verbose"), stdout);
> +	fprintf(stdout, USAGE_HELP_OPTIONS(20));
> +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> +	exit(EXIT_SUCCESS);
> +}
> +
> +#define bad_usage(FMT...)                 \
> +	do {                              \
> +		warnx(FMT);               \
> +		errtryhelp(EXIT_FAILURE); \
> +	} while (0)
> +
> +static sched_core_cookie core_sched_get_cookie(pid_t pid)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
> +		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
> +	return cookie;
> +}
> +
> +static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
> +		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
> +}
> +
> +static void core_sched_pull_cookie(pid_t from)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +		  PR_SCHED_CORE_SCOPE_THREAD, 0))
> +		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
> +}
> +
> +static void core_sched_push_cookie(pid_t to, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
> +		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
> +}
> +
> +static void core_sched_copy_cookie(pid_t from, pid_t to,
> +				   sched_core_scope to_type)
> +{
> +	core_sched_pull_cookie(from);
> +	core_sched_push_cookie(to, to_type);
> +
> +	if (sched_core_verbose) {
> +		sched_core_cookie before = core_sched_get_cookie(from);
> +		warnx(_("copied cookie 0x%lx from PID %d to PID %d"), before,
> +		      from, to);
> +	}
> +}
> +
> +static void core_sched_get_and_print_cookie(pid_t pid)
> +{
> +	if (sched_core_verbose) {
> +		sched_core_cookie after = core_sched_get_cookie(pid);
> +		warnx(_("set cookie of PID %d to 0x%lx"), pid, after);
> +	}
> +}
> +
> +static void core_sched_exec_with_cookie(struct args *args, char **argv)
> +{
> +	// Move the argument list to the first argument of the program
> +	argv = &argv[args->exec_argv_offset];
> +
> +	// If a source PID is provided, try to copy the cookie from
> +	// that PID. Otherwise, create a brand new cookie with the
> +	// provided type.
> +	if (args->src) {
> +		core_sched_pull_cookie(args->src);
> +		core_sched_get_and_print_cookie(args->src);
> +	} else {
> +		pid_t pid = getpid();
> +		core_sched_create_cookie(pid, args->type);
> +		core_sched_get_and_print_cookie(pid);
> +	}
> +
> +	if (execvp(argv[0], argv))
> +		errexec(argv[0]);
> +}
> +
> +// If PR_SCHED_CORE is not recognized, or not supported on this system,
> +// then prctl will set errno to EINVAL. Assuming all other operands of
> +// prctl are valid, we can use errno==EINVAL as a check to see whether
> +// core scheduling is available on this system.
> +static bool is_core_sched_supported(void)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
> +		if (errno == EINVAL)
> +			return false;
> +
> +	return true;
> +}
> +
> +static sched_core_scope parse_core_sched_type(char *str)
> +{
> +	if (!strcmp(str, "pid"))
> +		return PR_SCHED_CORE_SCOPE_THREAD;
> +	else if (!strcmp(str, "tgid"))
> +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +	else if (!strcmp(str, "pgid"))
> +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
> +
> +	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
> +		  str);
> +}
> +
> +static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
> +{
> +	int c;
> +
> +	static const struct option longopts[] = {
> +		{ "source", required_argument, NULL, 's' },
> +		{ "dest", required_argument, NULL, 'd' },
> +		{ "dest-type", required_argument, NULL, 't' },
> +		{ "verbose", no_argument, NULL, 'v' },
> +		{ "version", no_argument, NULL, 'V' },
> +		{ "help", no_argument, NULL, 'h' },
> +		{ NULL, 0, NULL, 0 }
> +	};
> +
> +	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
> +		switch (c) {
> +		case 's':
> +			args->src = strtopid_or_err(
> +				optarg,
> +				_("Failed to parse PID for -s/--source"));
> +			break;
> +		case 'd':
> +			args->dest = strtopid_or_err(
> +				optarg, _("Failed to parse PID for -d/--dest"));
> +			break;
> +		case 't':
> +			args->type = parse_core_sched_type(optarg);
> +			break;
> +		case 'v':
> +			sched_core_verbose = true;
> +			break;
> +		case 'V':
> +			print_version(EXIT_SUCCESS);
> +		case 'h':
> +			usage();
> +		default:
> +			errtryhelp(EXIT_FAILURE);
> +		}
> +
> +	if (argc <= optind) {
> +		args->cmd = SCHED_CORE_CMD_GET;
> +	} else {
> +		if (!strcmp(argv[optind], "get"))
> +			args->cmd = SCHED_CORE_CMD_GET;
> +		else if (!strcmp(argv[optind], "new"))
> +			args->cmd = SCHED_CORE_CMD_NEW;
> +		else if (!strcmp(argv[optind], "copy"))
> +			args->cmd = SCHED_CORE_CMD_COPY;
> +		else
> +			bad_usage(_("Unknown function"));
> +
> +		// Since we parsed an extra "option" outside of getopt_long, we have to
> +		// increment optind manually.
> +		++optind;
> +	}
> +
> +	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
> +		bad_usage(_("get does not accept the --dest option"));
> +
> +	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
> +		bad_usage(_("new does not accept the --source option"));
> +
> +	// If the -s/--source option is not specified, it defaults to the PID
> +	// of the current coresched process
> +	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
> +		args->src = getpid();
> +
> +	// More arguments have been passed, which means that the user wants to run
> +	// another program with a core scheduling cookie.
> +	if (argc > optind) {
> +		switch (args->cmd) {
> +		case SCHED_CORE_CMD_GET:
> +			bad_usage(_("bad usage of the get function"));
> +			break;
> +		case SCHED_CORE_CMD_NEW:
> +			if (args->dest)
> +				bad_usage(_(
> +					"new requires either a -d/--dest or a command"));
> +			else
> +				args->exec_argv_offset = optind;
> +			break;
> +		case SCHED_CORE_CMD_COPY:
> +			if (args->dest)
> +				bad_usage(_(
> +					"copy requires either a -d/--dest or a command"));
> +			else
> +				args->exec_argv_offset = optind;
> +			break;
> +		}
> +	} else {
> +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
> +			bad_usage(_(
> +				"new requires either a -d/--dest or a command"));
> +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
> +			bad_usage(_(
> +				"copy requires either a -d/--dest or a command"));
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
> +
> +	setlocale(LC_ALL, "");
> +	bindtextdomain(PACKAGE, LOCALEDIR);
> +	textdomain(PACKAGE);
> +	close_stdout_atexit();
> +
> +	parse_and_verify_arguments(argc, argv, &args);
> +
> +	if (!is_core_sched_supported())
> +		errx(EXIT_FAILURE,
> +		     _("No support for core scheduling found. Does your kernel"
> +		       "support CONFIG_SCHED_CORE?"));
> +
> +	sched_core_cookie cookie;
> +
> +	switch (args.cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		cookie = core_sched_get_cookie(args.src);
> +		printf(_("cookie of pid %d is 0x%lx\n"), args.src, cookie);
> +		break;
> +	case SCHED_CORE_CMD_NEW:
> +		if (args.exec_argv_offset) {
> +			core_sched_exec_with_cookie(&args, argv);
> +		} else {
> +			core_sched_create_cookie(args.dest, args.type);
> +			core_sched_get_and_print_cookie(args.dest);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		if (args.exec_argv_offset)
> +			core_sched_exec_with_cookie(&args, argv);
> +		else
> +			core_sched_copy_cookie(args.src, args.dest, args.type);
> +		break;
> +	default:
> +		usage();
> +	}
> +}
> diff --git a/tests/commands.sh b/tests/commands.sh
> index 5674c5ff0..9eef92ccb 100644
> --- a/tests/commands.sh
> +++ b/tests/commands.sh
> @@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
>  TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
>  TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
>  TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
> +TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
>  TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
>  TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
>  TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
> diff --git a/tests/expected/schedutils/coresched-copy-from-child-to-parent b/tests/expected/schedutils/coresched-copy-from-child-to-parent
> new file mode 100644
> index 000000000..5b9c40052
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-copy-from-child-to-parent
> @@ -0,0 +1 @@
> +DIFFERENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
> new file mode 100644
> index 000000000..ecfc41142
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
> @@ -0,0 +1 @@
> +SAME_COOKIE
> diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
> new file mode 100644
> index 000000000..84f182cbe
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
> @@ -0,0 +1 @@
> +cookie of pid OWN_PID is PARENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
> new file mode 100644
> index 000000000..e183e0402
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
> @@ -0,0 +1 @@
> +cookie of pid PARENT_PID is PARENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
> new file mode 100644
> index 000000000..5b9c40052
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
> @@ -0,0 +1 @@
> +DIFFERENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
> new file mode 100644
> index 000000000..e7318ffc2
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
> @@ -0,0 +1 @@
> +coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
> diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
> new file mode 100644
> index 000000000..e7318ffc2
> --- /dev/null
> +++ b/tests/expected/schedutils/set-cookie-parent-pid
> @@ -0,0 +1 @@
> +coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
> diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
> new file mode 100755
> index 000000000..e34fa319f
> --- /dev/null
> +++ b/tests/ts/schedutils/coresched
> @@ -0,0 +1,83 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: EUPL-1.2
> +#
> +# This file is part of util-linux
> +#
> +# Copyright (C) 2024 Thijs Raymakers
> +# Licensed under the EUPL v1.2
> +
> +TS_TOPDIR="${0%/*}/../.."
> +TS_DESC="coresched"
> +
> +. "$TS_TOPDIR"/functions.sh
> +ts_init "$*"
> +
> +ts_check_test_command "$TS_CMD_CORESCHED"
> +ts_check_prog "tee"
> +ts_check_prog "sed"
> +
> +# If there is no kernel support, skip the test suite
> +CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
> +if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
> +  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
> +fi
> +
> +# The output of coresched contains PIDs and core scheduling cookies, both of which should be
> +# assumed to be random values as we have no control over them. The tests replace these values
> +# with sed before writing them to the output file, so it can match the expected output file.
> +# - The PID of this bash script is replaced with the placeholder `OWN_PID`
> +# - The core scheduling cookie of this bash script is replaced by `COOKIE`
> +# - Any other cookie is replaced by `DIFFERENT_COOKIE`
> +# The behavior of coresched does not depend on the exact values of these cookies, so using
> +# placeholder values does not change the behavior tests.
> +ts_init_subtest "set-cookie-parent-pid"
> +CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
> +  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
> +  | sed "s/$$/PARENT_PID/g")
> +CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
> +if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
> +  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
> +fi
> +CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
> +  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
> +echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
> +ts_finalize_subtest
> +
> +ts_init_subtest "get-cookie-parent-pid"
> +$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
> +  | sed -e "s/$$/PARENT_PID/g" \
> +        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "get-cookie-own-pid"
> +$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
> +  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
> +        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "new-child-with-new-cookie"
> +$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
> +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
> +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
> +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "copy-from-parent-to-nested-child"
> +$TS_CMD_CORESCHED new -- /bin/bash -c \
> +  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
> +2>> "$TS_ERRLOG" \
> +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
> +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
> +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "copy-from-child-to-parent"
> +$TS_CMD_CORESCHED new -- /bin/bash -c \
> +  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
> +$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
> +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
> +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
> +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_finalize
> -- 
> 2.44.0
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v8] coresched: Manage core scheduling cookies for tasks
  2024-04-10 22:15                                   ` Phil Auld
@ 2024-04-11 11:02                                     ` Thijs Raymakers
  2024-04-11 13:46                                       ` Phil Auld
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-11 11:02 UTC (permalink / raw)
  To: pauld; +Cc: kzak, thomas, util-linux

Co-authored-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>

---

Hi Phil,

That might have been a bit too drastic from my side. I just wanted to
make sure that I didn't accidentally attribute something to you that you
didn't fully support, since the arguments of this version differ
significantly from what we've previously discussed. I've put you back.

Thijs

Interdiff against v7:
  diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
  index 00f2603e0..aa228b84f 100644
  --- a/schedutils/coresched.1.adoc
  +++ b/schedutils/coresched.1.adoc
  @@ -113,11 +113,12 @@ If a _command_ is being executed, the return value of *{command}* will be the re
   This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
   
   == AUTHORS
  -mailto:thijs@raymakers.nl[Thijs Raymakers]
  +mailto:thijs@raymakers.nl[Thijs Raymakers],
  +mailto:pauld@redhat.com[Phil Auld]
   
   == COPYRIGHT
   
  -Copyright {copyright} 2024 Thijs Raymakers. This is free software licensed under the EUPL.
  +Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
   
   == SEE ALSO
   *chrt*(1),
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index 7e3cc46d2..bb97cc020 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -3,7 +3,7 @@
    *
    * coresched.c - manage core scheduling cookies for tasks
    *
  - * Copyright (C) 2024 Thijs Raymakers
  + * Copyright (C) 2024 Thijs Raymakers, Phil Auld
    * Licensed under the EUPL v1.2
    */
   

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 139 +++++++
 schedutils/coresched.c                        | 358 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-copy-from-child-to-parent       |   1 +
 ...coresched-copy-from-parent-to-nested-child |   1 +
 .../schedutils/coresched-get-cookie-own-pid   |   1 +
 .../coresched-get-cookie-parent-pid           |   1 +
 .../coresched-new-child-with-new-cookie       |   1 +
 .../coresched-set-cookie-parent-pid.err       |   1 +
 .../expected/schedutils/set-cookie-parent-pid |   1 +
 tests/ts/schedutils/coresched                 |  83 ++++
 17 files changed, 621 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
 create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
 create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
 create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index ab7c98636..3a189a075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 9600ce49f..a3a648753 100644
--- a/meson.build
+++ b/meson.build
@@ -3111,13 +3111,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 7b8cf3f35..3405c1b73 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..aa228b84f
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,139 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [*get*] [*-s* _pid_]
+
+*{command}* *new* [*-t* _type_] *-d* _pid_
+
+*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*get*::
+Retrieve the core scheduling cookie of the PID specified in *-s*.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+*new*::
+Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
+
+*copy*::
+Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+If no function is specified, it will run the *get* function.
+
+== OPTIONS
+*-s*, *--source* _PID_::
+Which _PID_ to get the cookie from.
+If this option is omitted, it will get the cookie from the current *{command}* process.
+
+*-d*, *--dest* _PID_::
+Which _PID_ to modify the cookie of.
+
+*-t*, *--dest-type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
+*{command} get*
+
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} get -s* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} new -d* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} new* \-- _command_ [_argument_...]
+
+Copy the cookie from the current {command} process another task with pid _456_{colon}::
+*{command} copy -d* _456_
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} copy -s* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} copy -s* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
+
+== PERMISSIONS
+When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+
+If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
+
+== NOTES
+*{command}* requires core scheduling support in the kernel.
+This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers],
+mailto:pauld@redhat.com[Phil Auld]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..bb97cc020
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,358 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers, Phil Auld
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+	#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+	#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+	#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+	#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+	#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+	#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t src;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
+		stdout);
+	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
+		  "                            PID or execute a program with a new cookie"),
+		stdout);
+	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
+		  "                            to another PID, or execute a program with that\n"
+		  "                            copied cookie"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fprintf(stdout,
+		_(" -s, --source <PID>       which PID to get the cookie from\n"
+		  "                            If omitted, it is the PID of %s itself\n"),
+		program_invocation_short_name);
+	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
+		stdout);
+	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
+		  "                            when a new core scheduling cookie is created.\n"
+		  "                            Can be one of the following: pid, tgid or pgid.\n"
+		  "                            The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout, USAGE_HELP_OPTIONS(20));
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...)                 \
+	do {                              \
+		warnx(FMT);               \
+		errtryhelp(EXIT_FAILURE); \
+	} while (0)
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0))
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		warnx(_("copied cookie 0x%lx from PID %d to PID %d"), before,
+		      from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		warnx(_("set cookie of PID %d to 0x%lx"), pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->src) {
+		core_sched_pull_cookie(args->src);
+		core_sched_get_and_print_cookie(args->src);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv))
+		errexec(argv[0]);
+}
+
+// If PR_SCHED_CORE is not recognized, or not supported on this system,
+// then prctl will set errno to EINVAL. Assuming all other operands of
+// prctl are valid, we can use errno==EINVAL as a check to see whether
+// core scheduling is available on this system.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		if (errno == EINVAL)
+			return false;
+
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strcmp(str, "pid"))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strcmp(str, "tgid"))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strcmp(str, "pgid"))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "source", required_argument, NULL, 's' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "dest-type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
+		switch (c) {
+		case 's':
+			args->src = strtopid_or_err(
+				optarg,
+				_("Failed to parse PID for -s/--source"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+
+	if (argc <= optind) {
+		args->cmd = SCHED_CORE_CMD_GET;
+	} else {
+		if (!strcmp(argv[optind], "get"))
+			args->cmd = SCHED_CORE_CMD_GET;
+		else if (!strcmp(argv[optind], "new"))
+			args->cmd = SCHED_CORE_CMD_NEW;
+		else if (!strcmp(argv[optind], "copy"))
+			args->cmd = SCHED_CORE_CMD_COPY;
+		else
+			bad_usage(_("Unknown function"));
+
+		// Since we parsed an extra "option" outside of getopt_long, we have to
+		// increment optind manually.
+		++optind;
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
+		bad_usage(_("get does not accept the --dest option"));
+
+	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
+		bad_usage(_("new does not accept the --source option"));
+
+	// If the -s/--source option is not specified, it defaults to the PID
+	// of the current coresched process
+	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
+		args->src = getpid();
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("bad usage of the get function"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->dest)
+				bad_usage(_(
+					"new requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest)
+				bad_usage(_(
+					"copy requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		}
+	} else {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
+			bad_usage(_(
+				"new requires either a -d/--dest or a command"));
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
+			bad_usage(_(
+				"copy requires either a -d/--dest or a command"));
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_and_verify_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported())
+		errx(EXIT_FAILURE,
+		     _("No support for core scheduling found. Does your kernel"
+		       "support CONFIG_SCHED_CORE?"));
+
+	sched_core_cookie cookie;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		cookie = core_sched_get_cookie(args.src);
+		printf(_("cookie of pid %d is 0x%lx\n"), args.src, cookie);
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.exec_argv_offset) {
+			core_sched_exec_with_cookie(&args, argv);
+		} else {
+			core_sched_create_cookie(args.dest, args.type);
+			core_sched_get_and_print_cookie(args.dest);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.exec_argv_offset)
+			core_sched_exec_with_cookie(&args, argv);
+		else
+			core_sched_copy_cookie(args.src, args.dest, args.type);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-copy-from-child-to-parent b/tests/expected/schedutils/coresched-copy-from-child-to-parent
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-child-to-parent
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
new file mode 100644
index 000000000..ecfc41142
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
@@ -0,0 +1 @@
+SAME_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
new file mode 100644
index 000000000..84f182cbe
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
@@ -0,0 +1 @@
+cookie of pid OWN_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
new file mode 100644
index 000000000..e183e0402
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
@@ -0,0 +1 @@
+cookie of pid PARENT_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/set-cookie-parent-pid
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..e34fa319f
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,83 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+ts_check_prog "tee"
+ts_check_prog "sed"
+
+# If there is no kernel support, skip the test suite
+CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
+if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
+  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
+fi
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "set-cookie-parent-pid"
+CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
+  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
+  | sed "s/$$/PARENT_PID/g")
+CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
+  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
+fi
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
+  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-parent-pid"
+$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
+  | sed -e "s/$$/PARENT_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-own-pid"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "new-child-with-new-cookie"
+$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-parent-to-nested-child"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
+2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-child-to-parent"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v8] coresched: Manage core scheduling cookies for tasks
  2024-04-11 11:02                                     ` [PATCH v8] " Thijs Raymakers
@ 2024-04-11 13:46                                       ` Phil Auld
  2024-04-17 10:31                                         ` Karel Zak
  0 siblings, 1 reply; 42+ messages in thread
From: Phil Auld @ 2024-04-11 13:46 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, thomas, util-linux

On Thu, Apr 11, 2024 at 01:02:49PM +0200 Thijs Raymakers wrote:
> Co-authored-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
>

Hi Thijs.

> ---
> 
> Hi Phil,
> 
> That might have been a bit too drastic from my side. I just wanted to
> make sure that I didn't accidentally attribute something to you that you
> didn't fully support, since the arguments of this version differ
> significantly from what we've previously discussed. I've put you back.

Thanks!  I support whatever we can get in that covers the use cases. If we ask
three engineers about commandline options we'll likely get 5 answers :)

What we have here looks good and works well.


A couple of minor man page comments...

In the man page let's change this sentence:

"Core scheduling allows you to define groups of tasks that are allowed to share a physical core."

to

"Core scheduling permits the definition of groups ..." 


That takes out the "you" and reduces the number of times we say "allow" in that
paragraph.

Similarly in the PERMISSIONS section:

"When retrieving or modifying the core scheduling cookie of a process, you need to have PTRACE_MODE_READ_REALCREDS ptrace access to that process."

should be something like:

"Retrieving or modifying the core scheduling cookie of a process requires PTRACE_MODE_READ_REALCREDS ptrace access to that process."



Cheers,
Phil

> 
> Thijs
> 
> Interdiff against v7:
>   diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
>   index 00f2603e0..aa228b84f 100644
>   --- a/schedutils/coresched.1.adoc
>   +++ b/schedutils/coresched.1.adoc
>   @@ -113,11 +113,12 @@ If a _command_ is being executed, the return value of *{command}* will be the re
>    This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
>    
>    == AUTHORS
>   -mailto:thijs@raymakers.nl[Thijs Raymakers]
>   +mailto:thijs@raymakers.nl[Thijs Raymakers],
>   +mailto:pauld@redhat.com[Phil Auld]
>    
>    == COPYRIGHT
>    
>   -Copyright {copyright} 2024 Thijs Raymakers. This is free software licensed under the EUPL.
>   +Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
>    
>    == SEE ALSO
>    *chrt*(1),
>   diff --git a/schedutils/coresched.c b/schedutils/coresched.c
>   index 7e3cc46d2..bb97cc020 100644
>   --- a/schedutils/coresched.c
>   +++ b/schedutils/coresched.c
>   @@ -3,7 +3,7 @@
>     *
>     * coresched.c - manage core scheduling cookies for tasks
>     *
>   - * Copyright (C) 2024 Thijs Raymakers
>   + * Copyright (C) 2024 Thijs Raymakers, Phil Auld
>     * Licensed under the EUPL v1.2
>     */
>    
> 
>  .gitignore                                    |   1 +
>  bash-completion/coresched                     |   0
>  configure.ac                                  |  12 +-
>  meson.build                                   |  16 +-
>  meson_options.txt                             |   2 +-
>  schedutils/Makemodule.am                      |   8 +
>  schedutils/coresched.1.adoc                   | 139 +++++++
>  schedutils/coresched.c                        | 358 ++++++++++++++++++
>  tests/commands.sh                             |   1 +
>  .../coresched-copy-from-child-to-parent       |   1 +
>  ...coresched-copy-from-parent-to-nested-child |   1 +
>  .../schedutils/coresched-get-cookie-own-pid   |   1 +
>  .../coresched-get-cookie-parent-pid           |   1 +
>  .../coresched-new-child-with-new-cookie       |   1 +
>  .../coresched-set-cookie-parent-pid.err       |   1 +
>  .../expected/schedutils/set-cookie-parent-pid |   1 +
>  tests/ts/schedutils/coresched                 |  83 ++++
>  17 files changed, 621 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
>  create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
>  create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
>  create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
>  create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
>  create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
>  create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
>  create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
>  create mode 100755 tests/ts/schedutils/coresched
> 
> diff --git a/.gitignore b/.gitignore
> index 6ecbfa7fe..316f3cdcc 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -94,6 +94,7 @@ ylwrap
>  /colcrt
>  /colrm
>  /column
> +/coresched
>  /ctrlaltdel
>  /delpart
>  /dmesg
> diff --git a/bash-completion/coresched b/bash-completion/coresched
> new file mode 100644
> index 000000000..e69de29bb
> diff --git a/configure.ac b/configure.ac
> index ab7c98636..3a189a075 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -2500,9 +2500,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
>  AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
>  
>  # build_schedutils= is just configure-only variable to control
> -# ionice, taskset and chrt
> +# ionice, taskset, coresched and chrt
>  AC_ARG_ENABLE([schedutils],
> -  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
> +  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
>    [], [UL_DEFAULT_ENABLE([schedutils], [check])]
>  )
>  
> @@ -2545,6 +2545,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
>  AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
>  
>  
> +UL_ENABLE_ALIAS([coresched], [schedutils])
> +UL_BUILD_INIT([coresched])
> +UL_REQUIRES_SYSCALL_CHECK([coresched],
> +	[UL_CHECK_SYSCALL([prctl])],
> +	[prctl])
> +AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
> +
> +
>  have_schedsetter=no
>  AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
>        [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
> diff --git a/meson.build b/meson.build
> index 9600ce49f..a3a648753 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -3111,13 +3111,23 @@ exe4 = executable(
>    install : opt,
>    build_by_default : opt)
>  
> +exe5 = executable(
> +  'coresched',
> +  'schedutils/coresched.c',
> +  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, exe2, exe3, exe4]
> +  exes += [exe, exe2, exe3, exe4, exe5]
>    manadocs += ['schedutils/chrt.1.adoc',
>                 'schedutils/ionice.1.adoc',
>                 'schedutils/taskset.1.adoc',
> -	       'schedutils/uclampset.1.adoc']
> -  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
> +               'schedutils/uclampset.1.adoc',
> +               'schedutils/coresched.1.adoc']
> +  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
>  endif
>  
>  ############################################################
> diff --git a/meson_options.txt b/meson_options.txt
> index 7b8cf3f35..3405c1b73 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -162,7 +162,7 @@ option('build-pipesz', type : 'feature',
>  option('build-setterm', type : 'feature',
>         description : 'build setterm')
>  option('build-schedutils', type : 'feature',
> -       description : 'build chrt, ionice, taskset')
> +       description : 'build chrt, ionice, taskset, coresched')
>  option('build-wall', type : 'feature',
>         description : 'build wall')
>  option('build-write', type : 'feature',
> diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
> index 1040da85f..0cb655401 100644
> --- a/schedutils/Makemodule.am
> +++ b/schedutils/Makemodule.am
> @@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
>  uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
>  uclampset_LDADD = $(LDADD) libcommon.la
>  endif
> +
> +if BUILD_CORESCHED
> +usrbin_exec_PROGRAMS += coresched
> +MANPAGES += schedutils/coresched.1
> +dist_noinst_DATA += schedutils/coresched.1.adoc
> +coresched_SOURCES = schedutils/coresched.c
> +coresched_LDADD = $(LDADD) libcommon.la
> +endif
> diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
> new file mode 100644
> index 000000000..aa228b84f
> --- /dev/null
> +++ b/schedutils/coresched.1.adoc
> @@ -0,0 +1,139 @@
> +//po4a: entry man manual
> +////
> +coresched(1) manpage
> +////
> += coresched(1)
> +:doctype: manpage
> +:man manual: User Commands
> +:man source: util-linux {release-version}
> +:page-layout: base
> +:command: coresched
> +:colon: :
> +:copyright: ©
> +
> +== NAME
> +
> +coresched - manage core scheduling cookies for tasks
> +
> +== SYNOPSIS
> +
> +*{command}* [*get*] [*-s* _pid_]
> +
> +*{command}* *new* [*-t* _type_] *-d* _pid_
> +
> +*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
> +
> +*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
> +
> +*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
> +
> +== DESCRIPTION
> +The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
> +
> +Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
> +This is done by assigning a cookie to each task.
> +Only tasks have the same cookie are allowed to be scheduled on the same physical core.
> +
> +It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
> +
> +== FUNCTIONS
> +*get*::
> +Retrieve the core scheduling cookie of the PID specified in *-s*.
> +If *-s* is omitted, it will get the cookie of the current *{command}* process.
> +
> +*new*::
> +Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
> +
> +*copy*::
> +Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
> +If *-s* is omitted, it will get the cookie of the current *{command}* process.
> +
> +If no function is specified, it will run the *get* function.
> +
> +== OPTIONS
> +*-s*, *--source* _PID_::
> +Which _PID_ to get the cookie from.
> +If this option is omitted, it will get the cookie from the current *{command}* process.
> +
> +*-d*, *--dest* _PID_::
> +Which _PID_ to modify the cookie of.
> +
> +*-t*, *--dest-type* _TYPE_::
> +The type of the PID whose cookie will be modified. This can be one of three values:
> +- *pid*, or process ID
> +- *tgid*, or thread group ID (default value)
> +- *pgid*, or process group ID
> +
> +*-v*, *--verbose*::
> +Show extra information when modifying cookies of tasks.
> +
> +*-h*, *--help*::
> +Display help text and exit.
> +
> +*-V*, *--version*::
> +Print version and exit.
> +
> +== EXAMPLES
> +Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
> +*{command} get*
> +
> +Get the core scheduling cookie of a task with PID _123_{colon}::
> +*{command} get -s* _123_
> +
> +Give a task with PID _123_ a new core scheduling cookie{colon}::
> +*{command} new -d* _123_
> +
> +Spawn a new task with a new core scheduling cookie{colon}::
> +*{command} new* \-- _command_ [_argument_...]
> +
> +Copy the cookie from the current {command} process another task with pid _456_{colon}::
> +*{command} copy -d* _456_
> +
> +Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
> +*{command} copy -s* _123_ *-d* _456_
> +
> +Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
> +*{command} copy -s* _123_ \-- _command_ [_argument_...]
> +
> +Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
> +*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
> +
> +== PERMISSIONS
> +When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
> +See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
> +
> +== RETURN VALUE
> +On success, *{command}* returns 0.
> +If *{command}* fails, it will print an error and return 1.
> +
> +If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
> +
> +== NOTES
> +*{command}* requires core scheduling support in the kernel.
> +This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
> +
> +== AUTHORS
> +mailto:thijs@raymakers.nl[Thijs Raymakers],
> +mailto:pauld@redhat.com[Phil Auld]
> +
> +== COPYRIGHT
> +
> +Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
> +
> +== SEE ALSO
> +*chrt*(1),
> +*nice*(1),
> +*renice*(1),
> +*taskset*(1),
> +*ptrace*(2),
> +*sched*(7)
> +
> +The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
> +
> +include::man-common/bugreports.adoc[]
> +
> +include::man-common/footer.adoc[]
> +
> +ifdef::translation[]
> +include::man-common/translation.adoc[]
> +endif::[]
> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> new file mode 100644
> index 000000000..bb97cc020
> --- /dev/null
> +++ b/schedutils/coresched.c
> @@ -0,0 +1,358 @@
> +/**
> + * SPDX-License-Identifier: EUPL-1.2
> + *
> + * coresched.c - manage core scheduling cookies for tasks
> + *
> + * Copyright (C) 2024 Thijs Raymakers, Phil Auld
> + * Licensed under the EUPL v1.2
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <unistd.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "strutils.h"
> +
> +// These definitions might not be defined in the header files, even if the
> +// prctl interface in the kernel accepts them as valid.
> +#ifndef PR_SCHED_CORE
> +	#define PR_SCHED_CORE 62
> +#endif
> +#ifndef PR_SCHED_CORE_GET
> +	#define PR_SCHED_CORE_GET 0
> +#endif
> +#ifndef PR_SCHED_CORE_CREATE
> +	#define PR_SCHED_CORE_CREATE 1
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_TO
> +	#define PR_SCHED_CORE_SHARE_TO 2
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_FROM
> +	#define PR_SCHED_CORE_SHARE_FROM 3
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +	#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int sched_core_scope;
> +typedef unsigned long sched_core_cookie;
> +typedef enum {
> +	SCHED_CORE_CMD_GET,
> +	SCHED_CORE_CMD_NEW,
> +	SCHED_CORE_CMD_COPY,
> +} sched_core_cmd;
> +
> +struct args {
> +	pid_t src;
> +	pid_t dest;
> +	sched_core_scope type;
> +	sched_core_cmd cmd;
> +	int exec_argv_offset;
> +};
> +
> +static bool sched_core_verbose = false;
> +
> +static void __attribute__((__noreturn__)) usage(void)
> +{
> +	fputs(USAGE_HEADER, stdout);
> +	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
> +		program_invocation_short_name);
> +	fprintf(stdout,
> +		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
> +		program_invocation_short_name);
> +
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
> +
> +	fputs(USAGE_FUNCTIONS, stdout);
> +	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
> +		stdout);
> +	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
> +		  "                            PID or execute a program with a new cookie"),
> +		stdout);
> +	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
> +		  "                            to another PID, or execute a program with that\n"
> +		  "                            copied cookie"),
> +		stdout);
> +
> +	fputs(USAGE_OPTIONS, stdout);
> +	fprintf(stdout,
> +		_(" -s, --source <PID>       which PID to get the cookie from\n"
> +		  "                            If omitted, it is the PID of %s itself\n"),
> +		program_invocation_short_name);
> +	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
> +		stdout);
> +	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
> +		  "                            when a new core scheduling cookie is created.\n"
> +		  "                            Can be one of the following: pid, tgid or pgid.\n"
> +		  "                            The default is tgid."),
> +		stdout);
> +	fputs(USAGE_SEPARATOR, stdout);
> +	fputsln(_(" -v, --verbose      verbose"), stdout);
> +	fprintf(stdout, USAGE_HELP_OPTIONS(20));
> +	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
> +	exit(EXIT_SUCCESS);
> +}
> +
> +#define bad_usage(FMT...)                 \
> +	do {                              \
> +		warnx(FMT);               \
> +		errtryhelp(EXIT_FAILURE); \
> +	} while (0)
> +
> +static sched_core_cookie core_sched_get_cookie(pid_t pid)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
> +		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
> +	return cookie;
> +}
> +
> +static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
> +		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
> +}
> +
> +static void core_sched_pull_cookie(pid_t from)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
> +		  PR_SCHED_CORE_SCOPE_THREAD, 0))
> +		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
> +}
> +
> +static void core_sched_push_cookie(pid_t to, sched_core_scope type)
> +{
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
> +		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
> +}
> +
> +static void core_sched_copy_cookie(pid_t from, pid_t to,
> +				   sched_core_scope to_type)
> +{
> +	core_sched_pull_cookie(from);
> +	core_sched_push_cookie(to, to_type);
> +
> +	if (sched_core_verbose) {
> +		sched_core_cookie before = core_sched_get_cookie(from);
> +		warnx(_("copied cookie 0x%lx from PID %d to PID %d"), before,
> +		      from, to);
> +	}
> +}
> +
> +static void core_sched_get_and_print_cookie(pid_t pid)
> +{
> +	if (sched_core_verbose) {
> +		sched_core_cookie after = core_sched_get_cookie(pid);
> +		warnx(_("set cookie of PID %d to 0x%lx"), pid, after);
> +	}
> +}
> +
> +static void core_sched_exec_with_cookie(struct args *args, char **argv)
> +{
> +	// Move the argument list to the first argument of the program
> +	argv = &argv[args->exec_argv_offset];
> +
> +	// If a source PID is provided, try to copy the cookie from
> +	// that PID. Otherwise, create a brand new cookie with the
> +	// provided type.
> +	if (args->src) {
> +		core_sched_pull_cookie(args->src);
> +		core_sched_get_and_print_cookie(args->src);
> +	} else {
> +		pid_t pid = getpid();
> +		core_sched_create_cookie(pid, args->type);
> +		core_sched_get_and_print_cookie(pid);
> +	}
> +
> +	if (execvp(argv[0], argv))
> +		errexec(argv[0]);
> +}
> +
> +// If PR_SCHED_CORE is not recognized, or not supported on this system,
> +// then prctl will set errno to EINVAL. Assuming all other operands of
> +// prctl are valid, we can use errno==EINVAL as a check to see whether
> +// core scheduling is available on this system.
> +static bool is_core_sched_supported(void)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
> +		if (errno == EINVAL)
> +			return false;
> +
> +	return true;
> +}
> +
> +static sched_core_scope parse_core_sched_type(char *str)
> +{
> +	if (!strcmp(str, "pid"))
> +		return PR_SCHED_CORE_SCOPE_THREAD;
> +	else if (!strcmp(str, "tgid"))
> +		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
> +	else if (!strcmp(str, "pgid"))
> +		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
> +
> +	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
> +		  str);
> +}
> +
> +static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
> +{
> +	int c;
> +
> +	static const struct option longopts[] = {
> +		{ "source", required_argument, NULL, 's' },
> +		{ "dest", required_argument, NULL, 'd' },
> +		{ "dest-type", required_argument, NULL, 't' },
> +		{ "verbose", no_argument, NULL, 'v' },
> +		{ "version", no_argument, NULL, 'V' },
> +		{ "help", no_argument, NULL, 'h' },
> +		{ NULL, 0, NULL, 0 }
> +	};
> +
> +	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
> +		switch (c) {
> +		case 's':
> +			args->src = strtopid_or_err(
> +				optarg,
> +				_("Failed to parse PID for -s/--source"));
> +			break;
> +		case 'd':
> +			args->dest = strtopid_or_err(
> +				optarg, _("Failed to parse PID for -d/--dest"));
> +			break;
> +		case 't':
> +			args->type = parse_core_sched_type(optarg);
> +			break;
> +		case 'v':
> +			sched_core_verbose = true;
> +			break;
> +		case 'V':
> +			print_version(EXIT_SUCCESS);
> +		case 'h':
> +			usage();
> +		default:
> +			errtryhelp(EXIT_FAILURE);
> +		}
> +
> +	if (argc <= optind) {
> +		args->cmd = SCHED_CORE_CMD_GET;
> +	} else {
> +		if (!strcmp(argv[optind], "get"))
> +			args->cmd = SCHED_CORE_CMD_GET;
> +		else if (!strcmp(argv[optind], "new"))
> +			args->cmd = SCHED_CORE_CMD_NEW;
> +		else if (!strcmp(argv[optind], "copy"))
> +			args->cmd = SCHED_CORE_CMD_COPY;
> +		else
> +			bad_usage(_("Unknown function"));
> +
> +		// Since we parsed an extra "option" outside of getopt_long, we have to
> +		// increment optind manually.
> +		++optind;
> +	}
> +
> +	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
> +		bad_usage(_("get does not accept the --dest option"));
> +
> +	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
> +		bad_usage(_("new does not accept the --source option"));
> +
> +	// If the -s/--source option is not specified, it defaults to the PID
> +	// of the current coresched process
> +	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
> +		args->src = getpid();
> +
> +	// More arguments have been passed, which means that the user wants to run
> +	// another program with a core scheduling cookie.
> +	if (argc > optind) {
> +		switch (args->cmd) {
> +		case SCHED_CORE_CMD_GET:
> +			bad_usage(_("bad usage of the get function"));
> +			break;
> +		case SCHED_CORE_CMD_NEW:
> +			if (args->dest)
> +				bad_usage(_(
> +					"new requires either a -d/--dest or a command"));
> +			else
> +				args->exec_argv_offset = optind;
> +			break;
> +		case SCHED_CORE_CMD_COPY:
> +			if (args->dest)
> +				bad_usage(_(
> +					"copy requires either a -d/--dest or a command"));
> +			else
> +				args->exec_argv_offset = optind;
> +			break;
> +		}
> +	} else {
> +		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
> +			bad_usage(_(
> +				"new requires either a -d/--dest or a command"));
> +		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
> +			bad_usage(_(
> +				"copy requires either a -d/--dest or a command"));
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
> +
> +	setlocale(LC_ALL, "");
> +	bindtextdomain(PACKAGE, LOCALEDIR);
> +	textdomain(PACKAGE);
> +	close_stdout_atexit();
> +
> +	parse_and_verify_arguments(argc, argv, &args);
> +
> +	if (!is_core_sched_supported())
> +		errx(EXIT_FAILURE,
> +		     _("No support for core scheduling found. Does your kernel"
> +		       "support CONFIG_SCHED_CORE?"));
> +
> +	sched_core_cookie cookie;
> +
> +	switch (args.cmd) {
> +	case SCHED_CORE_CMD_GET:
> +		cookie = core_sched_get_cookie(args.src);
> +		printf(_("cookie of pid %d is 0x%lx\n"), args.src, cookie);
> +		break;
> +	case SCHED_CORE_CMD_NEW:
> +		if (args.exec_argv_offset) {
> +			core_sched_exec_with_cookie(&args, argv);
> +		} else {
> +			core_sched_create_cookie(args.dest, args.type);
> +			core_sched_get_and_print_cookie(args.dest);
> +		}
> +		break;
> +	case SCHED_CORE_CMD_COPY:
> +		if (args.exec_argv_offset)
> +			core_sched_exec_with_cookie(&args, argv);
> +		else
> +			core_sched_copy_cookie(args.src, args.dest, args.type);
> +		break;
> +	default:
> +		usage();
> +	}
> +}
> diff --git a/tests/commands.sh b/tests/commands.sh
> index 5674c5ff0..9eef92ccb 100644
> --- a/tests/commands.sh
> +++ b/tests/commands.sh
> @@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
>  TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
>  TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
>  TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
> +TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
>  TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
>  TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
>  TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
> diff --git a/tests/expected/schedutils/coresched-copy-from-child-to-parent b/tests/expected/schedutils/coresched-copy-from-child-to-parent
> new file mode 100644
> index 000000000..5b9c40052
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-copy-from-child-to-parent
> @@ -0,0 +1 @@
> +DIFFERENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
> new file mode 100644
> index 000000000..ecfc41142
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
> @@ -0,0 +1 @@
> +SAME_COOKIE
> diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
> new file mode 100644
> index 000000000..84f182cbe
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
> @@ -0,0 +1 @@
> +cookie of pid OWN_PID is PARENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
> new file mode 100644
> index 000000000..e183e0402
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
> @@ -0,0 +1 @@
> +cookie of pid PARENT_PID is PARENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
> new file mode 100644
> index 000000000..5b9c40052
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
> @@ -0,0 +1 @@
> +DIFFERENT_COOKIE
> diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
> new file mode 100644
> index 000000000..e7318ffc2
> --- /dev/null
> +++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
> @@ -0,0 +1 @@
> +coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
> diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
> new file mode 100644
> index 000000000..e7318ffc2
> --- /dev/null
> +++ b/tests/expected/schedutils/set-cookie-parent-pid
> @@ -0,0 +1 @@
> +coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
> diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
> new file mode 100755
> index 000000000..e34fa319f
> --- /dev/null
> +++ b/tests/ts/schedutils/coresched
> @@ -0,0 +1,83 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: EUPL-1.2
> +#
> +# This file is part of util-linux
> +#
> +# Copyright (C) 2024 Thijs Raymakers
> +# Licensed under the EUPL v1.2
> +
> +TS_TOPDIR="${0%/*}/../.."
> +TS_DESC="coresched"
> +
> +. "$TS_TOPDIR"/functions.sh
> +ts_init "$*"
> +
> +ts_check_test_command "$TS_CMD_CORESCHED"
> +ts_check_prog "tee"
> +ts_check_prog "sed"
> +
> +# If there is no kernel support, skip the test suite
> +CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
> +if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
> +  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
> +fi
> +
> +# The output of coresched contains PIDs and core scheduling cookies, both of which should be
> +# assumed to be random values as we have no control over them. The tests replace these values
> +# with sed before writing them to the output file, so it can match the expected output file.
> +# - The PID of this bash script is replaced with the placeholder `OWN_PID`
> +# - The core scheduling cookie of this bash script is replaced by `COOKIE`
> +# - Any other cookie is replaced by `DIFFERENT_COOKIE`
> +# The behavior of coresched does not depend on the exact values of these cookies, so using
> +# placeholder values does not change the behavior tests.
> +ts_init_subtest "set-cookie-parent-pid"
> +CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
> +  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
> +  | sed "s/$$/PARENT_PID/g")
> +CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
> +if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
> +  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
> +fi
> +CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
> +  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
> +echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
> +ts_finalize_subtest
> +
> +ts_init_subtest "get-cookie-parent-pid"
> +$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
> +  | sed -e "s/$$/PARENT_PID/g" \
> +        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "get-cookie-own-pid"
> +$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
> +  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
> +        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "new-child-with-new-cookie"
> +$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
> +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
> +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
> +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "copy-from-parent-to-nested-child"
> +$TS_CMD_CORESCHED new -- /bin/bash -c \
> +  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
> +2>> "$TS_ERRLOG" \
> +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
> +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
> +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_init_subtest "copy-from-child-to-parent"
> +$TS_CMD_CORESCHED new -- /bin/bash -c \
> +  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
> +$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
> +  | sed -e 's/^.*\(0x.*$\)/\1/g' \
> +        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
> +        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
> +ts_finalize_subtest
> +
> +ts_finalize
> -- 
> 2.44.0
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v8] coresched: Manage core scheduling cookies for tasks
  2024-04-11 13:46                                       ` Phil Auld
@ 2024-04-17 10:31                                         ` Karel Zak
  2024-04-17 11:39                                           ` [PATCH v9] " Thijs Raymakers
  2024-04-17 12:27                                           ` [PATCH v8] " Phil Auld
  0 siblings, 2 replies; 42+ messages in thread
From: Karel Zak @ 2024-04-17 10:31 UTC (permalink / raw)
  To: Phil Auld; +Cc: Thijs Raymakers, thomas, util-linux

On Thu, Apr 11, 2024 at 09:46:40AM -0400, Phil Auld wrote:
> On Thu, Apr 11, 2024 at 01:02:49PM +0200 Thijs Raymakers wrote:
> > Co-authored-by: Phil Auld <pauld@redhat.com>
> > Signed-off-by: Phil Auld <pauld@redhat.com>
> > Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> >
> 
> Hi Thijs.
> 
> > ---
> > 
> > Hi Phil,
> > 
> > That might have been a bit too drastic from my side. I just wanted to
> > make sure that I didn't accidentally attribute something to you that you
> > didn't fully support, since the arguments of this version differ
> > significantly from what we've previously discussed. I've put you back.
> 
> Thanks!  I support whatever we can get in that covers the use cases. If we ask
> three engineers about commandline options we'll likely get 5 answers :)
> 
> What we have here looks good and works well.

+1 This version is fine.

> A couple of minor man page comments...

Okay, please finalize it and I'll merge it.

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com


^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v9] coresched: Manage core scheduling cookies for tasks
  2024-04-17 10:31                                         ` Karel Zak
@ 2024-04-17 11:39                                           ` Thijs Raymakers
  2024-04-23  9:27                                             ` Karel Zak
  2024-04-23 10:19                                             ` Thomas Weißschuh
  2024-04-17 12:27                                           ` [PATCH v8] " Phil Auld
  1 sibling, 2 replies; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-17 11:39 UTC (permalink / raw)
  To: kzak; +Cc: thomas, util-linux, Thijs Raymakers, Phil Auld

Co-authored-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
---

Hi Karel,

Thanks for taking a look at it! Here is the version that addresses the
man page comments that Phil made.

Thijs

Interdiff against v8:
  diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
  index aa228b84f..8a9c28846 100644
  --- a/schedutils/coresched.1.adoc
  +++ b/schedutils/coresched.1.adoc
  @@ -30,7 +30,7 @@ coresched - manage core scheduling cookies for tasks
   == DESCRIPTION
   The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
   
  -Core scheduling allows you to define groups of tasks that are allowed to share a physical core.
  +Core scheduling permits the definition of groups of tasks that are allowed to share a physical core.
   This is done by assigning a cookie to each task.
   Only tasks have the same cookie are allowed to be scheduled on the same physical core.
   
  @@ -99,7 +99,7 @@ Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}:
   *{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
   
   == PERMISSIONS
  -When retrieving or modifying the core scheduling cookie of a process, you need to have *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
  +Retrieving or modifying the core scheduling cookie of a process requires *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
   See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
   
   == RETURN VALUE

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 139 +++++++
 schedutils/coresched.c                        | 358 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-copy-from-child-to-parent       |   1 +
 ...coresched-copy-from-parent-to-nested-child |   1 +
 .../schedutils/coresched-get-cookie-own-pid   |   1 +
 .../coresched-get-cookie-parent-pid           |   1 +
 .../coresched-new-child-with-new-cookie       |   1 +
 .../coresched-set-cookie-parent-pid.err       |   1 +
 .../expected/schedutils/set-cookie-parent-pid |   1 +
 tests/ts/schedutils/coresched                 |  83 ++++
 17 files changed, 621 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
 create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
 create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
 create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index 6293eb852..35e1a53c7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2514,9 +2514,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2559,6 +2559,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index d83a02a53..096d7c363 100644
--- a/meson.build
+++ b/meson.build
@@ -3182,13 +3182,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index 95cfb820d..70d53072a 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -178,7 +178,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..8a9c28846
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,139 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [*get*] [*-s* _pid_]
+
+*{command}* *new* [*-t* _type_] *-d* _pid_
+
+*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling permits the definition of groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*get*::
+Retrieve the core scheduling cookie of the PID specified in *-s*.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+*new*::
+Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
+
+*copy*::
+Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+If no function is specified, it will run the *get* function.
+
+== OPTIONS
+*-s*, *--source* _PID_::
+Which _PID_ to get the cookie from.
+If this option is omitted, it will get the cookie from the current *{command}* process.
+
+*-d*, *--dest* _PID_::
+Which _PID_ to modify the cookie of.
+
+*-t*, *--dest-type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
+*{command} get*
+
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} get -s* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} new -d* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} new* \-- _command_ [_argument_...]
+
+Copy the cookie from the current {command} process another task with pid _456_{colon}::
+*{command} copy -d* _456_
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} copy -s* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} copy -s* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
+
+== PERMISSIONS
+Retrieving or modifying the core scheduling cookie of a process requires *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+
+If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
+
+== NOTES
+*{command}* requires core scheduling support in the kernel.
+This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers],
+mailto:pauld@redhat.com[Phil Auld]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..bb97cc020
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,358 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers, Phil Auld
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+	#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+	#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+	#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+	#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+	#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+	#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t src;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
+		stdout);
+	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
+		  "                            PID or execute a program with a new cookie"),
+		stdout);
+	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
+		  "                            to another PID, or execute a program with that\n"
+		  "                            copied cookie"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fprintf(stdout,
+		_(" -s, --source <PID>       which PID to get the cookie from\n"
+		  "                            If omitted, it is the PID of %s itself\n"),
+		program_invocation_short_name);
+	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
+		stdout);
+	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
+		  "                            when a new core scheduling cookie is created.\n"
+		  "                            Can be one of the following: pid, tgid or pgid.\n"
+		  "                            The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout, USAGE_HELP_OPTIONS(20));
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...)                 \
+	do {                              \
+		warnx(FMT);               \
+		errtryhelp(EXIT_FAILURE); \
+	} while (0)
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0))
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		warnx(_("copied cookie 0x%lx from PID %d to PID %d"), before,
+		      from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		warnx(_("set cookie of PID %d to 0x%lx"), pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->src) {
+		core_sched_pull_cookie(args->src);
+		core_sched_get_and_print_cookie(args->src);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv))
+		errexec(argv[0]);
+}
+
+// If PR_SCHED_CORE is not recognized, or not supported on this system,
+// then prctl will set errno to EINVAL. Assuming all other operands of
+// prctl are valid, we can use errno==EINVAL as a check to see whether
+// core scheduling is available on this system.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		if (errno == EINVAL)
+			return false;
+
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strcmp(str, "pid"))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strcmp(str, "tgid"))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strcmp(str, "pgid"))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "source", required_argument, NULL, 's' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "dest-type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
+		switch (c) {
+		case 's':
+			args->src = strtopid_or_err(
+				optarg,
+				_("Failed to parse PID for -s/--source"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+
+	if (argc <= optind) {
+		args->cmd = SCHED_CORE_CMD_GET;
+	} else {
+		if (!strcmp(argv[optind], "get"))
+			args->cmd = SCHED_CORE_CMD_GET;
+		else if (!strcmp(argv[optind], "new"))
+			args->cmd = SCHED_CORE_CMD_NEW;
+		else if (!strcmp(argv[optind], "copy"))
+			args->cmd = SCHED_CORE_CMD_COPY;
+		else
+			bad_usage(_("Unknown function"));
+
+		// Since we parsed an extra "option" outside of getopt_long, we have to
+		// increment optind manually.
+		++optind;
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
+		bad_usage(_("get does not accept the --dest option"));
+
+	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
+		bad_usage(_("new does not accept the --source option"));
+
+	// If the -s/--source option is not specified, it defaults to the PID
+	// of the current coresched process
+	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
+		args->src = getpid();
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("bad usage of the get function"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->dest)
+				bad_usage(_(
+					"new requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest)
+				bad_usage(_(
+					"copy requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		}
+	} else {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
+			bad_usage(_(
+				"new requires either a -d/--dest or a command"));
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
+			bad_usage(_(
+				"copy requires either a -d/--dest or a command"));
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_and_verify_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported())
+		errx(EXIT_FAILURE,
+		     _("No support for core scheduling found. Does your kernel"
+		       "support CONFIG_SCHED_CORE?"));
+
+	sched_core_cookie cookie;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		cookie = core_sched_get_cookie(args.src);
+		printf(_("cookie of pid %d is 0x%lx\n"), args.src, cookie);
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.exec_argv_offset) {
+			core_sched_exec_with_cookie(&args, argv);
+		} else {
+			core_sched_create_cookie(args.dest, args.type);
+			core_sched_get_and_print_cookie(args.dest);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.exec_argv_offset)
+			core_sched_exec_with_cookie(&args, argv);
+		else
+			core_sched_copy_cookie(args.src, args.dest, args.type);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-copy-from-child-to-parent b/tests/expected/schedutils/coresched-copy-from-child-to-parent
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-child-to-parent
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
new file mode 100644
index 000000000..ecfc41142
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
@@ -0,0 +1 @@
+SAME_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
new file mode 100644
index 000000000..84f182cbe
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
@@ -0,0 +1 @@
+cookie of pid OWN_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
new file mode 100644
index 000000000..e183e0402
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
@@ -0,0 +1 @@
+cookie of pid PARENT_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/set-cookie-parent-pid
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..e34fa319f
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,83 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+ts_check_prog "tee"
+ts_check_prog "sed"
+
+# If there is no kernel support, skip the test suite
+CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
+if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
+  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
+fi
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "set-cookie-parent-pid"
+CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
+  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
+  | sed "s/$$/PARENT_PID/g")
+CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
+  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
+fi
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
+  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-parent-pid"
+$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
+  | sed -e "s/$$/PARENT_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-own-pid"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "new-child-with-new-cookie"
+$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-parent-to-nested-child"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
+2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-child-to-parent"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v8] coresched: Manage core scheduling cookies for tasks
  2024-04-17 10:31                                         ` Karel Zak
  2024-04-17 11:39                                           ` [PATCH v9] " Thijs Raymakers
@ 2024-04-17 12:27                                           ` Phil Auld
  1 sibling, 0 replies; 42+ messages in thread
From: Phil Auld @ 2024-04-17 12:27 UTC (permalink / raw)
  To: Karel Zak; +Cc: Thijs Raymakers, thomas, util-linux

On Wed, Apr 17, 2024 at 12:31:38PM +0200 Karel Zak wrote:
> On Thu, Apr 11, 2024 at 09:46:40AM -0400, Phil Auld wrote:
> > On Thu, Apr 11, 2024 at 01:02:49PM +0200 Thijs Raymakers wrote:
> > > Co-authored-by: Phil Auld <pauld@redhat.com>
> > > Signed-off-by: Phil Auld <pauld@redhat.com>
> > > Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> > >
> > 
> > Hi Thijs.
> > 
> > > ---
> > > 
> > > Hi Phil,
> > > 
> > > That might have been a bit too drastic from my side. I just wanted to
> > > make sure that I didn't accidentally attribute something to you that you
> > > didn't fully support, since the arguments of this version differ
> > > significantly from what we've previously discussed. I've put you back.
> > 
> > Thanks!  I support whatever we can get in that covers the use cases. If we ask
> > three engineers about commandline options we'll likely get 5 answers :)
> > 
> > What we have here looks good and works well.
> 
> +1 This version is fine.
> 
> > A couple of minor man page comments...
> 
> Okay, please finalize it and I'll merge it.
>

Thanks Karel!   v9 below looks good to me, still.


Cheers,
Phil

>     Karel
> 
> -- 
>  Karel Zak  <kzak@redhat.com>
>  http://karelzak.blogspot.com
> 

-- 


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v9] coresched: Manage core scheduling cookies for tasks
  2024-04-17 11:39                                           ` [PATCH v9] " Thijs Raymakers
@ 2024-04-23  9:27                                             ` Karel Zak
  2024-04-23 10:19                                             ` Thomas Weißschuh
  1 sibling, 0 replies; 42+ messages in thread
From: Karel Zak @ 2024-04-23  9:27 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: thomas, util-linux, Phil Auld

On Wed, Apr 17, 2024 at 01:39:32PM +0200, Thijs Raymakers wrote:
>  .gitignore                                    |   1 +
>  bash-completion/coresched                     |   0
>  configure.ac                                  |  12 +-
>  meson.build                                   |  16 +-
>  meson_options.txt                             |   2 +-
>  schedutils/Makemodule.am                      |   8 +
>  schedutils/coresched.1.adoc                   | 139 +++++++
>  schedutils/coresched.c                        | 358 ++++++++++++++++++
>  tests/commands.sh                             |   1 +
>  .../coresched-copy-from-child-to-parent       |   1 +
>  ...coresched-copy-from-parent-to-nested-child |   1 +
>  .../schedutils/coresched-get-cookie-own-pid   |   1 +
>  .../coresched-get-cookie-parent-pid           |   1 +
>  .../coresched-new-child-with-new-cookie       |   1 +
>  .../coresched-set-cookie-parent-pid.err       |   1 +
>  .../expected/schedutils/set-cookie-parent-pid |   1 +
>  tests/ts/schedutils/coresched                 |  83 ++++
>  17 files changed, 621 insertions(+), 6 deletions(-)


I have created pull-request with the patch to verify it pass all tests.

    https://github.com/util-linux/util-linux/pull/2990

 Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v9] coresched: Manage core scheduling cookies for tasks
  2024-04-17 11:39                                           ` [PATCH v9] " Thijs Raymakers
  2024-04-23  9:27                                             ` Karel Zak
@ 2024-04-23 10:19                                             ` Thomas Weißschuh
  2024-04-23 11:12                                               ` [PATCH v10] " Thijs Raymakers
  1 sibling, 1 reply; 42+ messages in thread
From: Thomas Weißschuh @ 2024-04-23 10:19 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux, Phil Auld

On 2024-04-17 13:39:32+0000, Thijs Raymakers wrote:
> Co-authored-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> ---
> 
> Hi Karel,
> 
> Thanks for taking a look at it! Here is the version that addresses the
> man page comments that Phil made.
> 
> Thijs

[..]

> diff --git a/schedutils/coresched.c b/schedutils/coresched.c
> new file mode 100644
> index 000000000..bb97cc020
> --- /dev/null
> +++ b/schedutils/coresched.c
> @@ -0,0 +1,358 @@
> +/**
> + * SPDX-License-Identifier: EUPL-1.2
> + *
> + * coresched.c - manage core scheduling cookies for tasks
> + *
> + * Copyright (C) 2024 Thijs Raymakers, Phil Auld
> + * Licensed under the EUPL v1.2
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <sys/prctl.h>
> +#include <unistd.h>
> +
> +#include "c.h"
> +#include "closestream.h"
> +#include "nls.h"
> +#include "strutils.h"
> +
> +// These definitions might not be defined in the header files, even if the
> +// prctl interface in the kernel accepts them as valid.
> +#ifndef PR_SCHED_CORE
> +	#define PR_SCHED_CORE 62
> +#endif
> +#ifndef PR_SCHED_CORE_GET
> +	#define PR_SCHED_CORE_GET 0
> +#endif
> +#ifndef PR_SCHED_CORE_CREATE
> +	#define PR_SCHED_CORE_CREATE 1
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_TO
> +	#define PR_SCHED_CORE_SHARE_TO 2
> +#endif
> +#ifndef PR_SCHED_CORE_SHARE_FROM
> +	#define PR_SCHED_CORE_SHARE_FROM 3
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD
> +	#define PR_SCHED_CORE_SCOPE_THREAD 0
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
> +	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
> +#endif
> +#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
> +	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
> +#endif
> +
> +typedef int sched_core_scope;
> +typedef unsigned long sched_core_cookie;

This should be uint64_t, as the kernel will always copy 64 bytes.

Otherwise it will smash the stack on 32bit:

https://download.copr.fedorainfracloud.org/results/packit/util-linux-util-linux-2990/fedora-rawhide-i386/07339240-util-linux/builder-live.log.gz

> +typedef enum {
> +	SCHED_CORE_CMD_GET,
> +	SCHED_CORE_CMD_NEW,
> +	SCHED_CORE_CMD_COPY,
> +} sched_core_cmd;
> +
> +struct args {
> +	pid_t src;
> +	pid_t dest;
> +	sched_core_scope type;
> +	sched_core_cmd cmd;
> +	int exec_argv_offset;
> +};
> +

[..]

^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v10] coresched: Manage core scheduling cookies for tasks
  2024-04-23 10:19                                             ` Thomas Weißschuh
@ 2024-04-23 11:12                                               ` Thijs Raymakers
  2024-04-25 15:36                                                 ` [PATCH v11] " Thijs Raymakers
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-23 11:12 UTC (permalink / raw)
  To: thomas; +Cc: kzak, util-linux, Thijs Raymakers, Phil Auld

Co-authored-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
---

Op 23-04-2024 om 12:19 p.m. schreef Thomas Weißschuh:
> On 2024-04-17 13:39:32+0000, Thijs Raymakers wrote:
>> +typedef unsigned long sched_core_cookie;
> This should be uint64_t, as the kernel will always copy 64 bytes.
> 
> Otherwise it will smash the stack on 32bit:

Thanks! I've changed the type to an unsigned long long which should have
a minimum size of 64 bits.

Interdiff against v9:
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index bb97cc020..7bc5c9d38 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -46,7 +46,7 @@
   #endif
   
   typedef int sched_core_scope;
  -typedef unsigned long sched_core_cookie;
  +typedef unsigned long long sched_core_cookie;
   typedef enum {
   	SCHED_CORE_CMD_GET,
   	SCHED_CORE_CMD_NEW,
  @@ -154,7 +154,7 @@ static void core_sched_copy_cookie(pid_t from, pid_t to,
   
   	if (sched_core_verbose) {
   		sched_core_cookie before = core_sched_get_cookie(from);
  -		warnx(_("copied cookie 0x%lx from PID %d to PID %d"), before,
  +		warnx(_("copied cookie 0x%llx from PID %d to PID %d"), before,
   		      from, to);
   	}
   }
  @@ -163,7 +163,7 @@ static void core_sched_get_and_print_cookie(pid_t pid)
   {
   	if (sched_core_verbose) {
   		sched_core_cookie after = core_sched_get_cookie(pid);
  -		warnx(_("set cookie of PID %d to 0x%lx"), pid, after);
  +		warnx(_("set cookie of PID %d to 0x%llx"), pid, after);
   	}
   }
   
  @@ -336,7 +336,7 @@ int main(int argc, char **argv)
   	switch (args.cmd) {
   	case SCHED_CORE_CMD_GET:
   		cookie = core_sched_get_cookie(args.src);
  -		printf(_("cookie of pid %d is 0x%lx\n"), args.src, cookie);
  +		printf(_("cookie of pid %d is 0x%llx\n"), args.src, cookie);
   		break;
   	case SCHED_CORE_CMD_NEW:
   		if (args.exec_argv_offset) {

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 139 +++++++
 schedutils/coresched.c                        | 358 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-copy-from-child-to-parent       |   1 +
 ...coresched-copy-from-parent-to-nested-child |   1 +
 .../schedutils/coresched-get-cookie-own-pid   |   1 +
 .../coresched-get-cookie-parent-pid           |   1 +
 .../coresched-new-child-with-new-cookie       |   1 +
 .../coresched-set-cookie-parent-pid.err       |   1 +
 .../expected/schedutils/set-cookie-parent-pid |   1 +
 tests/ts/schedutils/coresched                 |  83 ++++
 17 files changed, 621 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
 create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
 create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
 create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index 1d7a9cf70..70a60cf5d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2514,9 +2514,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2559,6 +2559,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 5b4f59b8c..3cfd63449 100644
--- a/meson.build
+++ b/meson.build
@@ -3194,13 +3194,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index ca76530a9..8a70555d7 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -182,7 +182,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..8a9c28846
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,139 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [*get*] [*-s* _pid_]
+
+*{command}* *new* [*-t* _type_] *-d* _pid_
+
+*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling permits the definition of groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*get*::
+Retrieve the core scheduling cookie of the PID specified in *-s*.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+*new*::
+Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
+
+*copy*::
+Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+If no function is specified, it will run the *get* function.
+
+== OPTIONS
+*-s*, *--source* _PID_::
+Which _PID_ to get the cookie from.
+If this option is omitted, it will get the cookie from the current *{command}* process.
+
+*-d*, *--dest* _PID_::
+Which _PID_ to modify the cookie of.
+
+*-t*, *--dest-type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
+*{command} get*
+
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} get -s* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} new -d* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} new* \-- _command_ [_argument_...]
+
+Copy the cookie from the current {command} process another task with pid _456_{colon}::
+*{command} copy -d* _456_
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} copy -s* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} copy -s* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
+
+== PERMISSIONS
+Retrieving or modifying the core scheduling cookie of a process requires *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+
+If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
+
+== NOTES
+*{command}* requires core scheduling support in the kernel.
+This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers],
+mailto:pauld@redhat.com[Phil Auld]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..7bc5c9d38
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,358 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers, Phil Auld
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+	#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+	#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+	#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+	#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+	#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+	#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t src;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
+		stdout);
+	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
+		  "                            PID or execute a program with a new cookie"),
+		stdout);
+	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
+		  "                            to another PID, or execute a program with that\n"
+		  "                            copied cookie"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fprintf(stdout,
+		_(" -s, --source <PID>       which PID to get the cookie from\n"
+		  "                            If omitted, it is the PID of %s itself\n"),
+		program_invocation_short_name);
+	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
+		stdout);
+	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
+		  "                            when a new core scheduling cookie is created.\n"
+		  "                            Can be one of the following: pid, tgid or pgid.\n"
+		  "                            The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout, USAGE_HELP_OPTIONS(20));
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...)                 \
+	do {                              \
+		warnx(FMT);               \
+		errtryhelp(EXIT_FAILURE); \
+	} while (0)
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0))
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		warnx(_("copied cookie 0x%llx from PID %d to PID %d"), before,
+		      from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		warnx(_("set cookie of PID %d to 0x%llx"), pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->src) {
+		core_sched_pull_cookie(args->src);
+		core_sched_get_and_print_cookie(args->src);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv))
+		errexec(argv[0]);
+}
+
+// If PR_SCHED_CORE is not recognized, or not supported on this system,
+// then prctl will set errno to EINVAL. Assuming all other operands of
+// prctl are valid, we can use errno==EINVAL as a check to see whether
+// core scheduling is available on this system.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		if (errno == EINVAL)
+			return false;
+
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strcmp(str, "pid"))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strcmp(str, "tgid"))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strcmp(str, "pgid"))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "source", required_argument, NULL, 's' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "dest-type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
+		switch (c) {
+		case 's':
+			args->src = strtopid_or_err(
+				optarg,
+				_("Failed to parse PID for -s/--source"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+
+	if (argc <= optind) {
+		args->cmd = SCHED_CORE_CMD_GET;
+	} else {
+		if (!strcmp(argv[optind], "get"))
+			args->cmd = SCHED_CORE_CMD_GET;
+		else if (!strcmp(argv[optind], "new"))
+			args->cmd = SCHED_CORE_CMD_NEW;
+		else if (!strcmp(argv[optind], "copy"))
+			args->cmd = SCHED_CORE_CMD_COPY;
+		else
+			bad_usage(_("Unknown function"));
+
+		// Since we parsed an extra "option" outside of getopt_long, we have to
+		// increment optind manually.
+		++optind;
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
+		bad_usage(_("get does not accept the --dest option"));
+
+	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
+		bad_usage(_("new does not accept the --source option"));
+
+	// If the -s/--source option is not specified, it defaults to the PID
+	// of the current coresched process
+	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
+		args->src = getpid();
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("bad usage of the get function"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->dest)
+				bad_usage(_(
+					"new requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest)
+				bad_usage(_(
+					"copy requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		}
+	} else {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
+			bad_usage(_(
+				"new requires either a -d/--dest or a command"));
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
+			bad_usage(_(
+				"copy requires either a -d/--dest or a command"));
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_and_verify_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported())
+		errx(EXIT_FAILURE,
+		     _("No support for core scheduling found. Does your kernel"
+		       "support CONFIG_SCHED_CORE?"));
+
+	sched_core_cookie cookie;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		cookie = core_sched_get_cookie(args.src);
+		printf(_("cookie of pid %d is 0x%llx\n"), args.src, cookie);
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.exec_argv_offset) {
+			core_sched_exec_with_cookie(&args, argv);
+		} else {
+			core_sched_create_cookie(args.dest, args.type);
+			core_sched_get_and_print_cookie(args.dest);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.exec_argv_offset)
+			core_sched_exec_with_cookie(&args, argv);
+		else
+			core_sched_copy_cookie(args.src, args.dest, args.type);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-copy-from-child-to-parent b/tests/expected/schedutils/coresched-copy-from-child-to-parent
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-child-to-parent
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
new file mode 100644
index 000000000..ecfc41142
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
@@ -0,0 +1 @@
+SAME_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
new file mode 100644
index 000000000..84f182cbe
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
@@ -0,0 +1 @@
+cookie of pid OWN_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
new file mode 100644
index 000000000..e183e0402
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
@@ -0,0 +1 @@
+cookie of pid PARENT_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/set-cookie-parent-pid
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..e34fa319f
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,83 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+ts_check_prog "tee"
+ts_check_prog "sed"
+
+# If there is no kernel support, skip the test suite
+CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
+if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
+  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
+fi
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "set-cookie-parent-pid"
+CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
+  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
+  | sed "s/$$/PARENT_PID/g")
+CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
+  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
+fi
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
+  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-parent-pid"
+$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
+  | sed -e "s/$$/PARENT_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-own-pid"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "new-child-with-new-cookie"
+$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-parent-to-nested-child"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
+2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-child-to-parent"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* [PATCH v11] coresched: Manage core scheduling cookies for tasks
  2024-04-23 11:12                                               ` [PATCH v10] " Thijs Raymakers
@ 2024-04-25 15:36                                                 ` Thijs Raymakers
  2024-04-25 15:51                                                   ` Thomas Weißschuh
  0 siblings, 1 reply; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-25 15:36 UTC (permalink / raw)
  To: thomas; +Cc: kzak, util-linux, Thijs Raymakers, Phil Auld

Co-authored-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
---

I noticed that the CI pipeline failed because the coresched test reported
a "No such device" error.

https://download.copr.fedorainfracloud.org/results/packit/util-linux-util-linux-2990/fedora-rawhide-x86_64/07343926-util-linux/builder-live.log.gz

This happens because the following check in the kernel's
kernel/sched/core_sched.c fails:

> /* Called from prctl interface: PR_SCHED_CORE */
> int sched_core_share_pid(unsigned int cmd, pid_t pid, enum pid_type type,
> 			 unsigned long uaddr)
> {
>   [snip]
> 
> 	if (!static_branch_likely(&sched_smt_present))
> 		return -ENODEV;

The prctl call will return ENODEV whenever SMT is not available,
but the kernel is compiled with CONFIG_SCHED_CORE. I've added a check that skips
the tests for coresched when this is the case.

Thijs


Interdiff against v10:
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index 7bc5c9d38..beeb01702 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -328,7 +328,7 @@ int main(int argc, char **argv)
   
   	if (!is_core_sched_supported())
   		errx(EXIT_FAILURE,
  -		     _("No support for core scheduling found. Does your kernel"
  +		     _("No support for core scheduling found. Does your kernel "
   		       "support CONFIG_SCHED_CORE?"));
   
   	sched_core_cookie cookie;
  diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
  index e34fa319f..debb8cbfc 100755
  --- a/tests/ts/schedutils/coresched
  +++ b/tests/ts/schedutils/coresched
  @@ -22,6 +22,12 @@ if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
     ts_skip "Kernel has no CONFIG_SCHED_CORE support"
   fi
   
  +# Kernel and the machine must support SMT
  +CORESCHED_HAS_SMT_SUPPORT=$(</sys/devices/system/cpu/smt/control)
  +if [[ "$CORESCHED_HAS_SMT_SUPPORT" != "on" ]]; then
  +  ts_skip "SMT is unavailable (state: $CORESCHED_HAS_SMT_SUPPORT)"
  +fi
  +
   # The output of coresched contains PIDs and core scheduling cookies, both of which should be
   # assumed to be random values as we have no control over them. The tests replace these values
   # with sed before writing them to the output file, so it can match the expected output file.

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 139 +++++++
 schedutils/coresched.c                        | 358 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-copy-from-child-to-parent       |   1 +
 ...coresched-copy-from-parent-to-nested-child |   1 +
 .../schedutils/coresched-get-cookie-own-pid   |   1 +
 .../coresched-get-cookie-parent-pid           |   1 +
 .../coresched-new-child-with-new-cookie       |   1 +
 .../coresched-set-cookie-parent-pid.err       |   1 +
 .../expected/schedutils/set-cookie-parent-pid |   1 +
 tests/ts/schedutils/coresched                 |  89 +++++
 17 files changed, 627 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
 create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
 create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
 create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index 1d7a9cf70..70a60cf5d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2514,9 +2514,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2559,6 +2559,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 5b4f59b8c..3cfd63449 100644
--- a/meson.build
+++ b/meson.build
@@ -3194,13 +3194,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index ca76530a9..8a70555d7 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -182,7 +182,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..8a9c28846
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,139 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [*get*] [*-s* _pid_]
+
+*{command}* *new* [*-t* _type_] *-d* _pid_
+
+*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling permits the definition of groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*get*::
+Retrieve the core scheduling cookie of the PID specified in *-s*.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+*new*::
+Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
+
+*copy*::
+Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+If no function is specified, it will run the *get* function.
+
+== OPTIONS
+*-s*, *--source* _PID_::
+Which _PID_ to get the cookie from.
+If this option is omitted, it will get the cookie from the current *{command}* process.
+
+*-d*, *--dest* _PID_::
+Which _PID_ to modify the cookie of.
+
+*-t*, *--dest-type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
+*{command} get*
+
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} get -s* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} new -d* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} new* \-- _command_ [_argument_...]
+
+Copy the cookie from the current {command} process another task with pid _456_{colon}::
+*{command} copy -d* _456_
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} copy -s* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} copy -s* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
+
+== PERMISSIONS
+Retrieving or modifying the core scheduling cookie of a process requires *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+
+If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
+
+== NOTES
+*{command}* requires core scheduling support in the kernel.
+This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers],
+mailto:pauld@redhat.com[Phil Auld]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..beeb01702
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,358 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers, Phil Auld
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+	#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+	#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+	#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+	#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+	#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+	#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t src;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
+		stdout);
+	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
+		  "                            PID or execute a program with a new cookie"),
+		stdout);
+	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
+		  "                            to another PID, or execute a program with that\n"
+		  "                            copied cookie"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fprintf(stdout,
+		_(" -s, --source <PID>       which PID to get the cookie from\n"
+		  "                            If omitted, it is the PID of %s itself\n"),
+		program_invocation_short_name);
+	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
+		stdout);
+	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
+		  "                            when a new core scheduling cookie is created.\n"
+		  "                            Can be one of the following: pid, tgid or pgid.\n"
+		  "                            The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout, USAGE_HELP_OPTIONS(20));
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...)                 \
+	do {                              \
+		warnx(FMT);               \
+		errtryhelp(EXIT_FAILURE); \
+	} while (0)
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0))
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		warnx(_("copied cookie 0x%llx from PID %d to PID %d"), before,
+		      from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		warnx(_("set cookie of PID %d to 0x%llx"), pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->src) {
+		core_sched_pull_cookie(args->src);
+		core_sched_get_and_print_cookie(args->src);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv))
+		errexec(argv[0]);
+}
+
+// If PR_SCHED_CORE is not recognized, or not supported on this system,
+// then prctl will set errno to EINVAL. Assuming all other operands of
+// prctl are valid, we can use errno==EINVAL as a check to see whether
+// core scheduling is available on this system.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		if (errno == EINVAL)
+			return false;
+
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strcmp(str, "pid"))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strcmp(str, "tgid"))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strcmp(str, "pgid"))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "source", required_argument, NULL, 's' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "dest-type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
+		switch (c) {
+		case 's':
+			args->src = strtopid_or_err(
+				optarg,
+				_("Failed to parse PID for -s/--source"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+
+	if (argc <= optind) {
+		args->cmd = SCHED_CORE_CMD_GET;
+	} else {
+		if (!strcmp(argv[optind], "get"))
+			args->cmd = SCHED_CORE_CMD_GET;
+		else if (!strcmp(argv[optind], "new"))
+			args->cmd = SCHED_CORE_CMD_NEW;
+		else if (!strcmp(argv[optind], "copy"))
+			args->cmd = SCHED_CORE_CMD_COPY;
+		else
+			bad_usage(_("Unknown function"));
+
+		// Since we parsed an extra "option" outside of getopt_long, we have to
+		// increment optind manually.
+		++optind;
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
+		bad_usage(_("get does not accept the --dest option"));
+
+	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
+		bad_usage(_("new does not accept the --source option"));
+
+	// If the -s/--source option is not specified, it defaults to the PID
+	// of the current coresched process
+	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
+		args->src = getpid();
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("bad usage of the get function"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->dest)
+				bad_usage(_(
+					"new requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest)
+				bad_usage(_(
+					"copy requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		}
+	} else {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
+			bad_usage(_(
+				"new requires either a -d/--dest or a command"));
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
+			bad_usage(_(
+				"copy requires either a -d/--dest or a command"));
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_and_verify_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported())
+		errx(EXIT_FAILURE,
+		     _("No support for core scheduling found. Does your kernel "
+		       "support CONFIG_SCHED_CORE?"));
+
+	sched_core_cookie cookie;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		cookie = core_sched_get_cookie(args.src);
+		printf(_("cookie of pid %d is 0x%llx\n"), args.src, cookie);
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.exec_argv_offset) {
+			core_sched_exec_with_cookie(&args, argv);
+		} else {
+			core_sched_create_cookie(args.dest, args.type);
+			core_sched_get_and_print_cookie(args.dest);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.exec_argv_offset)
+			core_sched_exec_with_cookie(&args, argv);
+		else
+			core_sched_copy_cookie(args.src, args.dest, args.type);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-copy-from-child-to-parent b/tests/expected/schedutils/coresched-copy-from-child-to-parent
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-child-to-parent
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
new file mode 100644
index 000000000..ecfc41142
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
@@ -0,0 +1 @@
+SAME_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
new file mode 100644
index 000000000..84f182cbe
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
@@ -0,0 +1 @@
+cookie of pid OWN_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
new file mode 100644
index 000000000..e183e0402
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
@@ -0,0 +1 @@
+cookie of pid PARENT_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/set-cookie-parent-pid
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..debb8cbfc
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,89 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+ts_check_prog "tee"
+ts_check_prog "sed"
+
+# If there is no kernel support, skip the test suite
+CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
+if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
+  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
+fi
+
+# Kernel and the machine must support SMT
+CORESCHED_HAS_SMT_SUPPORT=$(</sys/devices/system/cpu/smt/control)
+if [[ "$CORESCHED_HAS_SMT_SUPPORT" != "on" ]]; then
+  ts_skip "SMT is unavailable (state: $CORESCHED_HAS_SMT_SUPPORT)"
+fi
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "set-cookie-parent-pid"
+CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
+  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
+  | sed "s/$$/PARENT_PID/g")
+CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
+  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
+fi
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
+  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-parent-pid"
+$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
+  | sed -e "s/$$/PARENT_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-own-pid"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "new-child-with-new-cookie"
+$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-parent-to-nested-child"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
+2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-child-to-parent"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v11] coresched: Manage core scheduling cookies for tasks
  2024-04-25 15:36                                                 ` [PATCH v11] " Thijs Raymakers
@ 2024-04-25 15:51                                                   ` Thomas Weißschuh
  2024-04-25 16:22                                                     ` [PATCH v12] " Thijs Raymakers
  0 siblings, 1 reply; 42+ messages in thread
From: Thomas Weißschuh @ 2024-04-25 15:51 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux, Phil Auld

Hi,

On 2024-04-25 17:36:26+0000, Thijs Raymakers wrote:
> Co-authored-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> ---
> 
> I noticed that the CI pipeline failed because the coresched test reported
> a "No such device" error.
> 
> https://download.copr.fedorainfracloud.org/results/packit/util-linux-util-linux-2990/fedora-rawhide-x86_64/07343926-util-linux/builder-live.log.gz
> 
> This happens because the following check in the kernel's
> kernel/sched/core_sched.c fails:
> 
> > /* Called from prctl interface: PR_SCHED_CORE */
> > int sched_core_share_pid(unsigned int cmd, pid_t pid, enum pid_type type,
> > 			 unsigned long uaddr)
> > {
> >   [snip]
> > 
> > 	if (!static_branch_likely(&sched_smt_present))
> > 		return -ENODEV;
> 
> The prctl call will return ENODEV whenever SMT is not available,
> but the kernel is compiled with CONFIG_SCHED_CORE. I've added a check that skips
> the tests for coresched when this is the case.
> 
> Thijs
> 
> 
> Interdiff against v10:
>   diff --git a/schedutils/coresched.c b/schedutils/coresched.c
>   index 7bc5c9d38..beeb01702 100644
>   --- a/schedutils/coresched.c
>   +++ b/schedutils/coresched.c
>   @@ -328,7 +328,7 @@ int main(int argc, char **argv)
>    
>    	if (!is_core_sched_supported())
>    		errx(EXIT_FAILURE,
>   -		     _("No support for core scheduling found. Does your kernel"
>   +		     _("No support for core scheduling found. Does your kernel "
>    		       "support CONFIG_SCHED_CORE?"));
>    
>    	sched_core_cookie cookie;
>   diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
>   index e34fa319f..debb8cbfc 100755
>   --- a/tests/ts/schedutils/coresched
>   +++ b/tests/ts/schedutils/coresched
>   @@ -22,6 +22,12 @@ if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
>      ts_skip "Kernel has no CONFIG_SCHED_CORE support"
>    fi
>    
>   +# Kernel and the machine must support SMT
>   +CORESCHED_HAS_SMT_SUPPORT=$(</sys/devices/system/cpu/smt/control)
>   +if [[ "$CORESCHED_HAS_SMT_SUPPORT" != "on" ]]; then
>   +  ts_skip "SMT is unavailable (state: $CORESCHED_HAS_SMT_SUPPORT)"
>   +fi
>   +
>    # The output of coresched contains PIDs and core scheduling cookies, both of which should be
>    # assumed to be random values as we have no control over them. The tests replace these values
>    # with sed before writing them to the output file, so it can match the expected output file.
> 
>  .gitignore                                    |   1 +
>  bash-completion/coresched                     |   0
>  configure.ac                                  |  12 +-
>  meson.build                                   |  16 +-
>  meson_options.txt                             |   2 +-
>  schedutils/Makemodule.am                      |   8 +
>  schedutils/coresched.1.adoc                   | 139 +++++++
>  schedutils/coresched.c                        | 358 ++++++++++++++++++
>  tests/commands.sh                             |   1 +
>  .../coresched-copy-from-child-to-parent       |   1 +
>  ...coresched-copy-from-parent-to-nested-child |   1 +
>  .../schedutils/coresched-get-cookie-own-pid   |   1 +
>  .../coresched-get-cookie-parent-pid           |   1 +
>  .../coresched-new-child-with-new-cookie       |   1 +
>  .../coresched-set-cookie-parent-pid.err       |   1 +
>  .../expected/schedutils/set-cookie-parent-pid |   1 +
>  tests/ts/schedutils/coresched                 |  89 +++++
>  17 files changed, 627 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
>  create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
>  create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
>  create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
>  create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
>  create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
>  create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
>  create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
>  create mode 100755 tests/ts/schedutils/coresched

[..]

> +// If PR_SCHED_CORE is not recognized, or not supported on this system,
> +// then prctl will set errno to EINVAL. Assuming all other operands of
> +// prctl are valid, we can use errno==EINVAL as a check to see whether
> +// core scheduling is available on this system.
> +static bool is_core_sched_supported(void)
> +{
> +	sched_core_cookie cookie = 0;
> +	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
> +		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
> +		if (errno == EINVAL)
> +			return false;

IMO the ENODEV should be handled here.
So the user can get a proper error message.

This would then also remove the need for another ts_skip in the
testcase.

> +
> +	return true;
> +}
> +

[..]

^ permalink raw reply	[flat|nested] 42+ messages in thread

* [PATCH v12] coresched: Manage core scheduling cookies for tasks
  2024-04-25 15:51                                                   ` Thomas Weißschuh
@ 2024-04-25 16:22                                                     ` Thijs Raymakers
  2024-04-26  7:59                                                       ` Karel Zak
  2024-04-26 18:03                                                       ` Thomas Weißschuh
  0 siblings, 2 replies; 42+ messages in thread
From: Thijs Raymakers @ 2024-04-25 16:22 UTC (permalink / raw)
  To: thomas; +Cc: kzak, util-linux, Thijs Raymakers, Phil Auld

Co-authored-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Phil Auld <pauld@redhat.com>
Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
---

Hi Thomas,

Op 25-04-2024 om 5:51 p.m. schreef Thomas Weißschuh:
> On 2024-04-25 17:36:26+0000, Thijs Raymakers wrote:
>>+static bool is_core_sched_supported(void)
>>+{
>>+	sched_core_cookie cookie = 0;
>>+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
>>+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
>>+		if (errno == EINVAL)
>>+			return false;
> IMO the ENODEV should be handled here.
> So the user can get a proper error message.
> 
> This would then also remove the need for another ts_skip in the
> testcase.

I agree, it would be better to handle it there. The error message "No
device available" you would get otherwise is a bit cryptic so this
is an improvement.

I've removed the ts_skip and added the case there.

Interdiff against v11:
  diff --git a/schedutils/coresched.c b/schedutils/coresched.c
  index beeb01702..9d8be3e12 100644
  --- a/schedutils/coresched.c
  +++ b/schedutils/coresched.c
  @@ -188,16 +188,21 @@ static void core_sched_exec_with_cookie(struct args *args, char **argv)
   		errexec(argv[0]);
   }
   
  -// If PR_SCHED_CORE is not recognized, or not supported on this system,
  -// then prctl will set errno to EINVAL. Assuming all other operands of
  -// prctl are valid, we can use errno==EINVAL as a check to see whether
  -// core scheduling is available on this system.
  +// There are two failure conditions for the core scheduling prctl calls
  +// that rely on the environment in which coresched is running.
  +// 1. If PR_SCHED_CORE is not recognized, or not supported on this system,
  +//    then prctl will set errno to EINVAL. Assuming all other operands of
  +//    prctl are valid, we can use errno==EINVAL as a check to see whether
  +//    core scheduling is available on this system.
  +// 2. prctl sets errno to ENODEV if SMT is not available on this system,
  +//    either because SMT support has been disabled in the kernel, or because
  +//    the hardware doesn't support it.
   static bool is_core_sched_supported(void)
   {
   	sched_core_cookie cookie = 0;
   	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
   		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
  -		if (errno == EINVAL)
  +		if (errno == EINVAL || errno == ENODEV)
   			return false;
   
   	return true;
  @@ -328,8 +333,8 @@ int main(int argc, char **argv)
   
   	if (!is_core_sched_supported())
   		errx(EXIT_FAILURE,
  -		     _("No support for core scheduling found. Does your kernel "
  -		       "support CONFIG_SCHED_CORE?"));
  +		     _("Core scheduling is not supported on this system. Either SMT "
  +		       "is unavailable or your kernel does not support CONFIG_SCHED_CORE."));
   
   	sched_core_cookie cookie;
   
  diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
  index debb8cbfc..2ab0803bd 100755
  --- a/tests/ts/schedutils/coresched
  +++ b/tests/ts/schedutils/coresched
  @@ -16,16 +16,10 @@ ts_check_test_command "$TS_CMD_CORESCHED"
   ts_check_prog "tee"
   ts_check_prog "sed"
   
  -# If there is no kernel support, skip the test suite
  +# If coresched cannot succesfully run, skip the test suite
   CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
   if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
  -  ts_skip "Kernel has no CONFIG_SCHED_CORE support"
  -fi
  -
  -# Kernel and the machine must support SMT
  -CORESCHED_HAS_SMT_SUPPORT=$(</sys/devices/system/cpu/smt/control)
  -if [[ "$CORESCHED_HAS_SMT_SUPPORT" != "on" ]]; then
  -  ts_skip "SMT is unavailable (state: $CORESCHED_HAS_SMT_SUPPORT)"
  +  ts_skip "Kernel has no CONFIG_SCHED_CORE support or SMT is not available"
   fi
   
   # The output of coresched contains PIDs and core scheduling cookies, both of which should be

 .gitignore                                    |   1 +
 bash-completion/coresched                     |   0
 configure.ac                                  |  12 +-
 meson.build                                   |  16 +-
 meson_options.txt                             |   2 +-
 schedutils/Makemodule.am                      |   8 +
 schedutils/coresched.1.adoc                   | 139 +++++++
 schedutils/coresched.c                        | 363 ++++++++++++++++++
 tests/commands.sh                             |   1 +
 .../coresched-copy-from-child-to-parent       |   1 +
 ...coresched-copy-from-parent-to-nested-child |   1 +
 .../schedutils/coresched-get-cookie-own-pid   |   1 +
 .../coresched-get-cookie-parent-pid           |   1 +
 .../coresched-new-child-with-new-cookie       |   1 +
 .../coresched-set-cookie-parent-pid.err       |   1 +
 .../expected/schedutils/set-cookie-parent-pid |   1 +
 tests/ts/schedutils/coresched                 |  83 ++++
 17 files changed, 626 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c
 create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
 create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
 create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
 create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
 create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
 create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
 create mode 100755 tests/ts/schedutils/coresched

diff --git a/.gitignore b/.gitignore
index 6ecbfa7fe..316f3cdcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ylwrap
 /colcrt
 /colrm
 /column
+/coresched
 /ctrlaltdel
 /delpart
 /dmesg
diff --git a/bash-completion/coresched b/bash-completion/coresched
new file mode 100644
index 000000000..e69de29bb
diff --git a/configure.ac b/configure.ac
index 1d7a9cf70..70a60cf5d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2514,9 +2514,9 @@ UL_REQUIRES_HAVE([setterm], [ncursesw, ncurses], [ncursesw or ncurses library])
 AM_CONDITIONAL([BUILD_SETTERM], [test "x$build_setterm" = xyes])
 
 # build_schedutils= is just configure-only variable to control
-# ionice, taskset and chrt
+# ionice, taskset, coresched and chrt
 AC_ARG_ENABLE([schedutils],
-  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset]),
+  AS_HELP_STRING([--disable-schedutils], [do not build chrt, ionice, taskset, coresched]),
   [], [UL_DEFAULT_ENABLE([schedutils], [check])]
 )
 
@@ -2559,6 +2559,14 @@ UL_REQUIRES_SYSCALL_CHECK([taskset],
 AM_CONDITIONAL([BUILD_TASKSET], [test "x$build_taskset" = xyes])
 
 
+UL_ENABLE_ALIAS([coresched], [schedutils])
+UL_BUILD_INIT([coresched])
+UL_REQUIRES_SYSCALL_CHECK([coresched],
+	[UL_CHECK_SYSCALL([prctl])],
+	[prctl])
+AM_CONDITIONAL([BUILD_CORESCHED], [test "x$build_coresched" = xyes])
+
+
 have_schedsetter=no
 AS_IF([test "x$ac_cv_func_sched_setscheduler" = xyes], [have_schedsetter=yes],
       [test "x$ac_cv_func_sched_setattr" = xyes], [have_schedsetter=yes])
diff --git a/meson.build b/meson.build
index 5b4f59b8c..3cfd63449 100644
--- a/meson.build
+++ b/meson.build
@@ -3194,13 +3194,23 @@ exe4 = executable(
   install : opt,
   build_by_default : opt)
 
+exe5 = executable(
+  'coresched',
+  'schedutils/coresched.c',
+  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, exe2, exe3, exe4]
+  exes += [exe, exe2, exe3, exe4, exe5]
   manadocs += ['schedutils/chrt.1.adoc',
                'schedutils/ionice.1.adoc',
                'schedutils/taskset.1.adoc',
-	       'schedutils/uclampset.1.adoc']
-  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset']
+               'schedutils/uclampset.1.adoc',
+               'schedutils/coresched.1.adoc']
+  bashcompletions += ['chrt', 'ionice', 'taskset', 'uclampset', 'coresched']
 endif
 
 ############################################################
diff --git a/meson_options.txt b/meson_options.txt
index ca76530a9..8a70555d7 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -182,7 +182,7 @@ option('build-pipesz', type : 'feature',
 option('build-setterm', type : 'feature',
        description : 'build setterm')
 option('build-schedutils', type : 'feature',
-       description : 'build chrt, ionice, taskset')
+       description : 'build chrt, ionice, taskset, coresched')
 option('build-wall', type : 'feature',
        description : 'build wall')
 option('build-write', type : 'feature',
diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am
index 1040da85f..0cb655401 100644
--- a/schedutils/Makemodule.am
+++ b/schedutils/Makemodule.am
@@ -29,3 +29,11 @@ dist_noinst_DATA += schedutils/uclampset.1.adoc
 uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h
 uclampset_LDADD = $(LDADD) libcommon.la
 endif
+
+if BUILD_CORESCHED
+usrbin_exec_PROGRAMS += coresched
+MANPAGES += schedutils/coresched.1
+dist_noinst_DATA += schedutils/coresched.1.adoc
+coresched_SOURCES = schedutils/coresched.c
+coresched_LDADD = $(LDADD) libcommon.la
+endif
diff --git a/schedutils/coresched.1.adoc b/schedutils/coresched.1.adoc
new file mode 100644
index 000000000..8a9c28846
--- /dev/null
+++ b/schedutils/coresched.1.adoc
@@ -0,0 +1,139 @@
+//po4a: entry man manual
+////
+coresched(1) manpage
+////
+= coresched(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: coresched
+:colon: :
+:copyright: ©
+
+== NAME
+
+coresched - manage core scheduling cookies for tasks
+
+== SYNOPSIS
+
+*{command}* [*get*] [*-s* _pid_]
+
+*{command}* *new* [*-t* _type_] *-d* _pid_
+
+*{command}* *new* [*-t* _type_] \-- _command_ [_argument_...]
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] *-d* _pid_
+
+*{command}* *copy* [*-s* _pid_] [*-t* _type_] \-- _command_ [_argument_...]
+
+== DESCRIPTION
+The *{command}* command is used to retrieve or modify the core scheduling cookies of a running process given its _pid_, or to spawn a new _command_ with core scheduling cookies.
+
+Core scheduling permits the definition of groups of tasks that are allowed to share a physical core.
+This is done by assigning a cookie to each task.
+Only tasks have the same cookie are allowed to be scheduled on the same physical core.
+
+It is possible to either assign a new random cookie to a task, or copy a cookie from another task. It is not possible to choose the value of the cookie.
+
+== FUNCTIONS
+*get*::
+Retrieve the core scheduling cookie of the PID specified in *-s*.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+*new*::
+Assign a new cookie to an existing PID specified in *-d*, or execute _command_ with a new cookie.
+
+*copy*::
+Copy the cookie from an existing PID (*-s*) to another PID (*-d*), or execute _command_ with that cookie.
+If *-s* is omitted, it will get the cookie of the current *{command}* process.
+
+If no function is specified, it will run the *get* function.
+
+== OPTIONS
+*-s*, *--source* _PID_::
+Which _PID_ to get the cookie from.
+If this option is omitted, it will get the cookie from the current *{command}* process.
+
+*-d*, *--dest* _PID_::
+Which _PID_ to modify the cookie of.
+
+*-t*, *--dest-type* _TYPE_::
+The type of the PID whose cookie will be modified. This can be one of three values:
+- *pid*, or process ID
+- *tgid*, or thread group ID (default value)
+- *pgid*, or process group ID
+
+*-v*, *--verbose*::
+Show extra information when modifying cookies of tasks.
+
+*-h*, *--help*::
+Display help text and exit.
+
+*-V*, *--version*::
+Print version and exit.
+
+== EXAMPLES
+Get the core scheduling cookie of the {command} task itself, usually inherited from its parent{colon}::
+*{command} get*
+
+Get the core scheduling cookie of a task with PID _123_{colon}::
+*{command} get -s* _123_
+
+Give a task with PID _123_ a new core scheduling cookie{colon}::
+*{command} new -d* _123_
+
+Spawn a new task with a new core scheduling cookie{colon}::
+*{command} new* \-- _command_ [_argument_...]
+
+Copy the cookie from the current {command} process another task with pid _456_{colon}::
+*{command} copy -d* _456_
+
+Copy the cookie from a task with pid _123_ to another task with pid _456_{colon}::
+*{command} copy -s* _123_ *-d* _456_
+
+Copy the cookie from a task with pid _123_ to a new task _command_{colon}::
+*{command} copy -s* _123_ \-- _command_ [_argument_...]
+
+Copy the cookie from a task with pid _123_ to the process group ID _456_{colon}::
+*{command} copy -s* _123_ *-t* _pgid_ *-d* _456_
+
+== PERMISSIONS
+Retrieving or modifying the core scheduling cookie of a process requires *PTRACE_MODE_READ_REALCREDS* ptrace access to that process.
+See the section "Ptrace access mode checking" in *ptrace*(2) for more information.
+
+== RETURN VALUE
+On success, *{command}* returns 0.
+If *{command}* fails, it will print an error and return 1.
+
+If a _command_ is being executed, the return value of *{command}* will be the return value of _command_.
+
+== NOTES
+*{command}* requires core scheduling support in the kernel.
+This can be enabled via the *CONFIG_SCHED_CORE* kernel config option.
+
+== AUTHORS
+mailto:thijs@raymakers.nl[Thijs Raymakers],
+mailto:pauld@redhat.com[Phil Auld]
+
+== COPYRIGHT
+
+Copyright {copyright} 2024 Thijs Raymakers and Phil Auld. This is free software licensed under the EUPL.
+
+== SEE ALSO
+*chrt*(1),
+*nice*(1),
+*renice*(1),
+*taskset*(1),
+*ptrace*(2),
+*sched*(7)
+
+The Linux kernel source files _Documentation/admin-guide/hw-vuln/core-scheduling.rst_
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
new file mode 100644
index 000000000..9d8be3e12
--- /dev/null
+++ b/schedutils/coresched.c
@@ -0,0 +1,363 @@
+/**
+ * SPDX-License-Identifier: EUPL-1.2
+ *
+ * coresched.c - manage core scheduling cookies for tasks
+ *
+ * Copyright (C) 2024 Thijs Raymakers, Phil Auld
+ * Licensed under the EUPL v1.2
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+
+// These definitions might not be defined in the header files, even if the
+// prctl interface in the kernel accepts them as valid.
+#ifndef PR_SCHED_CORE
+	#define PR_SCHED_CORE 62
+#endif
+#ifndef PR_SCHED_CORE_GET
+	#define PR_SCHED_CORE_GET 0
+#endif
+#ifndef PR_SCHED_CORE_CREATE
+	#define PR_SCHED_CORE_CREATE 1
+#endif
+#ifndef PR_SCHED_CORE_SHARE_TO
+	#define PR_SCHED_CORE_SHARE_TO 2
+#endif
+#ifndef PR_SCHED_CORE_SHARE_FROM
+	#define PR_SCHED_CORE_SHARE_FROM 3
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+	#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+	#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+	#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int sched_core_scope;
+typedef unsigned long long sched_core_cookie;
+typedef enum {
+	SCHED_CORE_CMD_GET,
+	SCHED_CORE_CMD_NEW,
+	SCHED_CORE_CMD_COPY,
+} sched_core_cmd;
+
+struct args {
+	pid_t src;
+	pid_t dest;
+	sched_core_scope type;
+	sched_core_cmd cmd;
+	int exec_argv_offset;
+};
+
+static bool sched_core_verbose = false;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	fputs(USAGE_HEADER, stdout);
+	fprintf(stdout, _(" %s [get] [--source <PID>]\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout, _(" %s new [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] --dest <PID>\n"),
+		program_invocation_short_name);
+	fprintf(stdout,
+		_(" %s copy [--source <PID>] [-t <TYPE>] -- PROGRAM [ARGS...]\n"),
+		program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_("Manage core scheduling cookies for tasks."), stdout);
+
+	fputs(USAGE_FUNCTIONS, stdout);
+	fputsln(_(" get                      retrieve the core scheduling cookie of a PID"),
+		stdout);
+	fputsln(_(" new                      assign a new core scheduling cookie to an existing\n"
+		  "                            PID or execute a program with a new cookie"),
+		stdout);
+	fputsln(_(" copy                     copy the core scheduling cookie from an existing PID\n"
+		  "                            to another PID, or execute a program with that\n"
+		  "                            copied cookie"),
+		stdout);
+
+	fputs(USAGE_OPTIONS, stdout);
+	fprintf(stdout,
+		_(" -s, --source <PID>       which PID to get the cookie from\n"
+		  "                            If omitted, it is the PID of %s itself\n"),
+		program_invocation_short_name);
+	fputsln(_(" -d, --dest <PID>         which PID to modify the cookie of\n"),
+		stdout);
+	fputsln(_(" -t, --dest-type <TYPE>   type of the destination PID, or the type of the PID\n"
+		  "                            when a new core scheduling cookie is created.\n"
+		  "                            Can be one of the following: pid, tgid or pgid.\n"
+		  "                            The default is tgid."),
+		stdout);
+	fputs(USAGE_SEPARATOR, stdout);
+	fputsln(_(" -v, --verbose      verbose"), stdout);
+	fprintf(stdout, USAGE_HELP_OPTIONS(20));
+	fprintf(stdout, USAGE_MAN_TAIL("coresched(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+#define bad_usage(FMT...)                 \
+	do {                              \
+		warnx(FMT);               \
+		errtryhelp(EXIT_FAILURE); \
+	} while (0)
+
+static sched_core_cookie core_sched_get_cookie(pid_t pid)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		err(EXIT_FAILURE, _("Failed to get cookie from PID %d"), pid);
+	return cookie;
+}
+
+static void core_sched_create_cookie(pid_t pid, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, pid, type, 0))
+		err(EXIT_FAILURE, _("Failed to create cookie for PID %d"), pid);
+}
+
+static void core_sched_pull_cookie(pid_t from)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0))
+		err(EXIT_FAILURE, _("Failed to pull cookie from PID %d"), from);
+}
+
+static void core_sched_push_cookie(pid_t to, sched_core_scope type)
+{
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0))
+		err(EXIT_FAILURE, _("Failed to push cookie to PID %d"), to);
+}
+
+static void core_sched_copy_cookie(pid_t from, pid_t to,
+				   sched_core_scope to_type)
+{
+	core_sched_pull_cookie(from);
+	core_sched_push_cookie(to, to_type);
+
+	if (sched_core_verbose) {
+		sched_core_cookie before = core_sched_get_cookie(from);
+		warnx(_("copied cookie 0x%llx from PID %d to PID %d"), before,
+		      from, to);
+	}
+}
+
+static void core_sched_get_and_print_cookie(pid_t pid)
+{
+	if (sched_core_verbose) {
+		sched_core_cookie after = core_sched_get_cookie(pid);
+		warnx(_("set cookie of PID %d to 0x%llx"), pid, after);
+	}
+}
+
+static void core_sched_exec_with_cookie(struct args *args, char **argv)
+{
+	// Move the argument list to the first argument of the program
+	argv = &argv[args->exec_argv_offset];
+
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->src) {
+		core_sched_pull_cookie(args->src);
+		core_sched_get_and_print_cookie(args->src);
+	} else {
+		pid_t pid = getpid();
+		core_sched_create_cookie(pid, args->type);
+		core_sched_get_and_print_cookie(pid);
+	}
+
+	if (execvp(argv[0], argv))
+		errexec(argv[0]);
+}
+
+// There are two failure conditions for the core scheduling prctl calls
+// that rely on the environment in which coresched is running.
+// 1. If PR_SCHED_CORE is not recognized, or not supported on this system,
+//    then prctl will set errno to EINVAL. Assuming all other operands of
+//    prctl are valid, we can use errno==EINVAL as a check to see whether
+//    core scheduling is available on this system.
+// 2. prctl sets errno to ENODEV if SMT is not available on this system,
+//    either because SMT support has been disabled in the kernel, or because
+//    the hardware doesn't support it.
+static bool is_core_sched_supported(void)
+{
+	sched_core_cookie cookie = 0;
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, getpid(),
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie))
+		if (errno == EINVAL || errno == ENODEV)
+			return false;
+
+	return true;
+}
+
+static sched_core_scope parse_core_sched_type(char *str)
+{
+	if (!strcmp(str, "pid"))
+		return PR_SCHED_CORE_SCOPE_THREAD;
+	else if (!strcmp(str, "tgid"))
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
+	else if (!strcmp(str, "pgid"))
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
+
+	bad_usage(_("'%s' is an invalid option. Must be one of pid/tgid/pgid"),
+		  str);
+}
+
+static void parse_and_verify_arguments(int argc, char **argv, struct args *args)
+{
+	int c;
+
+	static const struct option longopts[] = {
+		{ "source", required_argument, NULL, 's' },
+		{ "dest", required_argument, NULL, 'd' },
+		{ "dest-type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
+		{ NULL, 0, NULL, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "s:d:t:vVh", longopts, NULL)) != -1)
+		switch (c) {
+		case 's':
+			args->src = strtopid_or_err(
+				optarg,
+				_("Failed to parse PID for -s/--source"));
+			break;
+		case 'd':
+			args->dest = strtopid_or_err(
+				optarg, _("Failed to parse PID for -d/--dest"));
+			break;
+		case 't':
+			args->type = parse_core_sched_type(optarg);
+			break;
+		case 'v':
+			sched_core_verbose = true;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+
+	if (argc <= optind) {
+		args->cmd = SCHED_CORE_CMD_GET;
+	} else {
+		if (!strcmp(argv[optind], "get"))
+			args->cmd = SCHED_CORE_CMD_GET;
+		else if (!strcmp(argv[optind], "new"))
+			args->cmd = SCHED_CORE_CMD_NEW;
+		else if (!strcmp(argv[optind], "copy"))
+			args->cmd = SCHED_CORE_CMD_COPY;
+		else
+			bad_usage(_("Unknown function"));
+
+		// Since we parsed an extra "option" outside of getopt_long, we have to
+		// increment optind manually.
+		++optind;
+	}
+
+	if (args->cmd == SCHED_CORE_CMD_GET && args->dest)
+		bad_usage(_("get does not accept the --dest option"));
+
+	if (args->cmd == SCHED_CORE_CMD_NEW && args->src)
+		bad_usage(_("new does not accept the --source option"));
+
+	// If the -s/--source option is not specified, it defaults to the PID
+	// of the current coresched process
+	if (args->cmd != SCHED_CORE_CMD_NEW && !args->src)
+		args->src = getpid();
+
+	// More arguments have been passed, which means that the user wants to run
+	// another program with a core scheduling cookie.
+	if (argc > optind) {
+		switch (args->cmd) {
+		case SCHED_CORE_CMD_GET:
+			bad_usage(_("bad usage of the get function"));
+			break;
+		case SCHED_CORE_CMD_NEW:
+			if (args->dest)
+				bad_usage(_(
+					"new requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		case SCHED_CORE_CMD_COPY:
+			if (args->dest)
+				bad_usage(_(
+					"copy requires either a -d/--dest or a command"));
+			else
+				args->exec_argv_offset = optind;
+			break;
+		}
+	} else {
+		if (args->cmd == SCHED_CORE_CMD_NEW && !args->dest)
+			bad_usage(_(
+				"new requires either a -d/--dest or a command"));
+		if (args->cmd == SCHED_CORE_CMD_COPY && !args->dest)
+			bad_usage(_(
+				"copy requires either a -d/--dest or a command"));
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct args args = { .type = PR_SCHED_CORE_SCOPE_THREAD_GROUP };
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	parse_and_verify_arguments(argc, argv, &args);
+
+	if (!is_core_sched_supported())
+		errx(EXIT_FAILURE,
+		     _("Core scheduling is not supported on this system. Either SMT "
+		       "is unavailable or your kernel does not support CONFIG_SCHED_CORE."));
+
+	sched_core_cookie cookie;
+
+	switch (args.cmd) {
+	case SCHED_CORE_CMD_GET:
+		cookie = core_sched_get_cookie(args.src);
+		printf(_("cookie of pid %d is 0x%llx\n"), args.src, cookie);
+		break;
+	case SCHED_CORE_CMD_NEW:
+		if (args.exec_argv_offset) {
+			core_sched_exec_with_cookie(&args, argv);
+		} else {
+			core_sched_create_cookie(args.dest, args.type);
+			core_sched_get_and_print_cookie(args.dest);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (args.exec_argv_offset)
+			core_sched_exec_with_cookie(&args, argv);
+		else
+			core_sched_copy_cookie(args.src, args.dest, args.type);
+		break;
+	default:
+		usage();
+	}
+}
diff --git a/tests/commands.sh b/tests/commands.sh
index 5674c5ff0..9eef92ccb 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -71,6 +71,7 @@ TS_CMD_COLCRT=${TS_CMD_COLCRT:-"${ts_commandsdir}colcrt"}
 TS_CMD_COLRM=${TS_CMD_COLRM:-"${ts_commandsdir}colrm"}
 TS_CMD_COL=${TS_CMD_COL:-"${ts_commandsdir}col"}
 TS_CMD_COLUMN=${TS_CMD_COLUMN:-"${ts_commandsdir}column"}
+TS_CMD_CORESCHED=${TS_CMD_CORESCHED:-"${ts_commandsdir}coresched"}
 TS_CMD_ENOSYS=${TS_CMD_ENOSYS-"${ts_commandsdir}enosys"}
 TS_CMD_EJECT=${TS_CMD_EJECT-"${ts_commandsdir}eject"}
 TS_CMD_EXCH=${TS_CMD_EXCH-"${ts_commandsdir}exch"}
diff --git a/tests/expected/schedutils/coresched-copy-from-child-to-parent b/tests/expected/schedutils/coresched-copy-from-child-to-parent
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-child-to-parent
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
new file mode 100644
index 000000000..ecfc41142
--- /dev/null
+++ b/tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
@@ -0,0 +1 @@
+SAME_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-own-pid b/tests/expected/schedutils/coresched-get-cookie-own-pid
new file mode 100644
index 000000000..84f182cbe
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-own-pid
@@ -0,0 +1 @@
+cookie of pid OWN_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-get-cookie-parent-pid b/tests/expected/schedutils/coresched-get-cookie-parent-pid
new file mode 100644
index 000000000..e183e0402
--- /dev/null
+++ b/tests/expected/schedutils/coresched-get-cookie-parent-pid
@@ -0,0 +1 @@
+cookie of pid PARENT_PID is PARENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-new-child-with-new-cookie b/tests/expected/schedutils/coresched-new-child-with-new-cookie
new file mode 100644
index 000000000..5b9c40052
--- /dev/null
+++ b/tests/expected/schedutils/coresched-new-child-with-new-cookie
@@ -0,0 +1 @@
+DIFFERENT_COOKIE
diff --git a/tests/expected/schedutils/coresched-set-cookie-parent-pid.err b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/coresched-set-cookie-parent-pid.err
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/expected/schedutils/set-cookie-parent-pid b/tests/expected/schedutils/set-cookie-parent-pid
new file mode 100644
index 000000000..e7318ffc2
--- /dev/null
+++ b/tests/expected/schedutils/set-cookie-parent-pid
@@ -0,0 +1 @@
+coresched: set cookie of PID PARENT_PID to PARENT_COOKIE
diff --git a/tests/ts/schedutils/coresched b/tests/ts/schedutils/coresched
new file mode 100755
index 000000000..2ab0803bd
--- /dev/null
+++ b/tests/ts/schedutils/coresched
@@ -0,0 +1,83 @@
+#!/bin/bash
+# SPDX-License-Identifier: EUPL-1.2
+#
+# This file is part of util-linux
+#
+# Copyright (C) 2024 Thijs Raymakers
+# Licensed under the EUPL v1.2
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="coresched"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_CORESCHED"
+ts_check_prog "tee"
+ts_check_prog "sed"
+
+# If coresched cannot succesfully run, skip the test suite
+CORESCHED_TEST_KERNEL_SUPPORT_CMD=$($TS_CMD_CORESCHED 2>&1)
+if [[ $CORESCHED_TEST_KERNEL_SUPPORT_CMD == *"CONFIG_SCHED_CORE"* ]]; then
+  ts_skip "Kernel has no CONFIG_SCHED_CORE support or SMT is not available"
+fi
+
+# The output of coresched contains PIDs and core scheduling cookies, both of which should be
+# assumed to be random values as we have no control over them. The tests replace these values
+# with sed before writing them to the output file, so it can match the expected output file.
+# - The PID of this bash script is replaced with the placeholder `OWN_PID`
+# - The core scheduling cookie of this bash script is replaced by `COOKIE`
+# - Any other cookie is replaced by `DIFFERENT_COOKIE`
+# The behavior of coresched does not depend on the exact values of these cookies, so using
+# placeholder values does not change the behavior tests.
+ts_init_subtest "set-cookie-parent-pid"
+CORESCHED_OUTPUT=$( ($TS_CMD_CORESCHED -v new -d $$ \
+  | tee -a "$TS_OUTPUT") 3>&1 1>&2 2>&3 \
+  | sed "s/$$/PARENT_PID/g")
+CORESCHED_PARENT_COOKIE=$(echo "$CORESCHED_OUTPUT" | sed 's/^.*\(0x.*$\)/\1/g')
+if [ -z "$CORESCHED_PARENT_COOKIE" ]; then
+  ts_failed "empty value for CORESCHED_PARENT_COOKIE"
+fi
+CORESCHED_OUTPUT=$(echo "$CORESCHED_OUTPUT" \
+  | sed "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g")
+echo "$CORESCHED_OUTPUT" >> "$TS_ERRLOG"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-parent-pid"
+$TS_CMD_CORESCHED get -s $$ 2>> "$TS_ERRLOG" \
+  | sed -e "s/$$/PARENT_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "get-cookie-own-pid"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e "s/pid [0-9]\+/pid OWN_PID/g" \
+        -e "s/$CORESCHED_PARENT_COOKIE/PARENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "new-child-with-new-cookie"
+$TS_CMD_CORESCHED new -- "$TS_CMD_CORESCHED" get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-parent-to-nested-child"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s $$ -- $TS_CMD_CORESCHED get" \
+2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "copy-from-child-to-parent"
+$TS_CMD_CORESCHED new -- /bin/bash -c \
+  "$TS_CMD_CORESCHED copy -s \$\$ -d $$"
+$TS_CMD_CORESCHED get 2>> "$TS_ERRLOG" \
+  | sed -e 's/^.*\(0x.*$\)/\1/g' \
+        -e "s/$CORESCHED_PARENT_COOKIE/SAME_COOKIE/g" \
+        -e "s/0x.*$/DIFFERENT_COOKIE/g" >> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 42+ messages in thread

* Re: [PATCH v12] coresched: Manage core scheduling cookies for tasks
  2024-04-25 16:22                                                     ` [PATCH v12] " Thijs Raymakers
@ 2024-04-26  7:59                                                       ` Karel Zak
  2024-04-26 18:03                                                       ` Thomas Weißschuh
  1 sibling, 0 replies; 42+ messages in thread
From: Karel Zak @ 2024-04-26  7:59 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: thomas, util-linux, Phil Auld

On Thu, Apr 25, 2024 at 06:22:25PM +0200, Thijs Raymakers wrote:
> I agree, it would be better to handle it there. The error message "No
> device available" you would get otherwise is a bit cryptic so this
> is an improvement.
> 
> I've removed the ts_skip and added the case there.

PR updated https://github.com/util-linux/util-linux/pull/2990

    Karel
-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com


^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v12] coresched: Manage core scheduling cookies for tasks
  2024-04-25 16:22                                                     ` [PATCH v12] " Thijs Raymakers
  2024-04-26  7:59                                                       ` Karel Zak
@ 2024-04-26 18:03                                                       ` Thomas Weißschuh
  2024-04-29  8:28                                                         ` Karel Zak
  1 sibling, 1 reply; 42+ messages in thread
From: Thomas Weißschuh @ 2024-04-26 18:03 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux, Phil Auld

Thanks for all the work.

On 2024-04-25 18:22:25+0000, Thijs Raymakers wrote:
> Co-authored-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Phil Auld <pauld@redhat.com>
> Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>

Reviewed-by: Thomas Weißschuh <thomas@t-8ch.de>

> ---
> 
>  .gitignore                                    |   1 +
>  bash-completion/coresched                     |   0

Don't forget this :-)

>  configure.ac                                  |  12 +-
>  meson.build                                   |  16 +-
>  meson_options.txt                             |   2 +-
>  schedutils/Makemodule.am                      |   8 +
>  schedutils/coresched.1.adoc                   | 139 +++++++
>  schedutils/coresched.c                        | 363 ++++++++++++++++++
>  tests/commands.sh                             |   1 +
>  .../coresched-copy-from-child-to-parent       |   1 +
>  ...coresched-copy-from-parent-to-nested-child |   1 +
>  .../schedutils/coresched-get-cookie-own-pid   |   1 +
>  .../coresched-get-cookie-parent-pid           |   1 +
>  .../coresched-new-child-with-new-cookie       |   1 +
>  .../coresched-set-cookie-parent-pid.err       |   1 +
>  .../expected/schedutils/set-cookie-parent-pid |   1 +
>  tests/ts/schedutils/coresched                 |  83 ++++
>  17 files changed, 626 insertions(+), 6 deletions(-)
>  create mode 100644 bash-completion/coresched
>  create mode 100644 schedutils/coresched.1.adoc
>  create mode 100644 schedutils/coresched.c
>  create mode 100644 tests/expected/schedutils/coresched-copy-from-child-to-parent
>  create mode 100644 tests/expected/schedutils/coresched-copy-from-parent-to-nested-child
>  create mode 100644 tests/expected/schedutils/coresched-get-cookie-own-pid
>  create mode 100644 tests/expected/schedutils/coresched-get-cookie-parent-pid
>  create mode 100644 tests/expected/schedutils/coresched-new-child-with-new-cookie
>  create mode 100644 tests/expected/schedutils/coresched-set-cookie-parent-pid.err
>  create mode 100644 tests/expected/schedutils/set-cookie-parent-pid
>  create mode 100755 tests/ts/schedutils/coresched



^ permalink raw reply	[flat|nested] 42+ messages in thread

* Re: [PATCH v12] coresched: Manage core scheduling cookies for tasks
  2024-04-26 18:03                                                       ` Thomas Weißschuh
@ 2024-04-29  8:28                                                         ` Karel Zak
  0 siblings, 0 replies; 42+ messages in thread
From: Karel Zak @ 2024-04-29  8:28 UTC (permalink / raw)
  To: Thomas Weißschuh; +Cc: Thijs Raymakers, util-linux, Phil Auld

On Fri, Apr 26, 2024 at 08:03:35PM +0200, Thomas Weißschuh wrote:
> Thanks for all the work.
> 
> On 2024-04-25 18:22:25+0000, Thijs Raymakers wrote:
> > Co-authored-by: Phil Auld <pauld@redhat.com>
> > Signed-off-by: Phil Auld <pauld@redhat.com>
> > Signed-off-by: Thijs Raymakers <thijs@raymakers.nl>
> 
> Reviewed-by: Thomas Weißschuh <thomas@t-8ch.de>

Merged. Thanks!

    Karel

-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com


^ permalink raw reply	[flat|nested] 42+ messages in thread

end of thread, other threads:[~2024-04-29  8:28 UTC | newest]

Thread overview: 42+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-16 17:10 [PATCH RFC] coresched: Manage core scheduling cookies for tasks Thijs Raymakers
2024-03-24 20:07 ` Karel Zak
2024-03-26 14:41   ` Phil Auld
2024-03-26 14:45     ` Phil Auld
2024-03-26 17:49     ` [PATCH 0/1] " Thijs Raymakers
2024-03-26 18:13       ` Phil Auld
2024-03-27 12:43         ` [PATCH v2 " Thijs Raymakers
2024-03-27 13:21           ` Phil Auld
2024-03-27 14:18             ` Phil Auld
2024-03-27 15:30               ` [PATCH v3] " Thijs Raymakers
2024-04-01 17:18                 ` Thomas Weißschuh
2024-04-04 22:03                   ` [PATCH v4] " Thijs Raymakers
2024-04-05  6:26                     ` Thomas Weißschuh
2024-04-05 14:14                       ` Phil Auld
2024-04-08 21:16                         ` [PATCH v5] " Thijs Raymakers
2024-04-09  6:12                           ` Thomas Weißschuh
2024-04-09 11:55                             ` [PATCH v6] " Thijs Raymakers
2024-04-09 13:44                               ` Karel Zak
2024-04-09 14:12                                 ` Phil Auld
2024-04-10 21:11                                 ` [PATCH v7] " Thijs Raymakers
2024-04-10 22:15                                   ` Phil Auld
2024-04-11 11:02                                     ` [PATCH v8] " Thijs Raymakers
2024-04-11 13:46                                       ` Phil Auld
2024-04-17 10:31                                         ` Karel Zak
2024-04-17 11:39                                           ` [PATCH v9] " Thijs Raymakers
2024-04-23  9:27                                             ` Karel Zak
2024-04-23 10:19                                             ` Thomas Weißschuh
2024-04-23 11:12                                               ` [PATCH v10] " Thijs Raymakers
2024-04-25 15:36                                                 ` [PATCH v11] " Thijs Raymakers
2024-04-25 15:51                                                   ` Thomas Weißschuh
2024-04-25 16:22                                                     ` [PATCH v12] " Thijs Raymakers
2024-04-26  7:59                                                       ` Karel Zak
2024-04-26 18:03                                                       ` Thomas Weißschuh
2024-04-29  8:28                                                         ` Karel Zak
2024-04-17 12:27                                           ` [PATCH v8] " Phil Auld
2024-03-27 12:43         ` [PATCH v2 1/1] " Thijs Raymakers
2024-03-27 14:09           ` Phil Auld
2024-03-26 19:09       ` [PATCH 0/1] " Phil Auld
2024-03-26 19:26         ` Thijs Raymakers
2024-03-26 20:16           ` Phil Auld
2024-03-26 20:17             ` Phil Auld
2024-03-26 17:49     ` [PATCH 1/1] " Thijs Raymakers

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.