Util-Linux package development
 help / color / mirror / Atom feed
* [RFC] ipcs for POSIX IPC
From: Prasanna Paithankar @ 2024-04-23 22:40 UTC (permalink / raw)
  To: util-linux

Greetings,
The 'ipcs' (and 'ipcrm') command provides information on (or removes
some) System V IPC resources. I'd like to know why no similar utility
for POSIX IPC has existed for a long time. I would like to know if
such a tool exists in case I missed it. If not, I will provide patches
to ipcs and ipcrm (or should I separate the functionality into a new
utility).

Yours sincerely
Prasanna Paithankar

^ permalink raw reply

* Re: umount -r broken due to "mountinfo unnecessary"
From: Krzysztof Olędzki @ 2024-04-23 14:35 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux
In-Reply-To: <20240423083358.2k532xl557meewws@ws.net.home>

On 23.04.2024 at 01:33, Karel Zak wrote:
> On Thu, Apr 18, 2024 at 01:00:56AM -0700, Krzysztof Olędzki wrote:
>> I noticed that "umount -r" does not work on my system for filesystems other than root:
> 
> Fixed: https://github.com/util-linux/util-linux/pull/2989

Perfect, thanks!

When is the next release planned? Also, do you expect a backport to stable/v2.39 and v2.39.5?

Thanks,
 Krzysztof 


^ permalink raw reply

* RFE: hardlink: support specifying max_size too?
From: Mikko Rantalainen @ 2024-04-23 13:58 UTC (permalink / raw)
  To: util-linux

I have huge directory hierarchies that I would like to run hardlink
against but comparing a lot of files against each other results in high
RAM usage because so much of the file metadata is kept in memory.

Could you add max_size (--maximum-size) option in addition to min_size
(--minimum-size)? This would allow splitting the work into small
fragments where hardlink only needs to process files in given range and
immediately ignore all other files. Or it could be used to run full
linking in multiple parallel tasks with sensible RAM requirements if you
can run hardlink without size limitations (e.g. one task for 1–1MB
files, another for 1MB–10MB and third task for files bigger than 10MB).

It might also make sense to reorder the test for filesize and regex
processing in inserter() because testing for size is probably faster
because the stat() has already been made. Currently the stats.files is
also increased for files that get ignored by size filter which may not
be intentional.

I think I could provide patches if I just know which Git repo I should
use as the basis. Is https://github.com/util-linux/util-linux the
correct one?

-- 
Mikko


^ permalink raw reply

* [PATCH v2] flock: add support for using fcntl() with open file description locks
From: Rasmus Villemoes @ 2024-04-23 12:08 UTC (permalink / raw)
  To: util-linux; +Cc: Masatake YAMATO, Karel Zak, Rasmus Villemoes

Currently, there is no way for shell scripts to safely access
resources protected by POSIX locking (fcntl with the F_SETLK/F_SETLKW
commands). For example, the glibc function lckpwdf(), used to
protect access to the /etc/shadow database, works by taking a
F_SETLKW on /etc/.pwd.lock .

Due to the odd semantics of POSIX locking (e.g. released when any file
descriptor associated to the inode is closed), we cannot usefully
directly expose the POSIX F_SETLK/F_SETLKW commands. However, linux
3.15 introduced F_OFD_SETLK[W], with semantics wrt. ownership and
release better matching those of flock(2), and crucially they do
conflict with locks obtained via F_SETLK[W]. With this, a shell script
can do

  exec 4> /etc/.pwd.lock
  flock --fcntl 4
  <access/modify /etc/shadow ...>
  flock --fcntl --unlock 4 # or just exit

without conflicting with passwd(1) or other utilities that
access/modify /etc/shadow.

No single-letter shorthand is defined for the option, because this is
somewhat low-level and the user really needs to know what he is doing.

Also, this leaves the door open for teaching --fcntl to accept an
optional argument: "ofd", the default, and "posix", should anyone find
a use for flock(1) taking a F_SETLK[W] lock.

Signed-off-by: Rasmus Villemoes <rasmus.villemoes@prevas.dk>
---
v2:

- Shorten option name to --fcntl instead of --fcntl-ofd.

- Use a do_lock() helper function switching on the API to use, making
  the while () condition easier to read and making it simpler to add
  the mentioned --fcntl=posix should the need arise.

- Fix up places that need HAVE_FCNTL_OFD_LOCKS guarding.

 configure.ac      |  6 ++++
 meson.build       |  3 ++
 sys-utils/flock.c | 82 +++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/configure.ac b/configure.ac
index c302732e7..441b09440 100644
--- a/configure.ac
+++ b/configure.ac
@@ -434,6 +434,12 @@ AC_CHECK_DECLS([PR_REP_CAPACITY], [], [], [
 	#include <linux/pr.h>
 ])
 
