git: aae23170c8b5 - main - libutil: Move cpuset(1) domain policy parsing code into libutil

From: Bojan Novković <bnovkov_at_FreeBSD.org>
Date: Sun, 27 Jul 2025 16:32:15 UTC
The branch main has been updated by bnovkov:

URL: https://cgit.FreeBSD.org/src/commit/?id=aae23170c8b5ac320dbf9c5f6cec05bea0b4b62f

commit aae23170c8b5ac320dbf9c5f6cec05bea0b4b62f
Author:     Bojan Novković <bnovkov@FreeBSD.org>
AuthorDate: 2024-09-08 15:51:46 +0000
Commit:     Bojan Novković <bnovkov@FreeBSD.org>
CommitDate: 2025-07-27 16:31:48 +0000

    libutil: Move cpuset(1) domain policy parsing code into libutil
    
    cpuset(1) implements a domainset(9) policy parser that is used to
    translate a <policy>:<domain_list> string into a domainset_t mask
    and a valid domainset policy. This change moves the domainset parsing
    code into a new cpuset.c function - 'domainset_parselist'.
    
    The existing cpuset.c code was refactored into a generalized list
    parsing function which is now used to parse both CPU sets and domain
    sets. This is the same approach used in cpuset(1).
    
    Reviewed by:    markj, ziaee (manpages)
    Differential Revision:  https://reviews.freebsd.org/D46607
---
 bin/cpuset/Makefile   |   2 +-
 bin/cpuset/cpuset.c   | 153 +-------------------------------------------------
 lib/libutil/Makefile  |   1 +
 lib/libutil/cpuset.3  |  51 ++++++++++++++---
 lib/libutil/cpuset.c  |  98 ++++++++++++++++++++++++++++----
 lib/libutil/libutil.h |   8 ++-
 6 files changed, 140 insertions(+), 173 deletions(-)

diff --git a/bin/cpuset/Makefile b/bin/cpuset/Makefile
index d6f58db62901..639dd9812171 100644
--- a/bin/cpuset/Makefile
+++ b/bin/cpuset/Makefile
@@ -1,6 +1,6 @@
 PROG=   cpuset
 
-LIBADD=	jail
+LIBADD=	jail util
 
 SYMLINKS+=	../..${BINDIR}/cpuset	/usr/bin/cpuset
 
diff --git a/bin/cpuset/cpuset.c b/bin/cpuset/cpuset.c
index 82ffcaeec252..7416e100a3c6 100644
--- a/bin/cpuset/cpuset.c
+++ b/bin/cpuset/cpuset.c
@@ -43,6 +43,7 @@
 #include <err.h>
 #include <errno.h>
 #include <jail.h>
+#include <libutil.h>
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -69,154 +70,6 @@ static cpuwhich_t which;
 
 static void usage(void) __dead2;
 
-struct numa_policy {
-	const char 	*name;
-	int		policy;
-};
-
-static struct numa_policy policies[] = {
-	{ "round-robin", DOMAINSET_POLICY_ROUNDROBIN },
-	{ "rr", DOMAINSET_POLICY_ROUNDROBIN },
-	{ "first-touch", DOMAINSET_POLICY_FIRSTTOUCH },
-	{ "ft", DOMAINSET_POLICY_FIRSTTOUCH },
-	{ "prefer", DOMAINSET_POLICY_PREFER },
-	{ "interleave", DOMAINSET_POLICY_INTERLEAVE},
-	{ "il", DOMAINSET_POLICY_INTERLEAVE},
-	{ NULL, DOMAINSET_POLICY_INVALID }
-};
-
-static void printset(struct bitset *mask, int size);
-
-static void
-parselist(char *list, struct bitset *mask, int size)
-{
-	enum { NONE, NUM, DASH } state;
-	int lastnum;
-	int curnum;
-	char *l;
-
-	state = NONE;
-	curnum = lastnum = 0;
-	for (l = list; *l != '\0';) {
-		if (isdigit(*l)) {
-			curnum = atoi(l);
-			if (curnum >= size)
-				errx(EXIT_FAILURE,
-				    "List entry %d exceeds maximum of %d",
-				    curnum, size - 1);
-			while (isdigit(*l))
-				l++;
-			switch (state) {
-			case NONE:
-				lastnum = curnum;
-				state = NUM;
-				break;
-			case DASH:
-				for (; lastnum <= curnum; lastnum++)
-					BIT_SET(size, lastnum, mask);
-				state = NONE;
-				break;
-			case NUM:
-			default:
-				goto parserr;
-			}
-			continue;
-		}
-		switch (*l) {
-		case ',':
-			switch (state) {
-			case NONE:
-				break;
-			case NUM:
-				BIT_SET(size, curnum, mask);
-				state = NONE;
-				break;
-			case DASH:
-				goto parserr;
-				break;
-			}
-			break;
-		case '-':
-			if (state != NUM)
-				goto parserr;
-			state = DASH;
-			break;
-		default:
-			goto parserr;
-		}
-		l++;
-	}
-	switch (state) {
-		case NONE:
-			break;
-		case NUM:
-			BIT_SET(size, curnum, mask);
-			break;
-		case DASH:
-			goto parserr;
-	}
-	return;
-parserr:
-	errx(EXIT_FAILURE, "Malformed list %s", list);
-}
-
-static void
-parsecpulist(char *list, cpuset_t *mask)
-{
-
-	if (strcasecmp(list, "all") == 0) {
-		if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
-		    sizeof(*mask), mask) != 0)
-			err(EXIT_FAILURE, "getaffinity");
-		return;
-	}
-	parselist(list, (struct bitset *)mask, CPU_SETSIZE);
-}
-
-/*
- * permissively parse policy:domain list
- * allow:
- *	round-robin:0-4		explicit
- *	round-robin:all		explicit root domains
- *	0-4			implicit root policy
- *	round-robin		implicit root domains
- *	all			explicit root domains and implicit policy
- */
-static void
-parsedomainlist(char *list, domainset_t *mask, int *policyp)
-{
-	domainset_t rootmask;
-	struct numa_policy *policy;
-	char *l;
-	int p;
-
-	/*
-	 * Use the rootset's policy as the default for unspecified policies.
-	 */
-	if (cpuset_getdomain(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
-	    sizeof(rootmask), &rootmask, &p) != 0)
-		err(EXIT_FAILURE, "getdomain");
-
-	l = list;
-	for (policy = &policies[0]; policy->name != NULL; policy++) {
-		if (strncasecmp(l, policy->name, strlen(policy->name)) == 0) {
-			p = policy->policy;
-			l += strlen(policy->name);
-			if (*l != ':' && *l != '\0')
-				errx(EXIT_FAILURE, "Malformed list %s", list);
-			if (*l == ':')
-				l++;
-			break;
-		}
-	}
-	*policyp = p;
-	if (strcasecmp(l, "all") == 0 || *l == '\0') {
-		DOMAINSET_COPY(&rootmask, mask);
-		return;
-	}
-	parselist(l, (struct bitset *)mask, DOMAINSET_SETSIZE);
-}
-
 static void
 printset(struct bitset *mask, int size)
 {
@@ -327,11 +180,11 @@ main(int argc, char *argv[])
 			break;
 		case 'l':
 			lflag = 1;
-			parsecpulist(optarg, &mask);
+			cpuset_parselist(optarg, &mask);
 			break;
 		case 'n':
 			nflag = 1;
-			parsedomainlist(optarg, &domains, &policy);
+			domainset_parselist(optarg, &domains, &policy);
 			break;
 		case 'p':
 			pflag = 1;
diff --git a/lib/libutil/Makefile b/lib/libutil/Makefile
index 0639745d08fc..2d92c5ba1916 100644
--- a/lib/libutil/Makefile
+++ b/lib/libutil/Makefile
@@ -38,6 +38,7 @@ MAN+=	cpuset.3 expand_number.3 flopen.3 fparseln.3 ftime.3 getlocalbase.3 \
 	property.3 pty.3 quotafile.3 realhostname.3 realhostname_sa.3 \
 	_secure_path.3 trimdomain.3 uucplock.3 pw_util.3
 MAN+=	login.conf.5
+MLINKS+=cpuset.3 domainset_parselist.3
 MLINKS+=flopen.3 flopenat.3
 MLINKS+=kld.3 kld_isloaded.3 kld.3 kld_load.3
 MLINKS+=login_auth.3 auth_cat.3 login_auth.3 auth_checknologin.3
diff --git a/lib/libutil/cpuset.3 b/lib/libutil/cpuset.3
index be29d5309ef0..47dffd209ee6 100644
--- a/lib/libutil/cpuset.3
+++ b/lib/libutil/cpuset.3
@@ -22,21 +22,22 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd October 31, 2017
+.Dd June 24, 2025
 .Dt CPUSET 3
 .Os
 .Sh NAME
-.Nm cpuset_parselist
-.Nd utility functions for
-.Xr cpuset 2
-handling
+.Nm cpuset_parselist ,
+.Nm domainset_parselist
+.Nd utility functions for cpuset(2) handling
 .Sh LIBRARY
 .Lb libutil
 .Sh SYNOPSIS
 .In sys/cpuset.h
 .In libutil.h
 .Ft int
-.Fn cpuset_parselist "const char *cpu-list" "cpuset_t *mask"
+.Fn cpuset_parselist "const char *cpu_list" "cpuset_t *mask"
+.Ft int
+.Fn domainset_parselist "const char *domain_policy" "domainset_t *domain_mask" "int *policyp"
 .Sh DESCRIPTION
 The
 .Fn cpuset_parselist
@@ -52,6 +53,27 @@ numbers.
 A special list of
 .Dq all
 may be specified in which case the list includes all CPUs from the root set.
+.Pp
+The
+.Fn domainset_parselist
+function parses a
+.Xr domainset 9
+memory domain allocation policy
+specified by
+.Va domain_policy
+filling the
+.Va domain_mask
+and the
+.Va policyp .
+A valid
+.Va domain_policy
+is formatted as
+.Ar policy:domain-list .
+See the
+.Ar -n
+flag in
+.Xr cpuset 1
+for a list of valid domain policies.
 .Sh RETURN VALUES
 Return values can be the following
 .Bl -tag -width Er
@@ -60,19 +82,30 @@ The parsing was successful
 .It Dv CPUSET_PARSE_ERROR
 The
 .Va cpu-list
+or
+.Va domain-policy
 format is invalid
 .It Dv CPUSET_PARSE_GETAFFINITY
 The
 .Xr cpuset_getaffinity 2
 call has failed
 .It Dv CPUSET_PARSE_INVALID_CPU
-The number of supported CPUs has been exceeded.
+The number of supported CPUs or NUMA domains has been exceeded.
 The maximum number being
-.Va CPU_SETSIZE .
+.Va CPU_SETSIZE
+and
+.Va DOMAINSET_SETSIZE
+respectively.
+.It Dv CPUSET_PARSE_GETDOMAIN
+The
+.Xr cpuset_getdomain 2
+call has failed
 .El
 .Sh SEE ALSO
 .Xr cpuset 1 ,
 .Xr cpuset 2 ,
-.Xr cpuset 9
+.Xr numa 4 ,
+.Xr cpuset 9 ,
+.Xr domainset 9
 .Sh AUTHORS
 .An Jeffrey Roberson Aq Mt jeff@FreeBSD.org
diff --git a/lib/libutil/cpuset.c b/lib/libutil/cpuset.c
index 3c374bfa6cac..d4840af7e175 100644
--- a/lib/libutil/cpuset.c
+++ b/lib/libutil/cpuset.c
@@ -27,34 +27,48 @@
  * SUCH DAMAGE.
  */
 
+#include <sys/cdefs.h>
+#define _WANT_FREEBSD_BITSET
+
 #include <sys/types.h>
 #include <sys/cpuset.h>
+#include <sys/domainset.h>
 
 #include <stdlib.h>
 #include <string.h>
 #include <libutil.h>
 #include <ctype.h>
 
-int
-cpuset_parselist(const char *list, cpuset_t *mask)
+struct numa_policy {
+	const char 	*name;
+	int		policy;
+};
+
+static const struct numa_policy policies[] = {
+	{ "round-robin", DOMAINSET_POLICY_ROUNDROBIN },
+	{ "rr", DOMAINSET_POLICY_ROUNDROBIN },
+	{ "first-touch", DOMAINSET_POLICY_FIRSTTOUCH },
+	{ "ft", DOMAINSET_POLICY_FIRSTTOUCH },
+	{ "prefer", DOMAINSET_POLICY_PREFER },
+	{ "interleave", DOMAINSET_POLICY_INTERLEAVE},
+	{ "il", DOMAINSET_POLICY_INTERLEAVE},
+	{ NULL, DOMAINSET_POLICY_INVALID }
+};
+
+static int
+parselist(const char *list, struct bitset *mask, int size)
 {
 	enum { NONE, NUM, DASH } state;
 	int lastnum;
 	int curnum;
 	const char *l;
 
-	if (strcasecmp(list, "all") == 0) {
-		if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
-		    sizeof(*mask), mask) != 0)
-			return (CPUSET_PARSE_GETAFFINITY);
-		return (CPUSET_PARSE_OK);
-	}
 	state = NONE;
 	curnum = lastnum = 0;
 	for (l = list; *l != '\0';) {
 		if (isdigit(*l)) {
 			curnum = atoi(l);
-			if (curnum > CPU_SETSIZE)
+			if (curnum >= size)
 				return (CPUSET_PARSE_INVALID_CPU);
 			while (isdigit(*l))
 				l++;
@@ -65,7 +79,7 @@ cpuset_parselist(const char *list, cpuset_t *mask)
 				break;
 			case DASH:
 				for (; lastnum <= curnum; lastnum++)
-					CPU_SET(lastnum, mask);
+					BIT_SET(size, lastnum, mask);
 				state = NONE;
 				break;
 			case NUM:
@@ -80,7 +94,7 @@ cpuset_parselist(const char *list, cpuset_t *mask)
 			case NONE:
 				break;
 			case NUM:
-				CPU_SET(curnum, mask);
+				BIT_SET(size, curnum, mask);
 				state = NONE;
 				break;
 			case DASH:
@@ -102,7 +116,7 @@ cpuset_parselist(const char *list, cpuset_t *mask)
 		case NONE:
 			break;
 		case NUM:
-			CPU_SET(curnum, mask);
+			BIT_SET(size, curnum, mask);
 			break;
 		case DASH:
 			goto parserr;
@@ -111,3 +125,63 @@ cpuset_parselist(const char *list, cpuset_t *mask)
 parserr:
 	return (CPUSET_PARSE_ERROR);
 }
+
+/*
+ * permissively parse policy:domain list
+ * allow:
+ *	round-robin:0-4		explicit
+ *	round-robin:all		explicit root domains
+ *	0-4			implicit root policy
+ *	round-robin		implicit root domains
+ *	all			explicit root domains and implicit policy
+ */
+int
+domainset_parselist(const char *list, domainset_t *mask, int *policyp)
+{
+	domainset_t rootmask;
+	const struct numa_policy *policy;
+	const char *l;
+	int p;
+
+	/*
+	 * Use the rootset's policy as the default for unspecified policies.
+	 */
+	if (cpuset_getdomain(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
+	    sizeof(rootmask), &rootmask, &p) != 0)
+		return (CPUSET_PARSE_GETDOMAIN);
+
+	if (list == NULL || strcasecmp(list, "all") == 0 || *list == '\0') {
+		*policyp = p;
+		DOMAINSET_COPY(&rootmask, mask);
+		return (CPUSET_PARSE_OK);
+	}
+
+	l = list;
+	for (policy = &policies[0]; policy->name != NULL; policy++) {
+		if (strncasecmp(l, policy->name, strlen(policy->name)) == 0) {
+			p = policy->policy;
+			l += strlen(policy->name);
+			if (*l != ':' && *l != '\0')
+				return (CPUSET_PARSE_ERROR);
+			if (*l == ':')
+				l++;
+			break;
+		}
+	}
+	*policyp = p;
+
+	return (parselist(l, (struct bitset *)mask, DOMAINSET_SETSIZE));
+}
+
+int
+cpuset_parselist(const char *list, cpuset_t *mask)
+{
+	if (strcasecmp(list, "all") == 0) {
+		if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
+		    sizeof(*mask), mask) != 0)
+			return (CPUSET_PARSE_GETAFFINITY);
+		return (CPUSET_PARSE_OK);
+	}
+
+	return (parselist(list, (struct bitset *)mask, CPU_SETSIZE));
+}
diff --git a/lib/libutil/libutil.h b/lib/libutil/libutil.h
index 919855184caf..7d8bfdf67fac 100644
--- a/lib/libutil/libutil.h
+++ b/lib/libutil/libutil.h
@@ -213,7 +213,13 @@ int	cpuset_parselist(const char *list, cpuset_t *mask);
 #define CPUSET_PARSE_OK			0
 #define CPUSET_PARSE_GETAFFINITY	-1
 #define CPUSET_PARSE_ERROR		-2
-#define CPUSET_PARSE_INVALID_CPU	-3
+#define CPUSET_PARSE_OUT_OF_RANGE	-3
+#define CPUSET_PARSE_GETDOMAIN		-4
+#define CPUSET_PARSE_INVALID_CPU	CPUSET_PARSE_OUT_OF_RANGE /* backwards compat */
+#endif
+
+#ifdef _SYS_DOMAINSET_H_
+int	domainset_parselist(const char *list, domainset_t *mask, int *policyp);
 #endif
 
 __END_DECLS