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 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <sys/types.h> 30 #include <sys/task.h> 31 32 #include <alloca.h> 33 #include <libproc.h> 34 #include <libintl.h> 35 #include <libgen.h> 36 #include <limits.h> 37 #include <project.h> 38 #include <pwd.h> 39 #include <secdb.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <sys/varargs.h> 44 #include <unistd.h> 45 #include <errno.h> 46 #include <signal.h> 47 #include <priv_utils.h> 48 49 #include "utils.h" 50 51 #define OPTIONS_STRING "Fc:lp:v" 52 #define NENV 8 53 #define ENVSIZE 255 54 #define PATH "PATH=/usr/bin" 55 #define SUPATH "PATH=/usr/sbin:/usr/bin" 56 #define SHELL "/usr/bin/sh" 57 #define SHELL2 "/sbin/sh" 58 #define TIMEZONEFILE "/etc/default/init" 59 #define LOGINFILE "/etc/default/login" 60 #define GLOBAL_ERR_SZ 1024 61 #define GRAB_RETRY_MAX 100 62 63 static const char *pname; 64 extern char **environ; 65 static char *supath = SUPATH; 66 static char *path = PATH; 67 static char global_error[GLOBAL_ERR_SZ]; 68 static int verbose = 0; 69 70 static priv_set_t *nset; 71 72 /* Private definitions for libproject */ 73 extern projid_t setproject_proc(const char *, const char *, int, pid_t, 74 struct ps_prochandle *, struct project *); 75 extern priv_set_t *setproject_initpriv(void); 76 77 static void usage(void); 78 79 static void preserve_error(const char *format, ...); 80 81 static int update_running_proc(int, char *, char *); 82 static int set_ids(struct ps_prochandle *, struct project *, 83 struct passwd *); 84 static struct passwd *match_user(uid_t, char *, int); 85 static void setproject_err(char *, char *, int, struct project *); 86 87 static void 88 usage(void) 89 { 90 (void) fprintf(stderr, gettext("usage: \n\t%s [-v] [-p project] " 91 "[-c pid | [-Fl] [command [args ...]]]\n"), pname); 92 exit(2); 93 } 94 95 int 96 main(int argc, char *argv[]) 97 { 98 int c; 99 struct passwd *pw; 100 char *projname = NULL; 101 uid_t uid; 102 int login_flag = 0; 103 int finalize_flag = TASK_NORMAL; 104 int newproj_flag = 0; 105 taskid_t taskid; 106 char *shell; 107 char *env[NENV]; 108 char **targs; 109 char *filename, *procname = NULL; 110 int error; 111 112 nset = setproject_initpriv(); 113 if (nset == NULL) 114 die(gettext("privilege initialization failed\n")); 115 116 pname = getpname(argv[0]); 117 118 while ((c = getopt(argc, argv, OPTIONS_STRING)) != EOF) { 119 switch (c) { 120 case 'v': 121 verbose = 1; 122 break; 123 case 'p': 124 newproj_flag = 1; 125 projname = optarg; 126 break; 127 case 'F': 128 finalize_flag = TASK_FINAL; 129 break; 130 case 'l': 131 login_flag++; 132 break; 133 case 'c': 134 procname = optarg; 135 break; 136 case '?': 137 default: 138 usage(); 139 /*NOTREACHED*/ 140 } 141 } 142 143 /* -c option is invalid with -F, -l, or a specified command */ 144 if ((procname != NULL) && 145 (finalize_flag == TASK_FINAL || login_flag || optind < argc)) 146 usage(); 147 148 if (procname != NULL) { 149 /* Change project/task of an existing process */ 150 return (update_running_proc(newproj_flag, procname, projname)); 151 } 152 153 /* 154 * Get user data, so that we can confirm project membership as 155 * well as construct an appropriate login environment. 156 */ 157 uid = getuid(); 158 if ((pw = match_user(uid, projname, 1)) == NULL) { 159 die("%s\n", global_error); 160 } 161 162 /* 163 * If no projname was specified, we're just creating a new task 164 * under the current project, so we can just set the new taskid. 165 * If our project is changing, we need to update any attendant 166 * pool/rctl bindings, so let setproject() do the dirty work. 167 */ 168 (void) __priv_bracket(PRIV_ON); 169 if (projname == NULL) { 170 if (settaskid(getprojid(), finalize_flag) == -1) 171 if (errno == EAGAIN) 172 die(gettext("resource control limit has been " 173 "reached")); 174 else 175 die(gettext("settaskid failed")); 176 } else { 177 if ((error = setproject(projname, 178 pw->pw_name, finalize_flag)) != 0) { 179 setproject_err(pw->pw_name, projname, error, NULL); 180 if (error < 0) 181 die("%s\n", global_error); 182 else 183 warn("%s\n", global_error); 184 } 185 } 186 __priv_relinquish(); 187 188 taskid = gettaskid(); 189 190 if (verbose) 191 (void) fprintf(stderr, "%d\n", (int)taskid); 192 193 /* 194 * Validate user's shell from passwd database. 195 */ 196 if (strcmp(pw->pw_shell, "") == 0) { 197 if (access(SHELL, X_OK) == 0) 198 pw->pw_shell = SHELL; 199 else 200 pw->pw_shell = SHELL2; 201 } 202 203 if (login_flag) { 204 /* 205 * Since we've been invoked as a "simulated login", set up the 206 * environment. 207 */ 208 char *cur_tz = getenv("TZ"); 209 char *cur_term = getenv("TERM"); 210 211 char **envnext; 212 213 size_t len_home = strlen(pw->pw_dir) + strlen("HOME=") + 1; 214 size_t len_logname = strlen(pw->pw_name) + strlen("LOGNAME=") + 215 1; 216 size_t len_shell = strlen(pw->pw_shell) + strlen("SHELL=") + 1; 217 size_t len_mail = strlen(pw->pw_name) + 218 strlen("MAIL=/var/mail/") + 1; 219 size_t len_tz; 220 size_t len_term; 221 222 char *env_home = safe_malloc(len_home); 223 char *env_logname = safe_malloc(len_logname); 224 char *env_shell = safe_malloc(len_shell); 225 char *env_mail = safe_malloc(len_mail); 226 char *env_tz; 227 char *env_term; 228 229 (void) snprintf(env_home, len_home, "HOME=%s", pw->pw_dir); 230 (void) snprintf(env_logname, len_logname, "LOGNAME=%s", 231 pw->pw_name); 232 (void) snprintf(env_shell, len_shell, "SHELL=%s", pw->pw_shell); 233 (void) snprintf(env_mail, len_mail, "MAIL=/var/mail/%s", 234 pw->pw_name); 235 236 env[0] = env_home; 237 env[1] = env_logname; 238 env[2] = (pw->pw_uid == 0 ? supath : path); 239 env[3] = env_shell; 240 env[4] = env_mail; 241 env[5] = NULL; 242 env[6] = NULL; 243 env[7] = NULL; 244 245 envnext = (char **)&env[5]; 246 247 /* 248 * It's possible that TERM wasn't defined in the outer 249 * environment. 250 */ 251 if (cur_term != NULL) { 252 len_term = strlen(cur_term) + strlen("TERM=") + 1; 253 env_term = safe_malloc(len_term); 254 255 (void) snprintf(env_term, len_term, "TERM=%s", 256 cur_term); 257 *envnext = env_term; 258 envnext++; 259 } 260 261 /* 262 * It is also possible that TZ wasn't defined in the outer 263 * environment. In that case, we must attempt to open the file 264 * defining the default timezone and select the appropriate 265 * entry. If there is no default timezone there, try 266 * TIMEZONE in /etc/default/login, duplicating the algorithm 267 * that login uses. 268 */ 269 if (cur_tz != NULL) { 270 len_tz = strlen(cur_tz) + strlen("TZ=") + 1; 271 env_tz = safe_malloc(len_tz); 272 273 (void) snprintf(env_tz, len_tz, "TZ=%s", cur_tz); 274 *envnext = env_tz; 275 } else { 276 if ((env_tz = getdefault(TIMEZONEFILE, "TZ=", 277 "TZ=")) != NULL) 278 *envnext = env_tz; 279 else { 280 env_tz = getdefault(LOGINFILE, "TIMEZONE=", 281 "TZ="); 282 *envnext = env_tz; 283 } 284 } 285 286 environ = (char **)&env[0]; 287 288 /* 289 * Prefix the shell string with a hyphen, indicating a login 290 * shell. 291 */ 292 shell = safe_malloc(PATH_MAX); 293 (void) snprintf(shell, PATH_MAX, "-%s", basename(pw->pw_shell)); 294 } else { 295 shell = basename(pw->pw_shell); 296 } 297 298 /* 299 * If there are no arguments, we launch the user's shell; otherwise, the 300 * remaining commands are assumed to form a valid command invocation 301 * that we can exec. 302 */ 303 if (optind >= argc) { 304 targs = alloca(2 * sizeof (char *)); 305 filename = pw->pw_shell; 306 targs[0] = shell; 307 targs[1] = NULL; 308 } else { 309 targs = &argv[optind]; 310 filename = targs[0]; 311 } 312 313 if (execvp(filename, targs) == -1) 314 die(gettext("exec of %s failed"), targs[0]); 315 316 /* 317 * We should never get here. 318 */ 319 return (1); 320 } 321 322 static int 323 update_running_proc(int newproj_flag, char *procname, char *projname) 324 { 325 struct ps_prochandle *p; 326 prcred_t original_prcred, current_prcred; 327 projid_t prprojid; 328 taskid_t taskid; 329 int error = 0, gret; 330 struct project project; 331 char prbuf[PROJECT_BUFSZ]; 332 struct passwd *passwd_entry; 333 int grab_retry_count = 0; 334 335 /* 336 * Catch signals from terminal. There isn't much sense in 337 * doing anything but ignoring them since we don't do anything 338 * after the point we'd be capable of handling them again. 339 */ 340 (void) sigignore(SIGHUP); 341 (void) sigignore(SIGINT); 342 (void) sigignore(SIGQUIT); 343 (void) sigignore(SIGTERM); 344 345 /* flush stdout before grabbing the proc to avoid deadlock */ 346 (void) fflush(stdout); 347 348 /* 349 * We need to grab the process, which will force it to stop execution 350 * until the grab is released, in order to aquire some information about 351 * it, such as its current project (which is achieved via an injected 352 * system call and therefore needs an agent) and its credentials. We 353 * will then need to release it again because it may be a process that 354 * we rely on for later calls, for example nscd. 355 */ 356 if ((p = proc_arg_grab(procname, PR_ARG_PIDS, 0, &gret)) == NULL) { 357 warn(gettext("failed to grab for process %s: %s\n"), 358 procname, Pgrab_error(gret)); 359 return (1); 360 } 361 if (Pcreate_agent(p) != 0) { 362 Prelease(p, 0); 363 warn(gettext("cannot control process %s\n"), procname); 364 return (1); 365 } 366 367 /* 368 * The victim process is now held. Do not call any functions 369 * which generate stdout/stderr until the process has been 370 * released. 371 */ 372 373 /* 374 * The target process will soon be restarted (in case it is in newtask's 375 * execution path) and then stopped again. We need to ensure that our cached 376 * data doesn't change while the process runs so return here if the target 377 * process changes its user id in between our stop operations, so that we can 378 * try again. 379 */ 380 pgrab_retry: 381 382 /* Cache required information about the process. */ 383 if (Pcred(p, &original_prcred, 0) != 0) { 384 preserve_error(gettext("cannot get process credentials %s\n"), 385 procname); 386 error = 1; 387 } 388 if ((prprojid = pr_getprojid(p)) == -1) { 389 preserve_error(gettext("cannot get process project id %s\n"), 390 procname); 391 error = 1; 392 } 393 394 /* 395 * We now have all the required information, so release the target 396 * process and perform our sanity checks. The process needs to be 397 * running at this point because it may be in the execution path of the 398 * calls made below. 399 */ 400 Pdestroy_agent(p); 401 Prelease(p, 0); 402 403 /* if our data acquisition failed, then we can't continue. */ 404 if (error) { 405 warn("%s\n", global_error); 406 return (1); 407 } 408 409 if (newproj_flag == 0) { 410 /* 411 * Just changing the task, so set projname to the current 412 * project of the running process. 413 */ 414 if (getprojbyid(prprojid, &project, &prbuf, 415 PROJECT_BUFSZ) == NULL) { 416 warn(gettext("unable to get project name " 417 "for projid %d"), prprojid); 418 return (1); 419 } 420 projname = project.pj_name; 421 } else { 422 /* 423 * cache info for the project which user passed in via the 424 * command line 425 */ 426 if (getprojbyname(projname, &project, &prbuf, 427 PROJECT_BUFSZ) == NULL) { 428 warn(gettext("unknown project \"%s\"\n"), projname); 429 return (1); 430 } 431 } 432 433 /* 434 * Use our cached information to verify that the owner of the running 435 * process is a member of proj 436 */ 437 if ((passwd_entry = match_user(original_prcred.pr_ruid, 438 projname, 0)) == NULL) { 439 warn("%s\n", global_error); 440 return (1); 441 } 442 443 /* 444 * We can now safely stop the process again in order to change the 445 * project and taskid as required. 446 */ 447 if ((p = proc_arg_grab(procname, PR_ARG_PIDS, 0, &gret)) == NULL) { 448 warn(gettext("failed to grab for process %s: %s\n"), 449 procname, Pgrab_error(gret)); 450 return (1); 451 } 452 if (Pcreate_agent(p) != 0) { 453 Prelease(p, 0); 454 warn(gettext("cannot control process %s\n"), procname); 455 return (1); 456 } 457 458 /* 459 * Now that the target process is stopped, check the validity of our 460 * cached info. If we aren't superuser then match_user() will have 461 * checked to make sure that the owner of the process is in the relevant 462 * project. If our ruid has changed, then match_user()'s conclusion may 463 * be invalid. 464 */ 465 if (getuid() != 0) { 466 if (Pcred(p, ¤t_prcred, 0) != 0) { 467 Pdestroy_agent(p); 468 Prelease(p, 0); 469 warn(gettext("can't get process credentials %s\n"), 470 procname); 471 return (1); 472 } 473 474 if (original_prcred.pr_ruid != current_prcred.pr_ruid) { 475 if (grab_retry_count++ < GRAB_RETRY_MAX) 476 goto pgrab_retry; 477 478 warn(gettext("process consistently changed its " 479 "user id %s\n"), procname); 480 return (1); 481 } 482 } 483 484 error = set_ids(p, &project, passwd_entry); 485 486 if (verbose) 487 taskid = pr_gettaskid(p); 488 489 Pdestroy_agent(p); 490 Prelease(p, 0); 491 492 if (error) { 493 /* 494 * error is serious enough to stop, only if negative. 495 * Otherwise, it simply indicates one of the resource 496 * control assignments failed, which is worth warning 497 * about. 498 */ 499 warn("%s\n", global_error); 500 if (error < 0) 501 return (1); 502 } 503 504 if (verbose) 505 (void) fprintf(stderr, "%d\n", (int)taskid); 506 507 return (0); 508 } 509 510 static int 511 set_ids(struct ps_prochandle *p, struct project *project, 512 struct passwd *passwd_entry) 513 { 514 int be_su = 0; 515 prcred_t old_prcred; 516 int error; 517 prpriv_t *old_prpriv, *new_prpriv; 518 size_t prsz = sizeof (prpriv_t); 519 priv_set_t *eset, *pset; 520 int ind; 521 522 if (Pcred(p, &old_prcred, 0) != 0) { 523 preserve_error(gettext("can't get process credentials")); 524 return (1); 525 } 526 527 old_prpriv = proc_get_priv(Pstatus(p)->pr_pid); 528 if (old_prpriv == NULL) { 529 preserve_error(gettext("can't get process privileges")); 530 return (1); 531 } 532 533 prsz = PRIV_PRPRIV_SIZE(old_prpriv); 534 535 new_prpriv = malloc(prsz); 536 if (new_prpriv == NULL) { 537 preserve_error(gettext("can't allocate memory")); 538 free(old_prpriv); 539 return (1); 540 } 541 542 (void) memcpy(new_prpriv, old_prpriv, prsz); 543 544 /* 545 * If the process already has the proc_taskid privilege, 546 * we don't need to elevate its privileges; if it doesn't, 547 * we try to do it here. 548 * As we do not wish to leave a window in which the process runs 549 * with elevated privileges, we make sure that the process dies 550 * when we go away unexpectedly. 551 */ 552 553 ind = priv_getsetbyname(PRIV_EFFECTIVE); 554 eset = (priv_set_t *)&new_prpriv->pr_sets[new_prpriv->pr_setsize * ind]; 555 ind = priv_getsetbyname(PRIV_PERMITTED); 556 pset = (priv_set_t *)&new_prpriv->pr_sets[new_prpriv->pr_setsize * ind]; 557 558 if (!priv_issubset(nset, eset)) { 559 be_su = 1; 560 priv_union(nset, eset); 561 priv_union(nset, pset); 562 if (Psetflags(p, PR_KLC) != 0) { 563 preserve_error(gettext("cannot set process " 564 "privileges")); 565 (void) Punsetflags(p, PR_KLC); 566 free(new_prpriv); 567 free(old_prpriv); 568 return (1); 569 } 570 (void) __priv_bracket(PRIV_ON); 571 if (Psetpriv(p, new_prpriv) != 0) { 572 (void) __priv_bracket(PRIV_OFF); 573 preserve_error(gettext("cannot set process " 574 "privileges")); 575 (void) Punsetflags(p, PR_KLC); 576 free(new_prpriv); 577 free(old_prpriv); 578 return (1); 579 } 580 (void) __priv_bracket(PRIV_OFF); 581 } 582 583 (void) __priv_bracket(PRIV_ON); 584 if ((error = setproject_proc(project->pj_name, 585 passwd_entry->pw_name, 0, Pstatus(p)->pr_pid, p, project)) != 0) { 586 /* global_error is set by setproject_err */ 587 setproject_err(passwd_entry->pw_name, project->pj_name, 588 error, project); 589 } 590 (void) __priv_bracket(PRIV_OFF); 591 592 /* relinquish added privileges */ 593 if (be_su) { 594 (void) __priv_bracket(PRIV_ON); 595 if (Psetpriv(p, old_prpriv) != 0) { 596 /* 597 * We shouldn't ever be in a state where we can't 598 * set the process back to its old creds, but we 599 * don't want to take the chance of leaving a 600 * non-privileged process with enhanced creds. So, 601 * release the process from libproc control, knowing 602 * that it will be killed. 603 */ 604 (void) __priv_bracket(PRIV_OFF); 605 Pdestroy_agent(p); 606 die(gettext("cannot relinquish superuser credentials " 607 "for pid %d. The process was killed."), 608 Pstatus(p)->pr_pid); 609 } 610 (void) __priv_bracket(PRIV_OFF); 611 if (Punsetflags(p, PR_KLC) != 0) 612 preserve_error(gettext("error relinquishing " 613 "credentials. Process %d will be killed."), 614 Pstatus(p)->pr_pid); 615 } 616 free(new_prpriv); 617 free(old_prpriv); 618 619 return (error); 620 } 621 622 /* 623 * preserve_error() should be called rather than warn() by any 624 * function that is called while the victim process is being 625 * held by Pgrab. 626 * 627 * It saves a single error message to be printed until after 628 * the process has been released. Since multiple errors are not 629 * stored, any error should be considered critical. 630 */ 631 void 632 preserve_error(const char *format, ...) 633 { 634 va_list alist; 635 636 va_start(alist, format); 637 638 /* 639 * GLOBAL_ERR_SZ is pretty big. If the error is longer 640 * than that, just truncate it, rather than chance missing 641 * the error altogether. 642 */ 643 (void) vsnprintf(global_error, GLOBAL_ERR_SZ-1, format, alist); 644 645 va_end(alist); 646 647 } 648 649 /* 650 * Given the input arguments, return the passwd structure that matches best. 651 * Also, since we use getpwnam() and friends, subsequent calls to this 652 * function will re-use the memory previously returned. 653 */ 654 static struct passwd * 655 match_user(uid_t uid, char *projname, int is_my_uid) 656 { 657 char prbuf[PROJECT_BUFSZ], username[LOGNAME_MAX+1]; 658 struct project prj; 659 char *tmp_name; 660 struct passwd *pw = NULL; 661 662 /* 663 * In order to allow users with the same UID but distinguishable 664 * user names to be in different projects we play a guessing 665 * game of which username is most appropriate. If we're checking 666 * for the uid of the calling process, the login name is a 667 * good starting point. 668 */ 669 if (is_my_uid) { 670 if ((tmp_name = getlogin()) == NULL || 671 (pw = getpwnam(tmp_name)) == NULL || (pw->pw_uid != uid) || 672 (pw->pw_name == NULL)) 673 pw = NULL; 674 } 675 676 /* 677 * If the login name doesn't work, we try the first match for 678 * the current uid in the password file. 679 */ 680 if (pw == NULL) { 681 if (((pw = getpwuid(uid)) == NULL) || pw->pw_name == NULL) { 682 preserve_error(gettext("cannot find username " 683 "for uid %d"), uid); 684 return (NULL); 685 } 686 } 687 688 /* 689 * If projname wasn't supplied, we've done our best, so just return 690 * what we've got now. Alternatively, if newtask's invoker has 691 * superuser privileges, return the pw structure we've got now, with 692 * no further checking from inproj(). Superuser should be able to 693 * join any project, and the subsequent call to setproject() will 694 * allow this. 695 */ 696 if (projname == NULL || getuid() == (uid_t)0) 697 return (pw); 698 699 (void) strcpy(username, pw->pw_name); 700 701 if (inproj(username, projname, prbuf, PROJECT_BUFSZ) == 0) { 702 char **u; 703 tmp_name = NULL; 704 705 /* 706 * If the previous guesses didn't work, walk through all 707 * project members and test for UID-equivalence. 708 */ 709 710 if (getprojbyname(projname, &prj, prbuf, 711 PROJECT_BUFSZ) == NULL) { 712 preserve_error(gettext("unknown project \"%s\""), 713 projname); 714 return (NULL); 715 } 716 717 for (u = prj.pj_users; *u; u++) { 718 if ((pw = getpwnam(*u)) == NULL) 719 continue; 720 721 if (pw->pw_uid == uid) { 722 tmp_name = pw->pw_name; 723 break; 724 } 725 } 726 727 if (tmp_name == NULL) { 728 preserve_error(gettext("user \"%s\" is not a member of " 729 "project \"%s\""), username, projname); 730 return (NULL); 731 } 732 } 733 734 return (pw); 735 } 736 737 void 738 setproject_err(char *username, char *projname, int error, struct project *proj) 739 { 740 kva_t *kv_array = NULL; 741 char prbuf[PROJECT_BUFSZ]; 742 struct project local_proj; 743 744 switch (error) { 745 case SETPROJ_ERR_TASK: 746 if (errno == EAGAIN) 747 preserve_error(gettext("resource control limit has " 748 "been reached")); 749 else if (errno == ESRCH) 750 preserve_error(gettext("user \"%s\" is not a member of " 751 "project \"%s\""), username, projname); 752 else if (errno == EACCES) 753 preserve_error(gettext("the invoking task is final")); 754 else 755 preserve_error( 756 gettext("could not join project \"%s\""), 757 projname); 758 break; 759 case SETPROJ_ERR_POOL: 760 if (errno == EACCES) 761 preserve_error(gettext("no resource pool accepting " 762 "default bindings exists for project \"%s\""), 763 projname); 764 else if (errno == ESRCH) 765 preserve_error(gettext("specified resource pool does " 766 "not exist for project \"%s\""), projname); 767 else 768 preserve_error(gettext("could not bind to default " 769 "resource pool for project \"%s\""), projname); 770 break; 771 default: 772 if (error <= 0) { 773 preserve_error(gettext("setproject failed for " 774 "project \"%s\""), projname); 775 return; 776 } 777 /* 778 * If we have a stopped target process it may be in 779 * getprojbyname()'s execution path which would make it unsafe 780 * to access the project table, so only do that if the caller 781 * hasn't provided a cached version of the project structure. 782 */ 783 if (proj == NULL) 784 proj = getprojbyname(projname, &local_proj, prbuf, 785 PROJECT_BUFSZ); 786 787 if (proj == NULL || (kv_array = _str2kva(proj->pj_attr, 788 KV_ASSIGN, KV_DELIMITER)) == NULL || 789 kv_array->length < error) { 790 preserve_error(gettext("warning, resource control " 791 "assignment failed for project \"%s\" " 792 "attribute %d"), 793 projname, error); 794 if (kv_array) 795 _kva_free(kv_array); 796 return; 797 } 798 preserve_error(gettext("warning, %s resource control " 799 "assignment failed for project \"%s\""), 800 kv_array->data[error - 1].key, projname); 801 _kva_free(kv_array); 802 } 803 }