+AC_CHECK_DECL([F_OFD_SETLK],
+	[AC_DEFINE([HAVE_FCNTL_OFD_LOCKS], [1],
+	[Define to 1 if fcntl.h defines F_OFD_ constants])], [], [
+#include <fcntl.h>
+])
+
 AC_CHECK_HEADERS([security/openpam.h], [], [], [
 #ifdef HAVE_SECURITY_PAM_APPL_H
 #include <security/pam_appl.h>
diff --git a/meson.build b/meson.build
index 99126f7aa..004c849f1 100644
--- a/meson.build
+++ b/meson.build
@@ -704,6 +704,9 @@ conf.set('HAVE_DECL_BLK_ZONE_REP_CAPACITY', have ? 1 : false)
 have = cc.has_header_symbol('linux/pr.h', 'PR_REP_CAPACITY')
 conf.set('HAVE_DECL_PR_REP_CAPACITY', have ? 1 : false)
 
+have = cc.has_header_symbol('fcntl.h', 'F_OFD_SETLK', args: '-D_GNU_SOURCE')
+conf.set('HAVE_FCNTL_OFD_LOCKS', have ? 1 : false)
+
 code = '''
 #include <time.h>
 #if !@0@
diff --git a/sys-utils/flock.c b/sys-utils/flock.c
index 7d878ff81..17088ce7e 100644
--- a/sys-utils/flock.c
+++ b/sys-utils/flock.c
@@ -48,6 +48,11 @@
 #include "monotonic.h"
 #include "timer.h"
 
+enum {
+	API_FLOCK,
+	API_FCNTL_OFD,
+};
+
 static void __attribute__((__noreturn__)) usage(void)
 {
 	fputs(USAGE_HEADER, stdout);
@@ -70,6 +75,9 @@ static void __attribute__((__noreturn__)) usage(void)
 	fputs(_(  " -o, --close              close file descriptor before running command\n"), stdout);
 	fputs(_(  " -c, --command <command>  run a single command string through the shell\n"), stdout);
 	fputs(_(  " -F, --no-fork            execute command without forking\n"), stdout);
+#ifdef HAVE_FCNTL_OFD_LOCKS
+	fputs(_(  "     --fcntl              use fcntl(F_OFD_SETLK) rather than flock()\n"), stdout);
+#endif
 	fputs(_(  "     --verbose            increase verbosity\n"), stdout);
 	fputs(USAGE_SEPARATOR, stdout);
 	fprintf(stdout, USAGE_HELP_OPTIONS(26));
@@ -126,6 +134,53 @@ static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
 	_exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
 }
 
+#ifdef HAVE_FCNTL_OFD_LOCKS
+static int flock_to_fcntl_type(int op)
+{
+	switch (op) {
+	case LOCK_EX:
+		return F_WRLCK;
+	case LOCK_SH:
+		return F_RDLCK;
+	case LOCK_UN:
+		return F_UNLCK;
+	default:
+		errx(EX_SOFTWARE, _("internal error, unknown operation %d"), op);
+	}
+}
+
+static int fcntl_lock(int fd, int op, int block)
+{
+	struct flock arg = {
+		.l_type = flock_to_fcntl_type(op),
+		.l_whence = SEEK_SET,
+		.l_start = 0,
+		.l_len = 0,
+	};
+	int cmd = (block & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW;
+	return fcntl(fd, cmd, &arg);
+}
+#endif
+
+static int do_lock(int api, int fd, int op, int block)
+{
+	switch (api) {
+	case API_FLOCK:
+		return flock(fd, op | block);
+#ifdef HAVE_FCNTL_OFD_LOCKS
+	case API_FCNTL_OFD:
+		return fcntl_lock(fd, op, block);
+#endif
+	/*
+	 * Should never happen, api can never have values other than
+	 * API_*, and must be API_FLOCK when !HAVE_FCNTL_OFD_LOCKS.
+	 */
+	default:
+		errx(EX_SOFTWARE, _("internal error, unknown api %d"), api);
+	}
+}
+
+
 int main(int argc, char *argv[])
 {
 	struct ul_timer timer;
@@ -140,6 +195,7 @@ int main(int argc, char *argv[])
 	int no_fork = 0;
 	int status;
 	int verbose = 0;
+	int api = API_FLOCK;
 	struct timeval time_start = { 0 }, time_done = { 0 };
 	/*
 	 * The default exit code for lock conflict or timeout
@@ -149,7 +205,8 @@ int main(int argc, char *argv[])
 	char **cmd_argv = NULL, *sh_c_argv[4];
 	const char *filename = NULL;
 	enum {
-		OPT_VERBOSE = CHAR_MAX + 1
+		OPT_VERBOSE = CHAR_MAX + 1,
+		OPT_FCNTL,
 	};
 	static const struct option long_options[] = {
 		{"shared", no_argument, NULL, 's'},
@@ -163,6 +220,9 @@ int main(int argc, char *argv[])
 		{"close", no_argument, NULL, 'o'},
 		{"no-fork", no_argument, NULL, 'F'},
 		{"verbose", no_argument, NULL, OPT_VERBOSE},
+#ifdef HAVE_FCNTL_OFD_LOCKS
+		{"fcntl", no_argument, NULL, OPT_FCNTL},
+#endif
 		{"help", no_argument, NULL, 'h'},
 		{"version", no_argument, NULL, 'V'},
 		{NULL, 0, NULL, 0}
@@ -217,6 +277,11 @@ int main(int argc, char *argv[])
 			if (conflict_exit_code < 0 || conflict_exit_code > 255)
 				errx(EX_USAGE, _("exit code out of range (expected 0 to 255)"));
 			break;
+#ifdef HAVE_FCNTL_OFD_LOCKS
+		case OPT_FCNTL:
+			api = API_FCNTL_OFD;
+			break;
+#endif
 		case OPT_VERBOSE:
 			verbose = 1;
 			break;
@@ -234,6 +299,13 @@ int main(int argc, char *argv[])
 		errx(EX_USAGE,
 			_("the --no-fork and --close options are incompatible"));
 
+	/*
+	 * For fcntl(F_OFD_SETLK), an exclusive lock requires that the
+	 * file is open for write.
+	 */
+	if (api != API_FLOCK && type == LOCK_EX)
+		open_flags = O_WRONLY;
+
 	if (argc > optind + 1) {
 		/* Run command */
 		if (!strcmp(argv[optind + 1], "-c") ||
@@ -280,9 +352,15 @@ int main(int argc, char *argv[])
 
 	if (verbose)
 		gettime_monotonic(&time_start);
-	while (flock(fd, type | block)) {
+	while (do_lock(api, fd, type, block)) {
 		switch (errno) {
 		case EWOULDBLOCK:
+			/*
+			 * Per the man page, for fcntl(), EACCES may
+			 * be returned and means the same as
+			 * EAGAIN/EWOULDBLOCK.
+			 */
+		case EACCES:
 			/* -n option set and failed to lock. */
 			if (verbose)
 				warnx(_("failed to get lock"));
-- 
2.40.1.1.g1c60b9335d


^ permalink raw reply related

* [PATCH v10] coresched: Manage core scheduling cookies for tasks
From: Thijs Raymakers @ 2024-04-23 11:12 UTC (permalink / raw)
  To: thomas; +Cc: kzak, util-linux, Thijs Raymakers, Phil Auld
In-Reply-To: <df7a25a0-7923-4f8b-a527-5e6f0064074d@t-8ch.de>

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

* Re: [PATCH v9] coresched: Manage core scheduling cookies for tasks
From: Thomas Weißschuh @ 2024-04-23 10:19 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: kzak, util-linux, Phil Auld
In-Reply-To: <20240417113932.14237-1-thijs@raymakers.nl>

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

* Re: [PATCH] flock: add support for using fcntl() with open file description locks
From: Karel Zak @ 2024-04-23  9:53 UTC (permalink / raw)
  To: rasmus.villemoes; +Cc: util-linux, Masatake YAMATO
In-Reply-To: <20240423095002.rpxqdoqpra5p3uaw@ws.net.home>

On Tue, Apr 23, 2024 at 11:50:05AM +0200, Karel Zak wrote:
> On Thu, Apr 18, 2024 at 02:33:54AM +0900, Masatake YAMATO wrote:

Oh, sorry, I replied to Masatake's email instead of Rasmus's. My notes
are for the original patch :-)

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


^ permalink raw reply

* Re: [PATCH] flock: add support for using fcntl() with open file description locks
From: Karel Zak @ 2024-04-23  9:50 UTC (permalink / raw)
  To: Masatake YAMATO; +Cc: rasmus.villemoes, util-linux
In-Reply-To: <20240418.023354.1867217317145795622.yamato@redhat.com>

On Thu, Apr 18, 2024 at 02:33:54AM +0900, Masatake YAMATO wrote:
> >  int main(int argc, char *argv[])
> >  {
> >  	struct ul_timer timer;
> > @@ -140,6 +175,7 @@ int main(int argc, char *argv[])
> >  	int no_fork = 0;
> >  	int status;
> >  	int verbose = 0;
> > +	int use_fcntl_ofd = 0;

What about to introduce "lock_method" variable and define

 enum {
    LOCK_BY_FLOCK
    LOCK_BY_FCNTL_OFD
 };

later you can easily extend it by LOCK_BY_FCNTL_POSIX. IMHO the code
will be more readable.

> >  	struct timeval time_start = { 0 }, time_done = { 0 };
> >  	/*
> >  	 * The default exit code for lock conflict or timeout
> > @@ -149,7 +185,8 @@ int main(int argc, char *argv[])
> >  	char **cmd_argv = NULL, *sh_c_argv[4];
> >  	const char *filename = NULL;
> >  	enum {
> > -		OPT_VERBOSE = CHAR_MAX + 1
> > +		OPT_VERBOSE = CHAR_MAX + 1,
> > +		OPT_FCNTL_OFD,
> >  	};
> >  	static const struct option long_options[] = {
> >  		{"shared", no_argument, NULL, 's'},
> > @@ -163,6 +200,7 @@ int main(int argc, char *argv[])
> >  		{"close", no_argument, NULL, 'o'},
> >  		{"no-fork", no_argument, NULL, 'F'},
> >  		{"verbose", no_argument, NULL, OPT_VERBOSE},
> > +		{"fcntl-ofd", no_argument, NULL, OPT_FCNTL_OFD},

I agree that the sort name --fcntl sounds better and it's extendable.

> >  		{"help", no_argument, NULL, 'h'},
> >  		{"version", no_argument, NULL, 'V'},
> >  		{NULL, 0, NULL, 0}
> > @@ -217,6 +255,11 @@ int main(int argc, char *argv[])
> >  			if (conflict_exit_code < 0 || conflict_exit_code > 255)
> >  				errx(EX_USAGE, _("exit code out of range (expected 0 to 255)"));
> >  			break;
> > +#ifdef HAVE_FCNTL_OFD_LOCKS
> > +		case OPT_FCNTL_OFD:
> > +			use_fcntl_ofd = 1;
> > +			break;
> > +#endif
> >  		case OPT_VERBOSE:
> >  			verbose = 1;
> >  			break;
> > @@ -234,6 +277,13 @@ int main(int argc, char *argv[])
> >  		errx(EX_USAGE,
> >  			_("the --no-fork and --close options are incompatible"));
> >  
> > +	/*
> > +	 * For fcntl(F_OFD_SETLK), an exclusive lock requires that the
> > +	 * file is open for write.
> > +	 */
> > +	if (use_fcntl_ofd && type == LOCK_EX)
> > +		open_flags = O_WRONLY;
> > +
> >  	if (argc > optind + 1) {
> >  		/* Run command */
> >  		if (!strcmp(argv[optind + 1], "-c") ||
> > @@ -280,9 +330,15 @@ int main(int argc, char *argv[])
> >  
> >  	if (verbose)
> >  		gettime_monotonic(&time_start);
> > -	while (flock(fd, type | block)) {
> > +	while (use_fcntl_ofd ? do_fcntl_lock(fd, type, block) : flock(fd, type | block)) {

Maybe we can move the locking to a function

    do_lock(fd, method, type, block)

and hide the necessary flock and fcntl details there, instead of
trying to do it in main().

    Karel


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


^ permalink raw reply

* Re: [PATCH v9] coresched: Manage core scheduling cookies for tasks
From: Karel Zak @ 2024-04-23  9:27 UTC (permalink / raw)
  To: Thijs Raymakers; +Cc: thomas, util-linux, Phil Auld
In-Reply-To: <20240417113932.14237-1-thijs@raymakers.nl>

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

* Re: umount -r broken due to "mountinfo unnecessary"
From: Karel Zak @ 2024-04-23  8:33 UTC (permalink / raw)
  To: Krzysztof Olędzki; +Cc: util-linux
In-Reply-To: <315f1f43-013f-48c9-9016-474dc9d53a04@ans.pl>

On Thu, Apr 18, 2024 at 01:00:56AM -0700, Krzysztof Olędzki wrote:
> I noticed that "umount -r" does not work on my system for filesystems other than root:

Fixed: https://github.com/util-linux/util-linux/pull/2989

    Karel

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


^ permalink raw reply

* Re: umount -r broken due to "mountinfo unnecessary"
From: Krzysztof Olędzki @ 2024-04-22 15:06 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux
In-Reply-To: <20240422114314.pu3domts67l7qzvh@ws.net.home>

On 22.04.2024 at 04:43, Karel Zak wrote:
> On Thu, Apr 18, 2024 at 01:00:56AM -0700, Krzysztof Olędzki wrote:
>> Clearly this is not the right fix, but perhaps something like this would be correct:
>>
>> @@ -275,6 +275,7 @@
>>       || mnt_context_is_lazy(cxt)
>>       || mnt_context_is_nocanonicalize(cxt)
>>       || mnt_context_is_loopdel(cxt)
>> +     || mnt_context_is_rdonly_umount(cxt)
>>       || mnt_safe_stat(tgt, &st) != 0 || !S_ISDIR(st.st_mode)
>>       || has_utab_entry(cxt, tgt))
>>        return 1; /* not found */
>>
>> I wonder if we just missed the mnt_context_is_rdonly_umount case in https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/commit/?id=6a52473ecd877227f6f7da2b95da0b51593ffec1?
> 
> Yes :-)
> 
> Nice debugging work, thanks!

Thanks!

> I will most likely use the bugfix that you have suggested. The
> long-term solution should be to use the new mount API for
> context_umount.c as well, as it allows for reconfiguring the
> mountpoint without having to worry about the source.
> 
>   attr.attr_set = MOUNT_ATTR_RDONLY;
> 
>   fd = open_tree(AT_FDCWD, target, 0);
>   mount_setattr(fd, "", AT_EMPTY_PATH, &attr, sizeof(attr));
>   close(fd);
> 
> However, we need to implement it in a backwardly compatible way for
> cases where there is the new API unsupported.

Right, and it seems it will need to live for many years to come given mount_setattr got added in Linux 5.12 with xfs/ext4/FAT support only and several other filesystems added in 5.15/5.18/5.19.

BTW: we probably did not notice it for that long because Linux kernel has a special handling for root filesystem, where failed umount is internally handled as remount read-only, and it is not very common to have /usr on a separate filesystem.

Thanks,
 Krzysztof


^ permalink raw reply

* Re: umount -r broken due to "mountinfo unnecessary"
From: Karel Zak @ 2024-04-22 11:43 UTC (permalink / raw)
  To: Krzysztof Olędzki; +Cc: util-linux
In-Reply-To: <315f1f43-013f-48c9-9016-474dc9d53a04@ans.pl>

On Thu, Apr 18, 2024 at 01:00:56AM -0700, Krzysztof Olędzki wrote:
> Clearly this is not the right fix, but perhaps something like this would be correct:
> 
> @@ -275,6 +275,7 @@
>       || mnt_context_is_lazy(cxt)
>       || mnt_context_is_nocanonicalize(cxt)
>       || mnt_context_is_loopdel(cxt)
> +     || mnt_context_is_rdonly_umount(cxt)
>       || mnt_safe_stat(tgt, &st) != 0 || !S_ISDIR(st.st_mode)
>       || has_utab_entry(cxt, tgt))
>        return 1; /* not found */
> 
> I wonder if we just missed the mnt_context_is_rdonly_umount case in https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/commit/?id=6a52473ecd877227f6f7da2b95da0b51593ffec1?

Yes :-)

Nice debugging work, thanks!

I will most likely use the bugfix that you have suggested. The
long-term solution should be to use the new mount API for
context_umount.c as well, as it allows for reconfiguring the
mountpoint without having to worry about the source.

  attr.attr_set = MOUNT_ATTR_RDONLY;

  fd = open_tree(AT_FDCWD, target, 0);
  mount_setattr(fd, "", AT_EMPTY_PATH, &attr, sizeof(attr));
  close(fd);

However, we need to implement it in a backwardly compatible way for
cases where there is the new API unsupported.

 Karel


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


^ permalink raw reply

* Re: [PATCH] flock: add support for using fcntl() with open file description locks
From: Rasmus Villemoes @ 2024-04-18  8:49 UTC (permalink / raw)
  To: util-linux; +Cc: Karel Zak, Masatake YAMATO
In-Reply-To: <20240417100948.75817-1-rasmus.villemoes@prevas.dk>

On 17/04/2024 12.09, Rasmus Villemoes wrote:

> I'm not at all married to the option name. I also considered just
> making it --fcntl, 

Thinking more about it, I think I prefer this slightly shorter name.

with the possiblity of making that grow an optional
> argument (for example --fcntl=posix with plain --fcntl being an alias
> for --fcntl=ofd) 

Not that I'm gonna implement this part immediately, but I do like that
it leaves the door open for it.

If no other comments appear in a day or two, I'll resend with that
change and the fix for guarding the maybe-unused helper function.

Rasmus


^ permalink raw reply

* Re: [PATCH] flock: add support for using fcntl() with open file description locks
From: Rasmus Villemoes @ 2024-04-18  8:12 UTC (permalink / raw)
  To: Masatake YAMATO; +Cc: util-linux, kzak
In-Reply-To: <20240418.023354.1867217317145795622.yamato@redhat.com>

On 17/04/2024 19.33, Masatake YAMATO wrote:
>> Currently, there is no way for shell scripts to safely access
>> resources protected by POSIX locking (fcntl with the F_SETLK/F_SETLKW
>> commands). For example, the glibc function lckpwdf(), used to
>> protect access to the /etc/shadow database, works by taking a
>> F_SETLKW on /etc/.pwd.lock .
>>
>> Due to the odd semantics of POSIX locking (e.g. released when any file
>> descriptor associated to the inode is closed), we cannot usefully
>> directly expose the POSIX F_SETLK/F_SETLKW commands. However, linux
>> 3.15 introduced F_OFD_SETLK[W], with semantics wrt. ownership and
>> release better matching those of flock(2), and crucially they do
>> conflict with locks obtained via F_SETLK[W]. With this, a shell script
>> can do
>>
>>   exec 4> /etc/.pwd.lock
>>   flock --fcntl-ofd 4
>>   <access/modify /etc/shadow ...>
>>   flock --fcntl-ofd --unlock 4 # or just exit
>>
>> without conflicting with passwd(1) or other utilities that
>> access/modify /etc/shadow.
>>
>> The option name is a bit verbose, and no single-letter shorthand is
>> defined, because this is somewhat low-level and the user really needs
>> to know what he is doing.
>>
>> Signed-off-by: Rasmus Villemoes <rasmus.villemoes@prevas.dk>
>>
>> ---
>>
>> Both my autotools and meson fu are weak to non-existing, so I don't
>> know if I've written the "test if the header exposes this macro"
>> correctly.
>>
>> I'm not at all married to the option name. I also considered just
>> making it --fcntl, with the possiblity of making that grow an optional
>> argument (for example --fcntl=posix with plain --fcntl being an alias
>> for --fcntl=ofd) should anyone ever figure out a use for the plain
>> F_SETLK, perhaps just for testing.
>>
>>
>>  configure.ac      |  6 +++++
>>  meson.build       |  3 +++
>>  sys-utils/flock.c | 60 +++++++++++++++++++++++++++++++++++++++++++++--
>>  3 files changed, 67 insertions(+), 2 deletions(-)
> 
> You may have to update sys-utils/flock.1.adoc and the completion rule bash-completion/flock
> when adding a new optoin.

I will send a separate patch with that once the option naming is settled
and the concept itself is accepted (not necessarily applied to master).

>>  code = '''
>>  #include <time.h>
>>  #if !@0@
>> diff --git a/sys-utils/flock.c b/sys-utils/flock.c
>> index 7d878ff81..40751517d 100644
>> --- a/sys-utils/flock.c
>> +++ b/sys-utils/flock.c
>> @@ -70,6 +70,9 @@ static void __attribute__((__noreturn__)) usage(void)
>>  	fputs(_(  " -o, --close              close file descriptor before running command\n"), stdout);
>>  	fputs(_(  " -c, --command <command>  run a single command string through the shell\n"), stdout);
>>  	fputs(_(  " -F, --no-fork            execute command without forking\n"), stdout);
>> +#ifdef HAVE_FCNTL_OFD_LOCKS
>> +	fputs(_(  "     --fcntl-ofd          use fcntl(F_OFD_SETLK) rather than flock()\n"), stdout);
>> +#endif
>>  	fputs(_(  "     --verbose            increase verbosity\n"), stdout);
>>  	fputs(USAGE_SEPARATOR, stdout);
>>  	fprintf(stdout, USAGE_HELP_OPTIONS(26));
>> @@ -126,6 +129,38 @@ static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
>>  	_exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
>>  }
>>  
>> +static int flock_to_fcntl_type(int op)
>> +{
>> +        switch (op) {
>> +                case LOCK_EX:
>> +                        return F_WRLCK;
>> +                case LOCK_SH:
>> +                        return F_RDLCK;
>> +                case LOCK_UN:
>> +                        return F_UNLCK;
>> +                default:
>> +			errx(EX_SOFTWARE, _("internal error, unknown operation %d"), op);
>> +        }
>> +}
> 
> Don't you need wrap flock_to_fcntl_type with #ifdef HAVE_FCNTL_OFD_LOCKS/#endif?

Well, the constants mentioned here are the same as used with posix
locking, and have been in glibc and kernel headers since forever. Only
the F_OFD_* ones are "newish". So I don't think this needs guarding due
to non-existence of the symbols. But I might need to guard the whole
function (or mark it maybe-unused) to avoid a defined-but-not-used warning.

I wasn't even sure if I actually needed a configure check and HAVE_*
guard at all; 3.15 is 10 years old by now, but I couldn't find any
mention of the oldest glibc/kernel that util-linux is supposed to build
against. Karel?

Rasmus


^ permalink raw reply

* umount -r broken due to "mountinfo unnecessary"
From: Krzysztof Olędzki @ 2024-04-18  8:00 UTC (permalink / raw)
  To: Karel Zak; +Cc: util-linux

Hi Karel,

I noticed that "umount -r" does not work on my system for filesystems other than root:

# umount -r /usr
umount: /usr: target is busy.

Yes, umount first tries the umount2 syscall, so "target is busy" is very expected, but there is no follow-up attempt to remount fs as read-only:

umount2("/usr", 0)                      = -1 EBUSY (Device or resource busy)
openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2998, ...}, AT_EMPTY_PATH) = 0
read(3, "# Locale name alias data base.\n#"..., 4096) = 2998
read(3, "", 4096)                       = 0
close(3)                                = 0
openat(AT_FDCWD, "/usr/share/locale/en_US/LC_MESSAGES/util-linux.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/share/locale/en/LC_MESSAGES/util-linux.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
write(2, "umount: ", 8umount: )                 = 8
write(2, "/usr: target is busy.", 21/usr: target is busy.)   = 21
write(2, "\n", 1
)                       = 1
dup(1)                                  = 3
close(3)                                = 0
dup(2)                                  = 3
close(3)                                = 0
exit_group(32)                          = ?
+++ exited with 32 +++

Looking at the code, "try remount read-only" section in do_umount() from libmount/src/context_umount.c seems to only be called if all these conditions are met:
 - rc < 0
 - cxt->syscall_status == -EBUSY
 - mnt_context_is_rdonly_umount(cxt)
 - src is not NULL

I added some debug code to do_umount() to see why it fails:
        DBG(CXT, ul_debugobj(cxt, "rc=%d, cxt->syscall_status=%d, mnt_context_is_rdonly_umount=%d, src=%s",
        rc, cxt->syscall_status, mnt_context_is_rdonly_umount(cxt), src));

Result:
17555: libmount:      CXT: [0x5638caf185b0]: rc=-1, cxt->syscall_status=-16, mnt_context_is_rdonly_umount=1, src=(null)

With this, I noticed the additional hint in the log coming from lookup_umount_fs_by_statfs():

17555: libmount:      CXT: [0x5638caf185b0]: umount: lookup FS
17555: libmount:      CXT: [0x5638caf185b0]:  lookup by statfs
17555: libmount:      CXT: [0x5638caf185b0]:   trying fstatfs()
17555: libmount:      CXT: [0x5638caf185b0]:   umount: disabling mountinfo

Adding "DBG(CXT, mnt_fs_print_debug(cxt->fs, stderr));" in do_umount() confirmed my assumptions:

19114: libmount:      CXT: ------ fs:
source: (null)
target: /usr
fstype: ext4

The problem seems to be that lookup_umount_fs() first calls lookup_umount_fs_by_statfs() and when it succeeds, we only get partial information - without the source.

Commenting the following section in lookup_umount_fs():

//      rc = lookup_umount_fs_by_statfs(cxt, tgt);
//      if (rc <= 0)
//              goto done;

... allows this to work and /usr gets re-mounted ro:

23821: libmount:   UPDATE: ------ fs:
source: /dev/mapper/VG0-usr
target: /usr
fstype: ext3
optstr: rw,defaults,data=journal,nodev,remount
VFS-optstr: rw,nodev,remount
FS-opstr: data=journal
user-optstr: defaults

umount2("/usr", 0)                      = -1 EBUSY (Device or resource busy)
mount("/dev/mapper/VG0-usr", "/usr", NULL, MS_RDONLY|MS_REMOUNT, NULL) = 0

Clearly this is not the right fix, but perhaps something like this would be correct:

@@ -275,6 +275,7 @@
      || mnt_context_is_lazy(cxt)
      || mnt_context_is_nocanonicalize(cxt)
      || mnt_context_is_loopdel(cxt)
+     || mnt_context_is_rdonly_umount(cxt)
      || mnt_safe_stat(tgt, &st) != 0 || !S_ISDIR(st.st_mode)
      || has_utab_entry(cxt, tgt))
       return 1; /* not found */

