1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2007, 2008 Jeffrey Roberson <jeff@freebsd.org> 5 * All rights reserved. 6 * 7 * Copyright (c) 2008 Nokia Corporation 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __FBSDID("$FreeBSD$"); 34 35 #define _WANT_FREEBSD_BITSET 36 37 #include <sys/param.h> 38 #include <sys/types.h> 39 #include <sys/time.h> 40 #include <sys/resource.h> 41 #include <sys/cpuset.h> 42 #include <sys/domainset.h> 43 44 #include <ctype.h> 45 #include <err.h> 46 #include <errno.h> 47 #include <jail.h> 48 #include <limits.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <stdint.h> 52 #include <unistd.h> 53 #include <string.h> 54 55 static int Cflag; 56 static int cflag; 57 static int dflag; 58 static int gflag; 59 static int iflag; 60 static int jflag; 61 static int lflag; 62 static int nflag; 63 static int pflag; 64 static int rflag; 65 static int sflag; 66 static int tflag; 67 static int xflag; 68 static id_t id; 69 static cpulevel_t level; 70 static cpuwhich_t which; 71 72 static void usage(void) __dead2; 73 74 struct numa_policy { 75 const char *name; 76 int policy; 77 }; 78 79 static struct numa_policy policies[] = { 80 { "round-robin", DOMAINSET_POLICY_ROUNDROBIN }, 81 { "rr", DOMAINSET_POLICY_ROUNDROBIN }, 82 { "first-touch", DOMAINSET_POLICY_FIRSTTOUCH }, 83 { "ft", DOMAINSET_POLICY_FIRSTTOUCH }, 84 { "prefer", DOMAINSET_POLICY_PREFER }, 85 { "interleave", DOMAINSET_POLICY_INTERLEAVE}, 86 { "il", DOMAINSET_POLICY_INTERLEAVE}, 87 { NULL, DOMAINSET_POLICY_INVALID } 88 }; 89 90 static void printset(struct bitset *mask, int size); 91 92 static void 93 parselist(char *list, struct bitset *mask, int size) 94 { 95 enum { NONE, NUM, DASH } state; 96 int lastnum; 97 int curnum; 98 char *l; 99 100 state = NONE; 101 curnum = lastnum = 0; 102 for (l = list; *l != '\0';) { 103 if (isdigit(*l)) { 104 curnum = atoi(l); 105 if (curnum >= size) 106 errx(EXIT_FAILURE, 107 "List entry %d exceeds maximum of %d", 108 curnum, size - 1); 109 while (isdigit(*l)) 110 l++; 111 switch (state) { 112 case NONE: 113 lastnum = curnum; 114 state = NUM; 115 break; 116 case DASH: 117 for (; lastnum <= curnum; lastnum++) 118 BIT_SET(size, lastnum, mask); 119 state = NONE; 120 break; 121 case NUM: 122 default: 123 goto parserr; 124 } 125 continue; 126 } 127 switch (*l) { 128 case ',': 129 switch (state) { 130 case NONE: 131 break; 132 case NUM: 133 BIT_SET(size, curnum, mask); 134 state = NONE; 135 break; 136 case DASH: 137 goto parserr; 138 break; 139 } 140 break; 141 case '-': 142 if (state != NUM) 143 goto parserr; 144 state = DASH; 145 break; 146 default: 147 goto parserr; 148 } 149 l++; 150 } 151 switch (state) { 152 case NONE: 153 break; 154 case NUM: 155 BIT_SET(size, curnum, mask); 156 break; 157 case DASH: 158 goto parserr; 159 } 160 return; 161 parserr: 162 errx(EXIT_FAILURE, "Malformed list %s", list); 163 } 164 165 static void 166 parsecpulist(char *list, cpuset_t *mask) 167 { 168 169 if (strcasecmp(list, "all") == 0) { 170 if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, 171 sizeof(*mask), mask) != 0) 172 err(EXIT_FAILURE, "getaffinity"); 173 return; 174 } 175 parselist(list, (struct bitset *)mask, CPU_SETSIZE); 176 } 177 178 /* 179 * permissively parse policy:domain list 180 * allow: 181 * round-robin:0-4 explicit 182 * round-robin:all explicit root domains 183 * 0-4 implicit root policy 184 * round-robin implicit root domains 185 * all explicit root domains and implicit policy 186 */ 187 static void 188 parsedomainlist(char *list, domainset_t *mask, int *policyp) 189 { 190 domainset_t rootmask; 191 struct numa_policy *policy; 192 char *l; 193 int p; 194 195 /* 196 * Use the rootset's policy as the default for unspecified policies. 197 */ 198 if (cpuset_getdomain(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, 199 sizeof(rootmask), &rootmask, &p) != 0) 200 err(EXIT_FAILURE, "getdomain"); 201 202 l = list; 203 for (policy = &policies[0]; policy->name != NULL; policy++) { 204 if (strncasecmp(l, policy->name, strlen(policy->name)) == 0) { 205 p = policy->policy; 206 l += strlen(policy->name); 207 if (*l != ':' && *l != '\0') 208 errx(EXIT_FAILURE, "Malformed list %s", list); 209 if (*l == ':') 210 l++; 211 break; 212 } 213 } 214 *policyp = p; 215 if (strcasecmp(l, "all") == 0 || *l == '\0') { 216 DOMAINSET_COPY(&rootmask, mask); 217 return; 218 } 219 parselist(l, (struct bitset *)mask, DOMAINSET_SETSIZE); 220 } 221 222 static void 223 printset(struct bitset *mask, int size) 224 { 225 int once; 226 int bit; 227 228 for (once = 0, bit = 0; bit < size; bit++) { 229 if (BIT_ISSET(size, bit, mask)) { 230 if (once == 0) { 231 printf("%d", bit); 232 once = 1; 233 } else 234 printf(", %d", bit); 235 } 236 } 237 printf("\n"); 238 } 239 240 static const char *whichnames[] = { NULL, "tid", "pid", "cpuset", "irq", "jail", 241 "domain" }; 242 static const char *levelnames[] = { NULL, " root", " cpuset", "" }; 243 static const char *policynames[] = { "invalid", "round-robin", "first-touch", 244 "prefer", "interleave" }; 245 246 static void 247 printaffinity(void) 248 { 249 domainset_t domain; 250 cpuset_t mask; 251 int policy; 252 253 if (cpuset_getaffinity(level, which, id, sizeof(mask), &mask) != 0) 254 err(EXIT_FAILURE, "getaffinity"); 255 printf("%s %jd%s mask: ", whichnames[which], (intmax_t)id, 256 levelnames[level]); 257 printset((struct bitset *)&mask, CPU_SETSIZE); 258 if (dflag || xflag) 259 goto out; 260 if (cpuset_getdomain(level, which, id, sizeof(domain), &domain, 261 &policy) != 0) 262 err(EXIT_FAILURE, "getdomain"); 263 printf("%s %jd%s domain policy: %s mask: ", whichnames[which], 264 (intmax_t)id, levelnames[level], policynames[policy]); 265 printset((struct bitset *)&domain, DOMAINSET_SETSIZE); 266 out: 267 exit(EXIT_SUCCESS); 268 } 269 270 static void 271 printsetid(void) 272 { 273 cpusetid_t setid; 274 275 /* 276 * Only LEVEL_WHICH && WHICH_CPUSET has a numbered id. 277 */ 278 if (level == CPU_LEVEL_WHICH && !sflag) 279 level = CPU_LEVEL_CPUSET; 280 if (cpuset_getid(level, which, id, &setid)) 281 err(errno, "getid"); 282 printf("%s %jd%s id: %d\n", whichnames[which], (intmax_t)id, 283 levelnames[level], setid); 284 } 285 286 int 287 main(int argc, char *argv[]) 288 { 289 domainset_t domains; 290 cpusetid_t setid; 291 cpuset_t mask; 292 int policy; 293 lwpid_t tid; 294 pid_t pid; 295 int ch; 296 297 CPU_ZERO(&mask); 298 DOMAINSET_ZERO(&domains); 299 policy = DOMAINSET_POLICY_INVALID; 300 level = CPU_LEVEL_WHICH; 301 which = CPU_WHICH_PID; 302 id = pid = tid = setid = -1; 303 while ((ch = getopt(argc, argv, "Ccd:gij:l:n:p:rs:t:x:")) != -1) { 304 switch (ch) { 305 case 'C': 306 Cflag = 1; 307 break; 308 case 'c': 309 cflag = 1; 310 level = CPU_LEVEL_CPUSET; 311 break; 312 case 'd': 313 dflag = 1; 314 which = CPU_WHICH_DOMAIN; 315 id = atoi(optarg); 316 break; 317 case 'g': 318 gflag = 1; 319 break; 320 case 'i': 321 iflag = 1; 322 break; 323 case 'j': 324 jflag = 1; 325 which = CPU_WHICH_JAIL; 326 id = jail_getid(optarg); 327 if (id < 0) 328 errx(EXIT_FAILURE, "%s", jail_errmsg); 329 break; 330 case 'l': 331 lflag = 1; 332 parsecpulist(optarg, &mask); 333 break; 334 case 'n': 335 nflag = 1; 336 parsedomainlist(optarg, &domains, &policy); 337 break; 338 case 'p': 339 pflag = 1; 340 which = CPU_WHICH_PID; 341 id = pid = atoi(optarg); 342 break; 343 case 'r': 344 level = CPU_LEVEL_ROOT; 345 rflag = 1; 346 break; 347 case 's': 348 sflag = 1; 349 which = CPU_WHICH_CPUSET; 350 id = setid = atoi(optarg); 351 break; 352 case 't': 353 tflag = 1; 354 which = CPU_WHICH_TID; 355 id = tid = atoi(optarg); 356 break; 357 case 'x': 358 xflag = 1; 359 which = CPU_WHICH_IRQ; 360 id = atoi(optarg); 361 break; 362 default: 363 usage(); 364 } 365 } 366 argc -= optind; 367 argv += optind; 368 if (gflag) { 369 if (argc || Cflag || lflag || nflag) 370 usage(); 371 /* Only one identity specifier. */ 372 if (dflag + jflag + xflag + sflag + pflag + tflag > 1) 373 usage(); 374 if (iflag) 375 printsetid(); 376 else 377 printaffinity(); 378 exit(EXIT_SUCCESS); 379 } 380 381 if (dflag || iflag || rflag) 382 usage(); 383 /* 384 * The user wants to run a command with a set and possibly cpumask. 385 */ 386 if (argc) { 387 if (Cflag || pflag || tflag || xflag || jflag) 388 usage(); 389 if (sflag) { 390 if (cpuset_setid(CPU_WHICH_PID, -1, setid)) 391 err(argc, "setid"); 392 } else { 393 if (cpuset(&setid)) 394 err(argc, "newid"); 395 } 396 if (lflag) { 397 if (cpuset_setaffinity(level, CPU_WHICH_PID, 398 -1, sizeof(mask), &mask) != 0) 399 err(EXIT_FAILURE, "setaffinity"); 400 } 401 if (nflag) { 402 if (cpuset_setdomain(level, CPU_WHICH_PID, 403 -1, sizeof(domains), &domains, policy) != 0) 404 err(EXIT_FAILURE, "setdomain"); 405 } 406 errno = 0; 407 execvp(*argv, argv); 408 err(errno == ENOENT ? 127 : 126, "%s", *argv); 409 } 410 /* 411 * We're modifying something that presently exists. 412 */ 413 if (Cflag && (jflag || !pflag || sflag || tflag || xflag)) 414 usage(); 415 if ((!lflag && !nflag) && cflag) 416 usage(); 417 if ((!lflag && !nflag) && !(Cflag || sflag)) 418 usage(); 419 /* You can only set a mask on a thread. */ 420 if (tflag && (sflag | pflag | xflag | jflag)) 421 usage(); 422 /* You can only set a mask on an irq. */ 423 if (xflag && (jflag | pflag | sflag | tflag)) 424 usage(); 425 if (Cflag) { 426 /* 427 * Create a new cpuset and move the specified process 428 * into the set. 429 */ 430 if (cpuset(&setid) < 0) 431 err(EXIT_FAILURE, "newid"); 432 sflag = 1; 433 } 434 if (pflag && sflag) { 435 if (cpuset_setid(CPU_WHICH_PID, pid, setid)) 436 err(EXIT_FAILURE, "setid"); 437 /* 438 * If the user specifies a set and a list we want the mask 439 * to effect the pid and not the set. 440 */ 441 which = CPU_WHICH_PID; 442 id = pid; 443 } 444 if (lflag) { 445 if (cpuset_setaffinity(level, which, id, sizeof(mask), 446 &mask) != 0) 447 err(EXIT_FAILURE, "setaffinity"); 448 } 449 if (nflag) { 450 if (cpuset_setdomain(level, which, id, sizeof(domains), 451 &domains, policy) != 0) 452 err(EXIT_FAILURE, "setdomain"); 453 } 454 455 exit(EXIT_SUCCESS); 456 } 457 458 static void 459 usage(void) 460 { 461 462 fprintf(stderr, 463 "usage: cpuset [-l cpu-list] [-n policy:domain-list] [-s setid] cmd ...\n"); 464 fprintf(stderr, 465 " cpuset [-l cpu-list] [-n policy:domain-list] [-s setid] -p pid\n"); 466 fprintf(stderr, 467 " cpuset [-c] [-l cpu-list] [-n policy:domain-list] -C -p pid\n"); 468 fprintf(stderr, 469 " cpuset [-c] [-l cpu-list] [-n policy:domain-list]\n" 470 " [-j jailid | -p pid | -t tid | -s setid | -x irq]\n"); 471 fprintf(stderr, 472 " cpuset -g [-cir]\n" 473 " [-d domain | -j jailid | -p pid | -t tid | -s setid | -x irq]\n"); 474 exit(1); 475 } 476