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