I wonder if we just missed the mnt_context_is_rdonly_umount case in https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/commit/?id=6a52473ecd877227f6f7da2b95da0b51593ffec1?

Thanks,
 Krzysztof

^ permalink raw reply

* Re: [PATCH] flock: add support for using fcntl() with open file description locks
From: Masatake YAMATO @ 2024-04-17 17:33 UTC (permalink / raw)
  To: rasmus.villemoes; +Cc: util-linux, kzak
In-Reply-To: <20240417100948.75817-1-rasmus.villemoes@prevas.dk>

> Currently, there is no way for shell scripts to safely access
> resources protected by POSIX locking (fcntl with the F_SETLK/F_SETLKW
> commands). For example, the glibc function lckpwdf(), used to
> protect access to the /etc/shadow database, works by taking a
> F_SETLKW on /etc/.pwd.lock .
> 
> Due to the odd semantics of POSIX locking (e.g. released when any file
> descriptor associated to the inode is closed), we cannot usefully
> directly expose the POSIX F_SETLK/F_SETLKW commands. However, linux
> 3.15 introduced F_OFD_SETLK[W], with semantics wrt. ownership and
> release better matching those of flock(2), and crucially they do
> conflict with locks obtained via F_SETLK[W]. With this, a shell script
> can do
> 
>   exec 4> /etc/.pwd.lock
>   flock --fcntl-ofd 4
>   <access/modify /etc/shadow ...>
>   flock --fcntl-ofd --unlock 4 # or just exit
> 
> without conflicting with passwd(1) or other utilities that
> access/modify /etc/shadow.
> 
> The option name is a bit verbose, and no single-letter shorthand is
> defined, because this is somewhat low-level and the user really needs
> to know what he is doing.
> 
> Signed-off-by: Rasmus Villemoes <rasmus.villemoes@prevas.dk>
> 
> ---
> 
> Both my autotools and meson fu are weak to non-existing, so I don't
> know if I've written the "test if the header exposes this macro"
> correctly.
> 
> I'm not at all married to the option name. I also considered just
> making it --fcntl, with the possiblity of making that grow an optional
> argument (for example --fcntl=posix with plain --fcntl being an alias
> for --fcntl=ofd) should anyone ever figure out a use for the plain
> F_SETLK, perhaps just for testing.
> 
> 
>  configure.ac      |  6 +++++
>  meson.build       |  3 +++
>  sys-utils/flock.c | 60 +++++++++++++++++++++++++++++++++++++++++++++--
>  3 files changed, 67 insertions(+), 2 deletions(-)

