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