diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:24:02 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:24:02 +0300 |
| commit | 2ab63a8c60f0ebd6c813b19eb540476fbe63edda (patch) | |
| tree | 26ee075c4371081fcf2053f13aac19f3f45520cb /corebinutils/cpuset | |
| parent | bd39aec8766c747a5a85a90ec5755a96b8a23cf1 (diff) | |
| parent | c41b67bfa876d63ab4511bdd89fae4c2b66331e8 (diff) | |
| download | Project-Tick-2ab63a8c60f0ebd6c813b19eb540476fbe63edda.tar.gz Project-Tick-2ab63a8c60f0ebd6c813b19eb540476fbe63edda.zip | |
Add 'corebinutils/cpuset/' from commit 'c41b67bfa876d63ab4511bdd89fae4c2b66331e8'
git-subtree-dir: corebinutils/cpuset
git-subtree-mainline: bd39aec8766c747a5a85a90ec5755a96b8a23cf1
git-subtree-split: c41b67bfa876d63ab4511bdd89fae4c2b66331e8
Diffstat (limited to 'corebinutils/cpuset')
| -rw-r--r-- | corebinutils/cpuset/.gitignore | 25 | ||||
| -rw-r--r-- | corebinutils/cpuset/GNUmakefile | 35 | ||||
| -rw-r--r-- | corebinutils/cpuset/LICENSE | 29 | ||||
| -rw-r--r-- | corebinutils/cpuset/LICENSES/BSD-2-Clause.txt | 9 | ||||
| -rw-r--r-- | corebinutils/cpuset/README.md | 24 | ||||
| -rw-r--r-- | corebinutils/cpuset/cpuset.1 | 230 | ||||
| -rw-r--r-- | corebinutils/cpuset/cpuset.c | 385 | ||||
| -rw-r--r-- | corebinutils/cpuset/tests/test.sh | 62 |
8 files changed, 799 insertions, 0 deletions
diff --git a/corebinutils/cpuset/.gitignore b/corebinutils/cpuset/.gitignore new file mode 100644 index 0000000000..a74d30b48c --- /dev/null +++ b/corebinutils/cpuset/.gitignore @@ -0,0 +1,25 @@ +*.a +*.core +*.lo +*.nossppico +*.o +*.orig +*.pico +*.pieo +*.po +*.rej +*.so +*.so.[0-9]* +*.sw[nop] +*~ +.*DS_Store +.cache +.clangd +.ccls-cache +.depend* +compile_commands.json +compile_commands.events.json +tags +build/ +out/ +.linux-obj/ diff --git a/corebinutils/cpuset/GNUmakefile b/corebinutils/cpuset/GNUmakefile new file mode 100644 index 0000000000..2f4847c337 --- /dev/null +++ b/corebinutils/cpuset/GNUmakefile @@ -0,0 +1,35 @@ +.DEFAULT_GOAL := all + +CC ?= cc +CPPFLAGS += -D_GNU_SOURCE +CFLAGS ?= -O2 +CFLAGS += -std=c17 -g -Wall -Wextra -Wno-unused-parameter +LDFLAGS ?= +LDLIBS ?= + +OBJDIR := $(CURDIR)/build +OUTDIR := $(CURDIR)/out +TARGET := $(OUTDIR)/cpuset +OBJS := $(OBJDIR)/cpuset.o + +.PHONY: all clean dirs test status + +all: $(TARGET) + +dirs: + @mkdir -p "$(OBJDIR)" "$(OUTDIR)" + +$(TARGET): $(OBJS) | dirs + $(CC) $(LDFLAGS) -o "$@" $(OBJS) $(LDLIBS) + +$(OBJDIR)/cpuset.o: $(CURDIR)/cpuset.c | dirs + $(CC) $(CPPFLAGS) $(CFLAGS) -c "$(CURDIR)/cpuset.c" -o "$@" + +test: $(TARGET) + CPUSET_BIN="$(TARGET)" sh "$(CURDIR)/tests/test.sh" + +status: + @printf '%s\n' "$(TARGET)" + +clean: + @rm -rf "$(CURDIR)/build" "$(CURDIR)/out" diff --git a/corebinutils/cpuset/LICENSE b/corebinutils/cpuset/LICENSE new file mode 100644 index 0000000000..51b05d76f1 --- /dev/null +++ b/corebinutils/cpuset/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2007, 2008 Jeffrey Roberson <jeff@freebsd.org> + All rights reserved. + +Copyright (c) 2008 Nokia Corporation + All rights reserved. + +Copyright (c) 2026 + Project Tick. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/corebinutils/cpuset/LICENSES/BSD-2-Clause.txt b/corebinutils/cpuset/LICENSES/BSD-2-Clause.txt new file mode 100644 index 0000000000..5f662b354c --- /dev/null +++ b/corebinutils/cpuset/LICENSES/BSD-2-Clause.txt @@ -0,0 +1,9 @@ +Copyright (c) <year> <owner> + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/corebinutils/cpuset/README.md b/corebinutils/cpuset/README.md new file mode 100644 index 0000000000..93ec8bf89c --- /dev/null +++ b/corebinutils/cpuset/README.md @@ -0,0 +1,24 @@ +# cpuset + +Standalone musl-libc-based Linux port of FreeBSD `cpuset` for Project Tick BSD/Linux Distribution. + +## Build + +```sh +gmake -f GNUmakefile +gmake -f GNUmakefile CC=musl-gcc +``` + +## Test + +```sh +gmake -f GNUmakefile test +gmake -f GNUmakefile test CC=musl-gcc +``` + +## Notes + +- Port strategy is syscall translation, not a wrapper around the FreeBSD userland ABI. +- Linux-native affinity operations are mapped to `sched_getaffinity(2)` and `sched_setaffinity(2)`. +- The Linux build preserves `-g`, `-l`, `-p`, `-t`, and command execution with an affinity mask. +- FreeBSD-specific cpuset IDs, jail/irq targets, and NUMA domain policy flags have no direct Linux syscall equivalent in this port and currently return an explicit error. diff --git a/corebinutils/cpuset/cpuset.1 b/corebinutils/cpuset/cpuset.1 new file mode 100644 index 0000000000..5c4f2b9480 --- /dev/null +++ b/corebinutils/cpuset/cpuset.1 @@ -0,0 +1,230 @@ +.\" Copyright (c) 2008 Christian Brueffer +.\" Copyright (c) 2008 Jeffrey Roberson +.\" All rights reserved. +.\" +.\" Copyright (c) 2026 +.\" Project Tick. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd July 3, 2018 +.Dt CPUSET 1 +.Os +.Sh NAME +.Nm cpuset +.Nd "configure processor sets" +.Sh SYNOPSIS +.Nm +.Op Fl l Ar cpu-list +.Op Fl n Ar policy:domain-list +.Op Fl s Ar setid +.Ar cmd ... +.Nm +.Op Fl l Ar cpu-list +.Op Fl n Ar policy:domain-list +.Op Fl s Ar setid +.Fl p Ar pid +.Nm +.Op Fl c +.Op Fl l Ar cpu-list +.Op Fl n Ar policy:domain-list +.Fl C +.Fl p Ar pid +.Nm +.Op Fl c +.Op Fl l Ar cpu-list +.Op Fl n Ar policy:domain-list +.Op Fl j Ar jail | Fl p Ar pid | Fl t Ar tid | Fl s Ar setid | Fl x Ar irq +.Nm +.Fl g +.Op Fl cir +.Op Fl d Ar domain | Fl j Ar jail | Fl p Ar pid | Fl t Ar tid | Fl s Ar setid | Fl x Ar irq +.Sh DESCRIPTION +The +.Nm +command can be used to assign processor sets to processes, run commands +constrained to a given set or list of processors and memory domains, and query +information about processor binding, memory binding and policy, sets, and +available processors and memory domains in the system. +.Pp +.Nm +requires a target to modify or query. +The target may be specified as a command, process id, thread id, a +cpuset id, an irq, a jail, or a NUMA domain. +Using +.Fl g +the target's set id or mask may be queried. +Using +.Fl l +or +.Fl s +the target's CPU mask or set id may be set. +If no target is specified, +.Nm +operates on itself. +Not all combinations of operations and targets are supported. +For example, +you may not set the id of an existing set or query and launch a command +at the same time. +.Pp +There are two sets applicable to each process and one private mask per thread. +Every process in the system belongs to a cpuset. +By default processes are started in set 1. +The mask or id may be queried using +.Fl c . +Each thread also has a private mask of CPUs it is allowed to run +on that must be a subset of the assigned set. +And finally, there is a root set, numbered 0, that is immutable. +This last set is the list of all possible CPUs in the system and is +queried using +.Fl r . +.Pp +Most sets include NUMA memory domain and policy information. +This can be inspected with +.Fl g +and set with +.Fl n . +This will specify which NUMA domains are visible to the process and +affect where anonymous memory and file pages will be stored on first access. +Files accessed first by other processes may specify conflicting policy. +.Pp +When running a command it may join a set specified with +.Fl s +otherwise a new set is created. +In addition, a mask for the command may be specified using +.Fl l . +When used in conjunction with +.Fl c +the mask modifies the supplied or created set rather than the private mask +for the thread. +.Pp +The options are as follows: +.Bl -tag -width ".Fl l Ar cpu-list" +.It Fl C +Create a new cpuset and assign the target process to that set. +.It Fl c +The requested operation should reference the cpuset available via the +target specifier. +.It Fl d Ar domain +Specifies a NUMA domain id as the target of the operation. +This can only be used to query the cpus visible in each numberd domain. +.It Fl g +Causes +.Nm +to print either a list of valid CPUs or, using +.Fl i , +the id of the target. +.It Fl i +When used with the +.Fl g +option print the id rather than the valid mask of the target. +.It Fl j Ar jail +Specifies a jail id or name as the target of the operation. +.It Fl l Ar cpu-list +Specifies a list of CPUs to apply to a target. +Specification may include +numbers separated by '-' for ranges and commas separating individual numbers. +A special list of +.Dq all +may be specified in which case the list includes all CPUs from the root set. +.It Fl n Ar policy:domain-list +Specifies a list of domains and allocation policy to apply to a target. +Ranges may be specified as in +.Fl l . +Valid policies include first-touch (ft), round-robin (rr), prefer and +interleave (il). +First-touch allocates on the local domain when memory is available. +Round-robin alternates between every possible domain page at a time. +The prefer policy accepts only a single domain in the set. +The parent of the set is consulted if the preferred domain is unavailable. +Interleave operates like round-robin with an implementation defined stripe +width. +See +.Xr domainset 9 +for more details on policies. +.It Fl p Ar pid +Specifies a pid as the target of the operation. +.It Fl s Ar setid +Specifies a set id as the target of the operation. +.It Fl r +The requested operation should reference the root set available via the +target specifier. +.It Fl t Ar tid +Specifies a thread id as the target of the operation. +.It Fl x Ar irq +Specifies an irq as the target of the operation. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Create a new group with CPUs 0-4 inclusive and run +.Pa /bin/sh +on it: +.Dl cpuset -c -l 0-4 /bin/sh +.Pp +Query the mask of CPUs the +.Aq sh pid +is allowed to run on: +.Dl cpuset -g -p <sh pid> +.Pp +Restrict +.Pa /bin/sh +to run on CPUs 0 and 2 while its group is still allowed to run on +CPUs 0-4: +.Dl cpuset -l 0,2 -p <sh pid> +.Pp +Modify the cpuset +.Pa /bin/sh +belongs to restricting it to CPUs 0 and 2: +.Dl cpuset -l 0,2 -c -p <sh pid> +.Pp +Modify the cpuset all threads are in by default to contain only +the first 4 CPUs, leaving the rest idle: +.Dl cpuset -l 0-3 -s 1 +.Pp +Print the id of the cpuset +.Pa /bin/sh +is in: +.Dl cpuset -g -i -p <sh pid> +.Pp +Move the +.Ar pid +into the specified cpuset +.Ar setid +so it may be managed with other pids in that set: +.Dl cpuset -s <setid> -p <pid> +.Pp +Create a new cpuset that is restricted to CPUs 0 and 2 and move +.Ar pid +into the new set: +.Dl cpuset -C -c -l 0,2 -p <pid> +.Sh SEE ALSO +.Xr nproc 1 , +.Xr cpuset 2 , +.Xr rctl 8 +.Sh HISTORY +The +.Nm +command first appeared in +.Fx 7.1 . +.Sh AUTHORS +.An Jeffrey Roberson Aq Mt jeff@FreeBSD.org diff --git a/corebinutils/cpuset/cpuset.c b/corebinutils/cpuset/cpuset.c new file mode 100644 index 0000000000..003c830bbe --- /dev/null +++ b/corebinutils/cpuset/cpuset.c @@ -0,0 +1,385 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2007, 2008 Jeffrey Roberson <jeff@freebsd.org> + * All rights reserved. + * + * Copyright (c) 2008 Nokia Corporation + * All rights reserved. + * + * Copyright (c) 2026 + * Project Tick. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/syscall.h> + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <sched.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +struct options { + bool get_only; + bool have_cpu_list; + bool target_is_pid; + bool target_is_tid; + const char *cpu_list; + pid_t target_id; +}; + +static const char *progname = "cpuset"; + +static void usage(void) __attribute__((__noreturn__)); +static void die(const char *fmt, ...) __attribute__((__noreturn__, format(printf, 1, 2))); +static void die_errno(const char *fmt, ...) __attribute__((__noreturn__, format(printf, 1, 2))); + +static void parse_args(int argc, char **argv, struct options *options, int *command_index); +static void validate_options(const struct options *options, bool has_command); +static long parse_long_strict(const char *text, const char *what); +static long current_tid(void); +static int current_affinity_count(void); +static cpu_set_t *alloc_cpuset(size_t *setsize, int *cpu_count); +static void parse_cpu_list(const char *text, cpu_set_t *mask, size_t setsize, int cpu_count); +static void print_mask(const cpu_set_t *mask, size_t setsize, int cpu_count); +static int sched_target(const struct options *options); +static void print_affinity(const struct options *options); +static void set_affinity(const struct options *options); + +int +main(int argc, char **argv) +{ + struct options options; + int command_index; + bool has_command; + + if (argv[0] != NULL && argv[0][0] != '\0') { + const char *base; + + base = strrchr(argv[0], '/'); + progname = base != NULL ? base + 1 : argv[0]; + } + + parse_args(argc, argv, &options, &command_index); + has_command = command_index < argc; + validate_options(&options, has_command); + + if (options.get_only) { + print_affinity(&options); + return (EXIT_SUCCESS); + } + + if (options.have_cpu_list) + set_affinity(&options); + + if (has_command) { + errno = 0; + execvp(argv[command_index], &argv[command_index]); + if (errno == ENOENT) + exit(127); + die_errno("%s", argv[command_index]); + } + + return (EXIT_SUCCESS); +} + +static void +parse_args(int argc, char **argv, struct options *options, int *command_index) +{ + int ch; + + memset(options, 0, sizeof(*options)); + options->target_id = -1; + + optind = 1; + while ((ch = getopt(argc, argv, "+Ccd:gij:l:n:p:rs:t:x:")) != -1) { + switch (ch) { + case 'g': + options->get_only = true; + break; + case 'l': + options->have_cpu_list = true; + options->cpu_list = optarg; + break; + case 'p': + options->target_is_pid = true; + options->target_id = (pid_t)parse_long_strict(optarg, "pid"); + break; + case 't': + options->target_is_tid = true; + options->target_id = (pid_t)parse_long_strict(optarg, "tid"); + break; + case 'C': + case 'c': + case 'd': + case 'i': + case 'j': + case 'n': + case 'r': + case 's': + case 'x': + die("option -%c is not supported on Linux", ch); + default: + usage(); + } + } + + *command_index = optind; +} + +static void +validate_options(const struct options *options, bool has_command) +{ + if (options->target_is_pid && options->target_is_tid) + die("choose only one target: -p or -t"); + if (options->get_only && options->have_cpu_list) + usage(); + if (options->get_only && has_command) + usage(); + if (!options->get_only && !options->have_cpu_list && !has_command) + usage(); + if (has_command && (options->target_is_pid || options->target_is_tid)) + usage(); + if (!options->get_only && !options->have_cpu_list) + usage(); +} + +static long +parse_long_strict(const char *text, const char *what) +{ + char *end; + long value; + + errno = 0; + value = strtol(text, &end, 10); + if (errno != 0 || text[0] == '\0' || *end != '\0') + die("invalid %s: %s", what, text); + if (value < 0 || value > INT_MAX) + die("%s out of range: %s", what, text); + return (value); +} + +static long +current_tid(void) +{ + return ((long)syscall(SYS_gettid)); +} + +static int +current_affinity_count(void) +{ + long configured; + + configured = sysconf(_SC_NPROCESSORS_CONF); + if (configured > 0 && configured <= INT_MAX) + return ((int)configured); + return (CPU_SETSIZE); +} + +static cpu_set_t * +alloc_cpuset(size_t *setsize, int *cpu_count) +{ + cpu_set_t *mask; + + *cpu_count = current_affinity_count(); + if (*cpu_count < 1) + *cpu_count = 1; + + mask = CPU_ALLOC((size_t)*cpu_count); + if (mask == NULL) + die_errno("CPU_ALLOC"); + *setsize = CPU_ALLOC_SIZE((size_t)*cpu_count); + CPU_ZERO_S(*setsize, mask); + return (mask); +} + +static void +parse_cpu_list(const char *text, cpu_set_t *mask, size_t setsize, int cpu_count) +{ + const char *p; + + CPU_ZERO_S(setsize, mask); + p = text; + while (*p != '\0') { + long start; + long end; + char *next; + + while (isspace((unsigned char)*p)) + p++; + if (*p == '\0') + break; + if (!isdigit((unsigned char)*p)) + die("invalid cpu list: %s", text); + + errno = 0; + start = strtol(p, &next, 10); + if (errno != 0 || start < 0 || start >= cpu_count) + die("invalid cpu list: %s", text); + end = start; + p = next; + + if (*p == '-') { + p++; + if (!isdigit((unsigned char)*p)) + die("invalid cpu list: %s", text); + errno = 0; + end = strtol(p, &next, 10); + if (errno != 0 || end < start || end >= cpu_count) + die("invalid cpu list: %s", text); + p = next; + } + + for (long cpu = start; cpu <= end; cpu++) + CPU_SET_S((int)cpu, setsize, mask); + + while (isspace((unsigned char)*p)) + p++; + if (*p == ',') { + p++; + continue; + } + if (*p != '\0') + die("invalid cpu list: %s", text); + } + + if (CPU_COUNT_S(setsize, mask) == 0) + die("invalid cpu list: %s", text); +} + +static void +print_mask(const cpu_set_t *mask, size_t setsize, int cpu_count) +{ + bool first; + + first = true; + for (int cpu = 0; cpu < cpu_count; cpu++) { + if (!CPU_ISSET_S(cpu, setsize, mask)) + continue; + if (!first) + fputs(", ", stdout); + printf("%d", cpu); + first = false; + } + putchar('\n'); +} + +static int +sched_target(const struct options *options) +{ + if (options->target_is_pid || options->target_is_tid) + return ((int)options->target_id); + return (0); +} + +static void +print_affinity(const struct options *options) +{ + cpu_set_t *mask; + size_t setsize; + int cpu_count; + int target; + + mask = alloc_cpuset(&setsize, &cpu_count); + target = sched_target(options); + if (sched_getaffinity(target, setsize, mask) != 0) { + CPU_FREE(mask); + die_errno("sched_getaffinity"); + } + + if (options->target_is_pid) + printf("pid %jd mask: ", (intmax_t)options->target_id); + else if (options->target_is_tid) + printf("tid %jd mask: ", (intmax_t)options->target_id); + else + printf("tid %ld mask: ", current_tid()); + print_mask(mask, setsize, cpu_count); + CPU_FREE(mask); +} + +static void +set_affinity(const struct options *options) +{ + cpu_set_t *mask; + size_t setsize; + int cpu_count; + int target; + + mask = alloc_cpuset(&setsize, &cpu_count); + parse_cpu_list(options->cpu_list, mask, setsize, cpu_count); + target = sched_target(options); + if (sched_setaffinity(target, setsize, mask) != 0) { + CPU_FREE(mask); + die_errno("sched_setaffinity"); + } + CPU_FREE(mask); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-l cpu-list] cmd ...\n", progname); + fprintf(stderr, " %s [-l cpu-list] [-p pid | -t tid]\n", progname); + fprintf(stderr, " %s -g [-p pid | -t tid]\n", progname); + fprintf(stderr, + "note: Linux port supports affinity operations only; FreeBSD cpuset,\n"); + fprintf(stderr, + " jail, irq, cpuset-id, and domain policy options are unavailable.\n"); + exit(1); +} + +static void +die(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "%s: ", progname); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +static void +die_errno(const char *fmt, ...) +{ + va_list ap; + int saved_errno; + + saved_errno = errno; + fprintf(stderr, "%s: ", progname); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(saved_errno)); + exit(1); +} diff --git a/corebinutils/cpuset/tests/test.sh b/corebinutils/cpuset/tests/test.sh new file mode 100644 index 0000000000..550259db8b --- /dev/null +++ b/corebinutils/cpuset/tests/test.sh @@ -0,0 +1,62 @@ +#!/bin/sh +set -eu + +: "${CPUSET_BIN:?CPUSET_BIN is required}" + +tmpdir=$(mktemp -d) +child_pid="" +trap 'if [ -n "$child_pid" ]; then kill "$child_pid" 2>/dev/null || true; wait "$child_pid" 2>/dev/null || true; fi; rm -rf "$tmpdir"' EXIT INT TERM + +fail() { + printf '%s\n' "FAIL: $1" >&2 + exit 1 +} + +assert_match() { + pattern=$1 + text=$2 + message=$3 + printf '%s\n' "$text" | grep -Eq "$pattern" || fail "$message" +} + +[ -x "$CPUSET_BIN" ] || fail "missing binary: $CPUSET_BIN" + +usage_output=$("$CPUSET_BIN" 2>&1 || true) +assert_match '^usage: cpuset ' "$usage_output" "usage output missing" + +initial_output=$("$CPUSET_BIN" -g) +assert_match '^tid [0-9]+ mask: ' "$initial_output" "current affinity output missing" + +first_cpu=$(printf '%s\n' "$initial_output" | sed -n 's/^tid [0-9][0-9]* mask: \([0-9][0-9]*\).*/\1/p' | head -n1) +[ -n "$first_cpu" ] || fail "could not determine an allowed cpu" + +"$CPUSET_BIN" -l "$first_cpu" -t $$ +self_output=$("$CPUSET_BIN" -g -t $$) +assert_match "^tid $$ mask: $first_cpu$" "$self_output" "self affinity update missing" + +"$CPUSET_BIN" -l "$first_cpu" sh -c '"$1" -g' sh "$CPUSET_BIN" >"$tmpdir/cmd.out" +cmd_output=$(cat "$tmpdir/cmd.out") +assert_match "^tid [0-9]+ mask: $first_cpu$" "$cmd_output" "command affinity output missing" + +sleep 30 & +child_pid=$! +"$CPUSET_BIN" -l "$first_cpu" -p "$child_pid" +pid_output=$("$CPUSET_BIN" -g -p "$child_pid") +assert_match "^pid $child_pid mask: $first_cpu$" "$pid_output" "pid affinity update missing" +kill "$child_pid" 2>/dev/null || true +wait "$child_pid" 2>/dev/null || true +child_pid="" + +invalid_cpu=$("$CPUSET_BIN" -l bogus 2>&1 || true) +assert_match '^cpuset: invalid cpu list: bogus$' "$invalid_cpu" "invalid cpu list not rejected" + +bad_pid=$("$CPUSET_BIN" -p nope -l "$first_cpu" 2>&1 || true) +assert_match '^cpuset: invalid pid: nope$' "$bad_pid" "invalid pid not rejected" + +bad_mix=$("$CPUSET_BIN" -p 1 -t 2 -l "$first_cpu" 2>&1 || true) +assert_match '^cpuset: choose only one target: -p or -t$' "$bad_mix" "conflicting target check missing" + +unsupported=$("$CPUSET_BIN" -n local 2>&1 || true) +assert_match '^cpuset: option -n is not supported on Linux$' "$unsupported" "unsupported option check missing" + +printf '%s\n' "PASS" |