You may have to update sys-utils/flock.1.adoc and the completion rule bash-completion/flock
when adding a new optoin.

> diff --git a/configure.ac b/configure.ac
> index c302732e7..441b09440 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -434,6 +434,12 @@ AC_CHECK_DECLS([PR_REP_CAPACITY], [], [], [
>  	#include <linux/pr.h>
>  ])
>  
> +AC_CHECK_DECL([F_OFD_SETLK],
> +	[AC_DEFINE([HAVE_FCNTL_OFD_LOCKS], [1],
> +	[Define to 1 if fcntl.h defines F_OFD_ constants])], [], [
> +#include <fcntl.h>
> +])
> +
>  AC_CHECK_HEADERS([security/openpam.h], [], [], [
>  #ifdef HAVE_SECURITY_PAM_APPL_H
>  #include <security/pam_appl.h>
> diff --git a/meson.build b/meson.build
> index 99126f7aa..004c849f1 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -704,6 +704,9 @@ conf.set('HAVE_DECL_BLK_ZONE_REP_CAPACITY', have ? 1 : false)
>  have = cc.has_header_symbol('linux/pr.h', 'PR_REP_CAPACITY')
>  conf.set('HAVE_DECL_PR_REP_CAPACITY', have ? 1 : false)
>  
> +have = cc.has_header_symbol('fcntl.h', 'F_OFD_SETLK', args: '-D_GNU_SOURCE')
> +conf.set('HAVE_FCNTL_OFD_LOCKS', have ? 1 : false)
> +
>  code = '''
>  #include <time.h>
>  #if !@0@
> diff --git a/sys-utils/flock.c b/sys-utils/flock.c
> index 7d878ff81..40751517d 100644
> --- a/sys-utils/flock.c
> +++ b/sys-utils/flock.c
> @@ -70,6 +70,9 @@ static void __attribute__((__noreturn__)) usage(void)
>  	fputs(_(  " -o, --close              close file descriptor before running command\n"), stdout);
>  	fputs(_(  " -c, --command <command>  run a single command string through the shell\n"), stdout);
>  	fputs(_(  " -F, --no-fork            execute command without forking\n"), stdout);
> +#ifdef HAVE_FCNTL_OFD_LOCKS
> +	fputs(_(  "     --fcntl-ofd          use fcntl(F_OFD_SETLK) rather than flock()\n"), stdout);
> +#endif
>  	fputs(_(  "     --verbose            increase verbosity\n"), stdout);
>  	fputs(USAGE_SEPARATOR, stdout);
>  	fprintf(stdout, USAGE_HELP_OPTIONS(26));
> @@ -126,6 +129,38 @@ static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
>  	_exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
>  }
>  
> +static int flock_to_fcntl_type(int op)
> +{
> +        switch (op) {
> +                case LOCK_EX:
> +                        return F_WRLCK;
> +                case LOCK_SH:
> +                        return F_RDLCK;
> +                case LOCK_UN:
> +                        return F_UNLCK;
> +                default:
> +			errx(EX_SOFTWARE, _("internal error, unknown operation %d"), op);
> +        }
> +}

Don't you need wrap flock_to_fcntl_type with #ifdef HAVE_FCNTL_OFD_LOCKS/#endif?

> +static int do_fcntl_lock(int fd, int op, int block)
> +{
> +#ifdef HAVE_FCNTL_OFD_LOCKS
> +	struct flock arg = {
> +		.l_type = flock_to_fcntl_type(op),
> +		.l_whence = SEEK_SET,
> +		.l_start = 0,
> +		.l_len = 0,
> +	};
> +	int cmd = (block == LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW;
> +	return fcntl(fd, cmd, &arg);
> +#else
> +	/* Should never happen, nothing can ever set use_fcntl_ofd when !HAVE_FCNTL_OFD_LOCKS. */
> +	errno = ENOSYS;
> +	return -1;
> +#endif
> +}
> +
>  int main(int argc, char *argv[])
>  {
>  	struct ul_timer timer;
> @@ -140,6 +175,7 @@ int main(int argc, char *argv[])
>  	int no_fork = 0;
>  	int status;
>  	int verbose = 0;
> +	int use_fcntl_ofd = 0;
>  	struct timeval time_start = { 0 }, time_done = { 0 };
>  	/*
>  	 * The default exit code for lock conflict or timeout
> @@ -149,7 +185,8 @@ int main(int argc, char *argv[])
>  	char **cmd_argv = NULL, *sh_c_argv[4];
>  	const char *filename = NULL;
>  	enum {
> -		OPT_VERBOSE = CHAR_MAX + 1
> +		OPT_VERBOSE = CHAR_MAX + 1,
> +		OPT_FCNTL_OFD,
>  	};
>  	static const struct option long_options[] = {
>  		{"shared", no_argument, NULL, 's'},
> @@ -163,6 +200,7 @@ int main(int argc, char *argv[])
>  		{"close", no_argument, NULL, 'o'},
>  		{"no-fork", no_argument, NULL, 'F'},
>  		{"verbose", no_argument, NULL, OPT_VERBOSE},
> +		{"fcntl-ofd", no_argument, NULL, OPT_FCNTL_OFD},
>  		{"help", no_argument, NULL, 'h'},
>  		{"version", no_argument, NULL, 'V'},
>  		{NULL, 0, NULL, 0}
> @@ -217,6 +255,11 @@ int main(int argc, char *argv[])
>  			if (conflict_exit_code < 0 || conflict_exit_code > 255)
>  				errx(EX_USAGE, _("exit code out of range (expected 0 to 255)"));
>  			break;
> +#ifdef HAVE_FCNTL_OFD_LOCKS
> +		case OPT_FCNTL_OFD:
> +			use_fcntl_ofd = 1;
> +			break;
> +#endif
>  		case OPT_VERBOSE:
>  			verbose = 1;
>  			break;
> @@ -234,6 +277,13 @@ int main(int argc, char *argv[])
>  		errx(EX_USAGE,
>  			_("the --no-fork and --close options are incompatible"));
>  
> +	/*
> +	 * For fcntl(F_OFD_SETLK), an exclusive lock requires that the
> +	 * file is open for write.
> +	 */
> +	if (use_fcntl_ofd && type == LOCK_EX)
> +		open_flags = O_WRONLY;
> +
>  	if (argc > optind + 1) {
>  		/* Run command */
>  		if (!strcmp(argv[optind + 1], "-c") ||
> @@ -280,9 +330,15 @@ int main(int argc, char *argv[])
>  
>  	if (verbose)
>  		gettime_monotonic(&time_start);
> -	while (flock(fd, type | block)) {
> +	while (use_fcntl_ofd ? do_fcntl_lock(fd, type, block) : flock(fd, type | block)) {
>  		switch (errno) {
>  		case EWOULDBLOCK:
> +			/*
> +			 * Per the man page, for fcntl(), EACCES may
> +			 * be returned and means the same as
> +			 * EAGAIN/EWOULDBLOCK.
> +			 */
> +		case EACCES:
>  			/* -n option set and failed to lock. */
>  			if (verbose)
>  				warnx(_("failed to get lock"));
> -- 
> 2.40.1.1.g1c60b9335d
> 
> 

Masatake YAMATO


^ permalink raw reply

* Re: [PATCH v8] coresched: Manage core scheduling cookies for tasks
From: Phil Auld @ 2024-04-17 12:27 UTC (permalink / raw)
  To: Karel Zak; +Cc: Thijs Raymakers, thomas, util-linux
In-Reply-To: <20240417103138.g3bk5zamemhx6xm2@ws.net.home>

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

* [PATCH v9] coresched: Manage core scheduling cookies for tasks
From: Thijs Raymakers @ 2024-04-17 11:39 UTC (permalink / raw)
  To: kzak; +Cc: thomas, util-linux, Thijs Raymakers, Phil Auld
In-Reply-To: <20240417103138.g3bk5zamemhx6xm2@ws.net.home>

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

* Re: [PATCH v8] coresched: Manage core scheduling cookies for tasks
From: Karel Zak @ 2024-04-17 10:31 UTC (permalink / raw)
  To: Phil Auld; +Cc: Thijs Raymakers, thomas, util-linux
In-Reply-To: <20240411134640.GA413983@lorien.usersys.redhat.com>

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

* [PATCH] flock: add support for using fcntl() with open file description locks
From: Rasmus Villemoes @ 2024-04-17 10:09 UTC (permalink / raw)
  To: util-linux; +Cc: Karel Zak, Rasmus Villemoes

Currently, there is no way for shell scripts to safely access
resources protected by POSIX locking (fcntl with the F_SETLK/F_SETLKW
commands). For example, the glibc function lckpwdf(), used to
protect access to the /etc/shadow database, works by taking a
F_SETLKW on /etc/.pwd.lock .

Due to the odd semantics of POSIX locking (e.g. released when any file
descriptor associated to the inode is closed), we cannot usefully
directly expose the POSIX F_SETLK/F_SETLKW commands. However, linux
3.15 introduced F_OFD_SETLK[W], with semantics wrt. ownership and
release better matching those of flock(2), and crucially they do
conflict with locks obtained via F_SETLK[W]. With this, a shell script
can do

  exec 4> /etc/.pwd.lock
  flock --fcntl-ofd 4
  <access/modify /etc/shadow ...>
  flock --fcntl-ofd --unlock 4 # or just exit

without conflicting with passwd(1) or other utilities that
access/modify /etc/shadow.

The option name is a bit verbose, and no single-letter shorthand is
defined, because this is somewhat low-level and the user really needs
to know what he is doing.

Signed-off-by: Rasmus Villemoes <rasmus.villemoes@prevas.dk>

---

Both my autotools and meson fu are weak to non-existing, so I don't
know if I've written the "test if the header exposes this macro"
correctly.

I'm not at all married to the option name. I also considered just
making it --fcntl, with the possiblity of making that grow an optional
argument (for example --fcntl=posix with plain --fcntl being an alias
for --fcntl=ofd) should anyone ever figure out a use for the plain
F_SETLK, perhaps just for testing.


 configure.ac      |  6 +++++
 meson.build       |  3 +++
 sys-utils/flock.c | 60 +++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/configure.ac b/configure.ac
index c302732e7..441b09440 100644
--- a/configure.ac
+++ b/configure.ac
@@ -434,6 +434,12 @@ AC_CHECK_DECLS([PR_REP_CAPACITY], [], [], [
 	#include <linux/pr.h>
 ])
 
+AC_CHECK_DECL([F_OFD_SETLK],
+	[AC_DEFINE([HAVE_FCNTL_OFD_LOCKS], [1],
+	[Define to 1 if fcntl.h defines F_OFD_ constants])], [], [
+#include <fcntl.h>
+])
+
 AC_CHECK_HEADERS([security/openpam.h], [], [], [
 #ifdef HAVE_SECURITY_PAM_APPL_H
 #include <security/pam_appl.h>
diff --git a/meson.build b/meson.build
index 99126f7aa..004c849f1 100644
--- a/meson.build
+++ b/meson.build
@@ -704,6 +704,9 @@ conf.set('HAVE_DECL_BLK_ZONE_REP_CAPACITY', have ? 1 : false)
 have = cc.has_header_symbol('linux/pr.h', 'PR_REP_CAPACITY')
 conf.set('HAVE_DECL_PR_REP_CAPACITY', have ? 1 : false)
 
+have = cc.has_header_symbol('fcntl.h', 'F_OFD_SETLK', args: '-D_GNU_SOURCE')
+conf.set('HAVE_FCNTL_OFD_LOCKS', have ? 1 : false)
+
 code = '''
 #include <time.h>
 #if !@0@
diff --git a/sys-utils/flock.c b/sys-utils/flock.c
index 7d878ff81..40751517d 100644
--- a/sys-utils/flock.c
+++ b/sys-utils/flock.c
@@ -70,6 +70,9 @@ static void __attribute__((__noreturn__)) usage(void)
 	fputs(_(  " -o, --close              close file descriptor before running command\n"), stdout);
 	fputs(_(  " -c, --command <command>  run a single command string through the shell\n"), stdout);
 	fputs(_(  " -F, --no-fork            execute command without forking\n"), stdout);
+#ifdef HAVE_FCNTL_OFD_LOCKS
+	fputs(_(  "     --fcntl-ofd          use fcntl(F_OFD_SETLK) rather than flock()\n"), stdout);
+#endif
 	fputs(_(  "     --verbose            increase verbosity\n"), stdout);
 	fputs(USAGE_SEPARATOR, stdout);
 	fprintf(stdout, USAGE_HELP_OPTIONS(26));
@@ -126,6 +129,38 @@ static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
 	_exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
 }
 
+static int flock_to_fcntl_type(int op)
+{
+        switch (op) {
+                case LOCK_EX:
+                        return F_WRLCK;
+                case LOCK_SH:
+                        return F_RDLCK;
+                case LOCK_UN:
+                        return F_UNLCK;
+                default:
+			errx(EX_SOFTWARE, _("internal error, unknown operation %d"), op);
+        }
+}
+
+static int do_fcntl_lock(int fd, int op, int block)
+{
+#ifdef HAVE_FCNTL_OFD_LOCKS
+	struct flock arg = {
+		.l_type = flock_to_fcntl_type(op),
+		.l_whence = SEEK_SET,
+		.l_start = 0,
+		.l_len = 0,
+	};
+	int cmd = (block == LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW;
+	return fcntl(fd, cmd, &arg);
+#else
+	/* Should never happen, nothing can ever set use_fcntl_ofd when !HAVE_FCNTL_OFD_LOCKS. */
+	errno = ENOSYS;
+	return -1;
+#endif
+}
+
 int main(int argc, char *argv[])
 {
 	struct ul_timer timer;
@@ -140,6 +175,7 @@ int main(int argc, char *argv[])
 	int no_fork = 0;
 	int status;
 	int verbose = 0;
+	int use_fcntl_ofd = 0;
 	struct timeval time_start = { 0 }, time_done = { 0 };
 	/*
 	 * The default exit code for lock conflict or timeout
@@ -149,7 +185,8 @@ int main(int argc, char *argv[])
 	char **cmd_argv = NULL, *sh_c_argv[4];
 	const char *filename = NULL;
 	enum {
-		OPT_VERBOSE = CHAR_MAX + 1
+		OPT_VERBOSE = CHAR_MAX + 1,
+		OPT_FCNTL_OFD,
 	};
 	static const struct option long_options[] = {
 		{"shared", no_argument, NULL, 's'},
@@ -163,6 +200,7 @@ int main(int argc, char *argv[])
 		{"close", no_argument, NULL, 'o'},
 		{"no-fork", no_argument, NULL, 'F'},
 		{"verbose", no_argument, NULL, OPT_VERBOSE},
+		{"fcntl-ofd", no_argument, NULL, OPT_FCNTL_OFD},
 		{"help", no_argument, NULL, 'h'},
 		{"version", no_argument, NULL, 'V'},
 		{NULL, 0, NULL, 0}
@@ -217,6 +255,11 @@ int main(int argc, char *argv[])
 			if (conflict_exit_code < 0 || conflict_exit_code > 255)
 				errx(EX_USAGE, _("exit code out of range (expected 0 to 255)"));
 			break;
+#ifdef HAVE_FCNTL_OFD_LOCKS
+		case OPT_FCNTL_OFD:
+			use_fcntl_ofd = 1;
+			break;
+#endif
 		case OPT_VERBOSE:
 			verbose = 1;
 			break;
@@ -234,6 +277,13 @@ int main(int argc, char *argv[])
 		errx(EX_USAGE,
 			_("the --no-fork and --close options are incompatible"));
 
+	/*
+	 * For fcntl(F_OFD_SETLK), an exclusive lock requires that the
+	 * file is open for write.
+	 */
+	if (use_fcntl_ofd && type == LOCK_EX)
+		open_flags = O_WRONLY;
+
 	if (argc > optind + 1) {
 		/* Run command */
 		if (!strcmp(argv[optind + 1], "-c") ||
@@ -280,9 +330,15 @@ int main(int argc, char *argv[])
 
 	if (verbose)
 		gettime_monotonic(&time_start);
-	while (flock(fd, type | block)) {
+	while (use_fcntl_ofd ? do_fcntl_lock(fd, type, block) : flock(fd, type | block)) {
 		switch (errno) {
 		case EWOULDBLOCK:
+			/*
+			 * Per the man page, for fcntl(), EACCES may
+			 * be returned and means the same as
+			 * EAGAIN/EWOULDBLOCK.
+			 */
+		case EACCES:
 			/* -n option set and failed to lock. */
 			if (verbose)
 				warnx(_("failed to get lock"));
-- 
2.40.1.1.g1c60b9335d


^ permalink raw reply related

* Re: lsfd/error-eperm test fails on git master
From: Thomas Weißschuh @ 2024-04-16 20:00 UTC (permalink / raw)
  To: John Paul Adrian Glaubitz; +Cc: Masatake YAMATO, util-linux
In-Reply-To: <051624b9256db27a731d62c031cb627d9f5a256e.camel@physik.fu-berlin.de>

On 2024-04-16 10:57:20+0200, John Paul Adrian Glaubitz wrote:
> Hi Thomas,
> 
> On Tue, 2024-04-16 at 09:38 +0200, Thomas Weißschuh wrote:
> > > I can send a patch but wanted to wait for the original submitter.
> > 
> > Should be fixed by https://github.com/util-linux/util-linux/pull/2960
> 
> That doesn't seem to be 100% compatible with the default awk on Debian:

I switched over to sed, could you try that?

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

>   CC       lib/agetty-plymouth-ctrl.o
> mawk: line 1: syntax error at or near ,
>   CC       login-utils/sulogin.o

^ permalink raw reply

* Re: lsfd/error-eperm test fails on git master
From: John Paul Adrian Glaubitz @ 2024-04-16  8:57 UTC (permalink / raw)
  To: Thomas Weißschuh, Masatake YAMATO; +Cc: util-linux
In-Reply-To: <ed956683-5b2e-4eb5-9056-8c8eedf1c17c@t-8ch.de>

Hi Thomas,

On Tue, 2024-04-16 at 09:38 +0200, Thomas Weißschuh wrote:
> > I can send a patch but wanted to wait for the original submitter.
> 
> Should be fixed by https://github.com/util-linux/util-linux/pull/2960

That doesn't seem to be 100% compatible with the default awk on Debian:

  CC       lib/agetty-plymouth-ctrl.o
mawk: line 1: syntax error at or near ,
  CC       login-utils/sulogin.o

Adrian

-- 
 .''`.  John Paul Adrian Glaubitz
: :' :  Debian Developer
`. `'   Physicist
  `-    GPG: 62FF 8A75 84E0 2956 9546  0006 7426 3B37 F5B5 F913

^ permalink raw reply

* Re: lsfd/error-eperm test fails on git master
From: Thomas Weißschuh @ 2024-04-16  7:38 UTC (permalink / raw)
  To: Masatake YAMATO, glaubitz; +Cc: util-linux
In-Reply-To: <873299ec-913a-43a8-ac11-20ca4b03f2f7@t-8ch.de>

On 2024-04-16 07:59:05+0200, Thomas Weißschuh  wrote:
> Apr 16, 2024 02:30:10 Masatake YAMATO <yamato@redhat.com>:
> > From: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
> > Subject: lsfd/error-eperm test fails on git master
> > Date: Mon, 15 Apr 2024 12:24:08 +0200
> >
> >> lsfd/error-eperm fails for me on git master. I have reproduced the issue
> >> on 32-bit PowerPC, 64-bit SPARC, 64-bit s390x and x86_64.
> >>
> >> Is there a tentative fix for this failure?
> >
> > Thank you for reporting.
> > This may be a bug in a build script.
> >
> >> Thanks,
> >> Adrian
> >>
> >> (sid_powerpc-dchroot)glaubitz@perotto:~/util-linux-git/tests$ ./run.sh lsfd/error-eperm
> >>
> >> -------------------- util-linux regression tests --------------------
> >>
> >>                     For development purpose only.
> >>                  Don't execute on production system!
> >>
> >>        kernel: 6.6.15-powerpc64
> >>
> >>       options: --srcdir=/home/glaubitz/util-linux-git/tests/.. \
> >>                --builddir=/home/glaubitz/util-linux-git/tests/..
> >>
> >>          lsfd: fd opening a file cannot be stat(2)'ed ... FAILED (lsfd/error-eperm)
> >>
> >> ---------------------------------------------------------------------
> >>   1 tests of 1 FAILED
> >>
> >>       lsfd/error-eperm
> >> ---------------------------------------------------------------------
> >> (sid_powerpc-dchroot)glaubitz@perotto:~/util-linux-git/tests$
> >>
> >> diff-{{{
> >> --- /home/glaubitz/util-linux-git/tests/expected/lsfd/error-eperm       2024-04-09 09:22:29.505017516 +0000
> >> +++ /home/glaubitz/util-linux-git/tests/output/lsfd/error-eperm 2024-04-15 08:37:38.326220593 +0000
> >> @@ -1,2 +1,2 @@
> >> -mem ERROR stat:EPERM
> >> +mem ERROR stat:unknown(1)
> >
> > EPERM is defined as 1 in asm-generic/errno-base.h.
> > So `unknown(1)' should be decoded as EPERM.
> >
> > lsns uses errnos.h generated at build-time for decoding the error numbers.
> 
> The build log should be the same as reported before:
> https://buildd.debian.org/status/fetch.php?pkg=util-linux&arch=powerpc&ver=2.40-5&stamp=1712589234&raw=0
> 
> Which indeed contains:
> 
> GEN      syscalls.h
> ./tools/all_syscalls: line 13: gawk: command not found
> 
> So it should be the same issue as
> https://github.com/util-linux/util-linux/pull/2949
> 
> (which needs to be expanded to all_errnos and meson)
> 
> I can send a patch but wanted to wait for the original submitter.

Should be fixed by https://github.com/util-linux/util-linux/pull/2960

> [..]

^ permalink raw reply

* Re: lsfd/error-eperm test fails on git master
From: Thomas Weißschuh  @ 2024-04-16  5:59 UTC (permalink / raw)
  To: Masatake YAMATO; +Cc: glaubitz, util-linux
In-Reply-To: <20240416.092958.1925095964872687612.yamato@redhat.com>

Hi,

Apr 16, 2024 02:30:10 Masatake YAMATO <yamato@redhat.com>:

> From: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
> Subject: lsfd/error-eperm test fails on git master
> Date: Mon, 15 Apr 2024 12:24:08 +0200
>
>> Hello,
>>
>> lsfd/error-eperm fails for me on git master. I have reproduced the issue
>> on 32-bit PowerPC, 64-bit SPARC, 64-bit s390x and x86_64.
>>
>> Is there a tentative fix for this failure?
>
> Thank you for reporting.
> This may be a bug in a build script.
>
>> Thanks,
>> Adrian
>>
>> (sid_powerpc-dchroot)glaubitz@perotto:~/util-linux-git/tests$ ./run.sh lsfd/error-eperm
>>
>> -------------------- util-linux regression tests --------------------
>>
>>                     For development purpose only.
>>                  Don't execute on production system!
>>
>>        kernel: 6.6.15-powerpc64
>>
>>       options: --srcdir=/home/glaubitz/util-linux-git/tests/.. \
>>                --builddir=/home/glaubitz/util-linux-git/tests/..
>>
>>          lsfd: fd opening a file cannot be stat(2)'ed ... FAILED (lsfd/error-eperm)
>>
>> ---------------------------------------------------------------------
>>   1 tests of 1 FAILED
>>
>>       lsfd/error-eperm
>> ---------------------------------------------------------------------
>> (sid_powerpc-dchroot)glaubitz@perotto:~/util-linux-git/tests$
>>
>> diff-{{{
>> --- /home/glaubitz/util-linux-git/tests/expected/lsfd/error-eperm       2024-04-09 09:22:29.505017516 +0000
>> +++ /home/glaubitz/util-linux-git/tests/output/lsfd/error-eperm 2024-04-15 08:37:38.326220593 +0000
>> @@ -1,2 +1,2 @@
>> -mem ERROR stat:EPERM
>> +mem ERROR stat:unknown(1)
>
> EPERM is defined as 1 in asm-generic/errno-base.h.
> So `unknown(1)' should be decoded as EPERM.
>
> lsns uses errnos.h generated at build-time for decoding the error numbers.

The build log should be the same as reported before:
https://buildd.debian.org/status/fetch.php?pkg=util-linux&arch=powerpc&ver=2.40-5&stamp=1712589234&raw=0

Which indeed contains:

GEN      syscalls.h
./tools/all_syscalls: line 13: gawk: command not found

So it should be the same issue as
https://github.com/util-linux/util-linux/pull/2949

(which needs to be expanded to all_errnos and meson)

I can send a patch but wanted to wait for the original submitter.

> Can I see errnos.h at the top of the build directory ?
> I guess the file is at ~/util-linux-git/errnos.h.
>
>
> On my environment, the head of the file is:
>
>     UL_ERRNO("E2BIG", E2BIG)
>     UL_ERRNO("EACCES", EACCES)
>     UL_ERRNO("EADDRINUSE", EADDRINUSE)
>     UL_ERRNO("EADDRNOTAVAIL", EADDRNOTAVAIL)
>     UL_ERRNO("EADV", EADV)
>     UL_ERRNO("EAFNOSUPPORT", EAFNOSUPPORT)
>     ...
>
> Masatake YAMATO
>
>> ASSOC,TYPE,SOURCE:  0
>> }}}-diff
>>
>> libsmartcols: fromfile: [15] wrap-tree                      ... OK
>>          lsfd: fd opening a file cannot be stat(2)'ed        ... FAILED (lsfd/error-eperm)

^ permalink raw reply

* Re: lsfd/error-eperm test fails on git master
From: Masatake YAMATO @ 2024-04-16  0:29 UTC (permalink / raw)
  To: glaubitz; +Cc: util-linux
In-Reply-To: <31ccace2e5912ffc428e065cd66764088c625c4d.camel@physik.fu-berlin.de>

Hi,

From: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
Subject: lsfd/error-eperm test fails on git master
Date: Mon, 15 Apr 2024 12:24:08 +0200

> Hello,
> 
> lsfd/error-eperm fails for me on git master. I have reproduced the issue
> on 32-bit PowerPC, 64-bit SPARC, 64-bit s390x and x86_64.
> 
> Is there a tentative fix for this failure?

Thank you for reporting.
This may be a bug in a build script.

> Thanks,
> Adrian
> 
> (sid_powerpc-dchroot)glaubitz@perotto:~/util-linux-git/tests$ ./run.sh lsfd/error-eperm
> 
> -------------------- util-linux regression tests --------------------
> 
>                     For development purpose only.
>                  Don't execute on production system!
> 
>        kernel: 6.6.15-powerpc64
> 
>       options: --srcdir=/home/glaubitz/util-linux-git/tests/.. \
>                --builddir=/home/glaubitz/util-linux-git/tests/..
> 
>          lsfd: fd opening a file cannot be stat(2)'ed ... FAILED (lsfd/error-eperm)
> 
> ---------------------------------------------------------------------
>   1 tests of 1 FAILED
> 
>       lsfd/error-eperm
> ---------------------------------------------------------------------
> (sid_powerpc-dchroot)glaubitz@perotto:~/util-linux-git/tests$
> 
> diff-{{{
> --- /home/glaubitz/util-linux-git/tests/expected/lsfd/error-eperm       2024-04-09 09:22:29.505017516 +0000
> +++ /home/glaubitz/util-linux-git/tests/output/lsfd/error-eperm 2024-04-15 08:37:38.326220593 +0000
> @@ -1,2 +1,2 @@
> -mem ERROR stat:EPERM
> +mem ERROR stat:unknown(1)

EPERM is defined as 1 in asm-generic/errno-base.h.
So `unknown(1)' should be decoded as EPERM.

lsns uses errnos.h generated at build-time for decoding the error numbers.

Can I see errnos.h at the top of the build directory ?
I guess the file is at ~/util-linux-git/errnos.h.


On my environment, the head of the file is:

    UL_ERRNO("E2BIG", E2BIG)
    UL_ERRNO("EACCES", EACCES)
    UL_ERRNO("EADDRINUSE", EADDRINUSE)
    UL_ERRNO("EADDRNOTAVAIL", EADDRNOTAVAIL)
    UL_ERRNO("EADV", EADV)
    UL_ERRNO("EAFNOSUPPORT", EAFNOSUPPORT)
    ...

Masatake YAMATO

>  ASSOC,TYPE,SOURCE:  0
> }}}-diff
> 
>  libsmartcols: fromfile: [15] wrap-tree                      ... OK
>          lsfd: fd opening a file cannot be stat(2)'ed        ... FAILED (lsfd/error-eperm)
> 
> -- 
>  .''`.  John Paul Adrian Glaubitz
> : :' :  Debian Developer
> `. `'   Physicist
>   `-    GPG: 62FF 8A75 84E0 2956 9546  0006 7426 3B37 F5B5 F913
> 


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox