1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright 2015 Ryan Zezeski
  24  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
  25  * Use is subject to license terms.
  26  */
  27 
  28 /*
  29  * pbind - bind a process to a processor (non-exclusively)
  30  */
  31 
  32 #include <sys/types.h>
  33 #include <sys/procset.h>
  34 #include <sys/processor.h>
  35 #include <stdio.h>
  36 #include <stdlib.h>
  37 #include <string.h>
  38 #include <procfs.h>
  39 #include <fcntl.h>
  40 #include <errno.h>
  41 #include <dirent.h>
  42 #include <locale.h>
  43 #include <libproc.h>
  44 #include <stdarg.h>
  45 
  46 #if !defined(TEXT_DOMAIN)               /* should be defined by cc -D */
  47 #define TEXT_DOMAIN     "SYS_TEST"      /* Use this only if it weren't */
  48 #endif
  49 
  50 #define ERR_OK          0               /* exit status for success */
  51 #define ERR_FAIL        1               /* exit status for errors */
  52 #define ERR_USAGE       2               /* exit status for usage errors */
  53 
  54 static char     *progname;
  55 static char     bflag;
  56 static char     eflag;
  57 static char     qflag;
  58 static char     Qflag;
  59 static char     uflag;
  60 static char     Uflag;
  61 static int      errors;
  62 
  63 #define MAX_PROCFS_PATH 80
  64 
  65 /*PRINTFLIKE1*/
  66 static void
  67 warn(char *format, ...)
  68 {
  69         int err = errno;
  70         va_list alist;
  71 
  72         (void) fprintf(stderr, "%s: ", progname);
  73         va_start(alist, format);
  74         (void) vfprintf(stderr, format, alist);
  75         va_end(alist);
  76         if (strchr(format, '\n') == NULL)
  77                 (void) fprintf(stderr, ": %s\n", strerror(err));
  78 }
  79 
  80 /*PRINTFLIKE1*/
  81 static void
  82 die(char *format, ...)
  83 {
  84         int err = errno;
  85         va_list alist;
  86 
  87         (void) fprintf(stderr, "%s: ", progname);
  88         va_start(alist, format);
  89         (void) vfprintf(stderr, format, alist);
  90         va_end(alist);
  91         if (strchr(format, '\n') == NULL)
  92                 (void) fprintf(stderr, ": %s\n", strerror(err));
  93         exit(ERR_FAIL);
  94 }
  95 
  96 /*
  97  * Output for query.
  98  */
  99 static void
 100 query_out(id_t pid, id_t lwpid, processorid_t cpu)
 101 {
 102         char *proclwp;
 103         char pidstr[21];
 104 
 105         if (lwpid == -1) {
 106                 (void) snprintf(pidstr, 20, "%d", (int)pid);
 107                 proclwp = "process";
 108         } else {
 109                 (void) snprintf(pidstr, 20, "%d/%d", (int)pid, (int)lwpid);
 110                 proclwp = "lwp";
 111         }
 112 
 113         if (cpu == PBIND_NONE)
 114                 (void) printf(gettext("%s id %s: not bound\n"),
 115                     proclwp, pidstr);
 116         else
 117                 (void) printf(gettext("%s id %s: %d\n"),
 118                     proclwp, pidstr, cpu);
 119 }
 120 
 121 /*
 122  * Binding error.
 123  */
 124 static void
 125 bind_err(processorid_t cpu, id_t pid, id_t lwpid, int err)
 126 {
 127         char *msg;
 128 
 129         switch (cpu) {
 130         case PBIND_NONE:
 131                 msg = gettext("unbind");
 132                 break;
 133         case PBIND_QUERY:
 134                 msg = gettext("query");
 135                 break;
 136         default:
 137                 msg = gettext("bind");
 138                 break;
 139         }
 140         if (lwpid == -1)
 141                 warn(gettext("cannot %s pid %d: %s\n"), msg,
 142                     (int)pid, strerror(err));
 143         else
 144                 warn(gettext("cannot %s lwpid %d/%d: %s\n"), msg,
 145                     (int)pid, (int)lwpid, strerror(err));
 146 }
 147 
 148 /*
 149  * Output for bind.
 150  */
 151 static void
 152 bind_out(id_t pid, id_t lwpid, processorid_t old, processorid_t new)
 153 {
 154         char *proclwp;
 155         char pidstr[21];
 156 
 157         if (lwpid == -1) {
 158                 (void) snprintf(pidstr, 20, "%d", (int)pid);
 159                 proclwp = "process";
 160         } else {
 161                 (void) snprintf(pidstr, 20, "%d/%d", (int)pid, (int)lwpid);
 162                 proclwp = "lwp";
 163         }
 164 
 165         if (old == PBIND_NONE) {
 166                 if (new == PBIND_NONE)
 167                         (void) printf(gettext("%s id %s: was not bound, "
 168                             "now not bound\n"), proclwp, pidstr);
 169                 else
 170                         (void) printf(gettext("%s id %s: was not bound, "
 171                             "now %d\n"), proclwp, pidstr, new);
 172         } else {
 173                 if (new == PBIND_NONE)
 174                         (void) printf(gettext("%s id %s: was %d, "
 175                             "now not bound\n"), proclwp, pidstr, old);
 176                 else
 177                         (void) printf(gettext("%s id %s: was %d, "
 178                             "now %d\n"), proclwp, pidstr, old, new);
 179         }
 180 }
 181 
 182 static struct ps_prochandle *
 183 grab_proc(id_t pid)
 184 {
 185         int ret;
 186         struct ps_prochandle *Pr;
 187 
 188         if ((Pr = Pgrab(pid, 0, &ret)) == NULL) {
 189                 warn(gettext("cannot control process %d: %s\n"),
 190                     (int)pid, Pgrab_error(ret));
 191                 errors = ERR_FAIL;
 192                 return (NULL);
 193         }
 194 
 195         /*
 196          * Set run-on-last-close flag so the controlled process
 197          * runs even if we die on a signal, and create an agent LWP.
 198          */
 199         if (Psetflags(Pr, PR_RLC) != 0 || Pcreate_agent(Pr) != 0) {
 200                 warn(gettext("cannot control process %d\n"), (int)pid);
 201                 errors = ERR_FAIL;
 202                 Prelease(Pr, 0);
 203                 return (NULL);
 204         }
 205         return (Pr);
 206 }
 207 
 208 static void
 209 rele_proc(struct ps_prochandle *Pr)
 210 {
 211         if (Pr == NULL)
 212                 return;
 213         Pdestroy_agent(Pr);
 214         Prelease(Pr, 0);
 215 }
 216 
 217 static void
 218 bind_lwp(struct ps_prochandle *Pr, id_t pid, id_t lwpid, processorid_t cpu)
 219 {
 220         processorid_t old_cpu;
 221 
 222         if (pr_processor_bind(Pr, P_LWPID, lwpid, cpu, &old_cpu) < 0) {
 223                 bind_err(cpu, pid, lwpid, errno);
 224                 errors = ERR_FAIL;
 225         } else {
 226                 if (qflag)
 227                         query_out(pid, lwpid, old_cpu);
 228                 else
 229                         bind_out(pid, lwpid, old_cpu, cpu);
 230         }
 231 }
 232 
 233 /*
 234  * Query, set, or clear bindings for the range of LWPs in the given process.
 235  */
 236 static int
 237 do_lwps(id_t pid, const char *range, processorid_t cpu)
 238 {
 239         char procfile[MAX_PROCFS_PATH];
 240         struct ps_prochandle *Pr;
 241         struct prheader header;
 242         processorid_t binding;
 243         struct lwpsinfo *lwp;
 244         char *lpsinfo, *ptr;
 245         int nent, size;
 246         int i, fd, found;
 247 
 248         /*
 249          * Report bindings for LWPs in process 'pid'.
 250          */
 251         (void) snprintf(procfile, MAX_PROCFS_PATH,
 252             "/proc/%d/lpsinfo", (int)pid);
 253         if ((fd = open(procfile, O_RDONLY)) < 0) {
 254                 if (errno == ENOENT)
 255                         errno = ESRCH;
 256                 bind_err(cpu, pid, -1, errno);
 257                 return (ERR_FAIL);
 258         }
 259         if (pread(fd, &header, sizeof (header), 0) != sizeof (header)) {
 260                 (void) close(fd);
 261                 bind_err(cpu, pid, -1, errno);
 262                 return (ERR_FAIL);
 263         }
 264         nent = header.pr_nent;
 265         size = header.pr_entsize * nent;
 266         ptr = lpsinfo = malloc(size);
 267         if (lpsinfo == NULL) {
 268                 bind_err(cpu, pid, -1, errno);
 269                 return (ERR_FAIL);
 270         }
 271         if (pread(fd, lpsinfo, size, sizeof (header)) != size) {
 272                 bind_err(cpu, pid, -1, errno);
 273                 free(lpsinfo);
 274                 (void) close(fd);
 275                 return (ERR_FAIL);
 276         }
 277 
 278         if ((bflag || uflag) && (Pr = grab_proc(pid)) == NULL) {
 279                 free(lpsinfo);
 280                 (void) close(fd);
 281                 return (ERR_FAIL);
 282         }
 283         found = 0;
 284         for (i = 0; i < nent; i++, ptr += header.pr_entsize) {
 285                 /*LINTED ALIGNMENT*/
 286                 lwp = (lwpsinfo_t *)ptr;
 287                 binding = lwp->pr_bindpro;
 288                 if (!proc_lwp_in_set(range, lwp->pr_lwpid))
 289                         continue;
 290                 found++;
 291                 if (bflag || uflag)
 292                         bind_lwp(Pr, pid, lwp->pr_lwpid, cpu);
 293                 else if (binding != PBIND_NONE)
 294                         query_out(pid, lwp->pr_lwpid, binding);
 295         }
 296         if (bflag || uflag)
 297                 rele_proc(Pr);
 298         free(lpsinfo);
 299         (void) close(fd);
 300         if (found == 0) {
 301                 warn(gettext("cannot %s lwpid %d/%s: "
 302                     "No matching LWPs found\n"),
 303                     bflag ? "bind" : "query", pid, range);
 304                 return (ERR_FAIL);
 305         }
 306         return (ERR_OK);
 307 }
 308 
 309 /*ARGSUSED*/
 310 static int
 311 query_all_proc(psinfo_t *psinfo, lwpsinfo_t *lwpsinfo, void *arg)
 312 {
 313         id_t pid = psinfo->pr_pid;
 314         processorid_t binding;
 315 
 316         if (processor_bind(P_PID, pid, PBIND_QUERY, &binding) < 0) {
 317                 /*
 318                  * Ignore search errors.  The process may have exited
 319                  * since we read the directory.
 320                  */
 321                 if (errno == ESRCH)
 322                         return (0);
 323                 bind_err(PBIND_QUERY, pid, -1, errno);
 324                 errors = ERR_FAIL;
 325                 return (0);
 326         }
 327         if (binding != PBIND_NONE)
 328                 query_out(pid, -1, binding);
 329         return (0);
 330 }
 331 
 332 static int
 333 query_all_lwp(psinfo_t *psinfo, lwpsinfo_t *lwpsinfo, void *arg)
 334 {
 335         id_t pid = psinfo->pr_pid;
 336         id_t lwpid = lwpsinfo->pr_lwpid;
 337         processorid_t *cpuid = arg;
 338         processorid_t binding = lwpsinfo->pr_bindpro;
 339 
 340         if (psinfo->pr_nlwp == 1)
 341                 lwpid = -1;     /* report process bindings if only 1 lwp */
 342         if ((cpuid != NULL && *cpuid == binding) ||
 343             (cpuid == NULL && binding != PBIND_NONE))
 344                 query_out(pid, lwpid, binding);
 345         return (0);
 346 }
 347 
 348 /*
 349  * Execute the cmd with args while bound to cpu. Does not return:
 350  * either executes cmd successfully or dies trying.
 351  */
 352 static void
 353 exec_cmd(processorid_t cpu, char *cmd, char **args)
 354 {
 355         if (processor_bind(P_PID, P_MYID, cpu, NULL) == -1) {
 356                 bind_err(cpu, getpid(), -1, errno);
 357                 exit(ERR_FAIL);
 358         }
 359 
 360         if (execvp(cmd, args) == -1)
 361                 die(gettext("failed to exec %s\n"), cmd);
 362 }
 363 
 364 /*
 365  * Attempt to parse str as a CPU identifier. Return the identifier or
 366  * die.
 367  */
 368 static processorid_t
 369 parse_cpu(char *str)
 370 {
 371         processorid_t cpu;
 372         char *endstr;
 373 
 374         cpu = strtol(str, &endstr, 10);
 375         if (endstr != NULL && *endstr != '\0' || cpu < 0)
 376                 die(gettext("invalid processor ID %s\n"), optarg);
 377 
 378         return (cpu);
 379 }
 380 
 381 static int
 382 usage(void)
 383 {
 384         (void) fprintf(stderr,
 385             gettext("usage: \n\t%1$s -b processor_id pid[/lwpids] ...\n"
 386             "\t%1$s -e processor_id cmd [args...]\n"
 387             "\t%1$s -U [processor_id] ...\n"
 388             "\t%1$s -Q [processor_id] ...\n"
 389             "\t%1$s -u pid[/lwpids] ...\n"
 390             "\t%1$s [-q] [pid[/lwpids] ...]\n"),
 391             progname);
 392         return (ERR_USAGE);
 393 }
 394 
 395 int
 396 main(int argc, char *argv[])
 397 {
 398         int c;
 399         int ret;
 400         id_t pid;
 401         processorid_t cpu, old_cpu;
 402         char *endstr;
 403 
 404         progname = argv[0];     /* put actual command name in messages */
 405 
 406         (void) setlocale(LC_ALL, "");   /* setup localization */
 407         (void) textdomain(TEXT_DOMAIN);
 408 
 409         while ((c = getopt(argc, argv, "b:e:qQuU")) != EOF) {
 410                 switch (c) {
 411 
 412                 case 'b':
 413                         bflag = 1;
 414                         cpu = parse_cpu(optarg);
 415                         break;
 416 
 417                 case 'e':
 418                         eflag = 1;
 419                         cpu = parse_cpu(optarg);
 420                         break;
 421 
 422                 case 'q':
 423                         qflag = 1;
 424                         cpu = PBIND_QUERY;
 425                         break;
 426 
 427                 case 'Q':
 428                         Qflag = 1;
 429                         cpu = PBIND_QUERY;
 430                         break;
 431 
 432                 case 'u':
 433                         uflag = 1;
 434                         cpu = PBIND_NONE;
 435                         break;
 436 
 437                 case 'U':
 438                         Uflag = 1;
 439                         break;
 440 
 441                 default:
 442                         return (usage());
 443                 }
 444         }
 445 
 446 
 447         /*
 448          * Make sure that at most one of the options b, e, q, Q, u, or
 449          * U was specified.
 450          */
 451         c = bflag + eflag + qflag + Qflag + uflag + Uflag;
 452         if (c < 1) {                         /* nothing specified */
 453                 qflag = 1;                      /* default to query */
 454                 cpu = PBIND_QUERY;
 455         } else if (c > 1) {
 456                 warn(gettext("options -b, -e, -q, -Q, -u and -U "
 457                     "are mutually exclusive\n"));
 458                 return (usage());
 459         }
 460 
 461         errors = 0;
 462         argc -= optind;
 463         argv += optind;
 464 
 465         /*
 466          * Handle query of all processes.
 467          */
 468         if (argc == 0) {
 469                 if (bflag || uflag) {
 470                         warn(gettext("must specify at least one pid\n"));
 471                         return (usage());
 472                 }
 473                 if (eflag) {
 474                         warn(gettext("must specify command\n"));
 475                         return (usage());
 476                 }
 477                 if (Uflag) {
 478                         if (processor_bind(P_ALL, 0, PBIND_NONE, &old_cpu) != 0)
 479                                 die(gettext("failed to unbind some LWPs"));
 480                 }
 481                 if (Qflag) {
 482                         (void) proc_walk(query_all_lwp, NULL, PR_WALK_LWP);
 483                         return (errors);
 484                 } else {
 485                         (void) proc_walk(query_all_proc, NULL, PR_WALK_PROC);
 486                         return (errors);
 487                 }
 488         }
 489 
 490         if (eflag)
 491                 exec_cmd(cpu, argv[0], argv);
 492 
 493         if (Qflag || Uflag) {
 494                 /*
 495                  * Go through listed processor IDs.
 496                  */
 497                 for (; argc > 0; argv++, argc--) {
 498                         errno = 0;
 499                         cpu = (id_t)strtol(*argv, &endstr, 10);
 500                         if (errno != 0 || (endstr != NULL && *endstr != '\0') ||
 501                             p_online(cpu, P_STATUS) == -1) {
 502                                 warn(gettext("invalid processor ID\n"));
 503                                 continue;
 504                         }
 505                         if (Qflag) {
 506                                 (void) proc_walk(query_all_lwp,
 507                                     &cpu, PR_WALK_LWP);
 508                                 continue;
 509                         }
 510                         if (Uflag) {
 511                                 if (processor_bind(P_CPUID, cpu,
 512                                     PBIND_NONE, &old_cpu) != 0) {
 513                                         warn(gettext("failed to unbind from "
 514                                             "processor %d"), (int)cpu);
 515                                         errors = ERR_FAIL;
 516                                 }
 517                                 continue;
 518                         }
 519                 }
 520                 return (errors);
 521         }
 522 
 523         /*
 524          * Go through listed process[/lwp_ranges].
 525          */
 526         for (; argc > 0; argv++, argc--) {
 527                 errno = 0;
 528                 pid = (id_t)strtol(*argv, &endstr, 10);
 529                 if (errno != 0 ||
 530                     (endstr != NULL && *endstr != '\0' && *endstr != '/')) {
 531                         warn(gettext("invalid process ID: %s\n"), *argv);
 532                         continue;
 533                 }
 534                 if (endstr != NULL && *endstr == '/') {
 535                         /*
 536                          * Handle lwp range case
 537                          */
 538                         const char *lwps = (const char *)(++endstr);
 539                         if (*lwps == '\0' ||
 540                             proc_lwp_range_valid(lwps) != 0) {
 541                                 warn(gettext("invalid lwp range "
 542                                     "for pid %d\n"), (int)pid);
 543                                 errors = ERR_FAIL;
 544                                 continue;
 545                         }
 546                         if (!qflag)
 547                                 (void) proc_initstdio();
 548                         ret = do_lwps(pid, lwps, qflag ? PBIND_QUERY : cpu);
 549                         if (!qflag)
 550                                 (void) proc_finistdio();
 551                         if (ret != ERR_OK)
 552                                 errors = ret;
 553                 } else {
 554                         /*
 555                          * Handle whole process case.
 556                          */
 557                         if (processor_bind(P_PID, pid, cpu, &old_cpu) < 0) {
 558                                 bind_err(cpu, pid, -1, errno);
 559                                 errors = ERR_FAIL;
 560                                 continue;
 561                         }
 562                         if (qflag)
 563                                 query_out(pid, -1, old_cpu);
 564                         else
 565                                 bind_out(pid, -1, old_cpu, cpu);
 566                 }
 567         }
 568         return (errors);
 569 }