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