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
63static const char *pname;
64extern char **environ;
65static char *supath = SUPATH;
66static char *path = PATH;
67static char global_error[GLOBAL_ERR_SZ];
68static int verbose = 0;
69
70static priv_set_t *nset;
71
72/* Private definitions for libproject */
73extern projid_t setproject_proc(const char *, const char *, int, pid_t,
74    struct ps_prochandle *, struct project *);
75extern priv_set_t *setproject_initpriv(void);
76
77static void usage(void);
78
79static void preserve_error(const char *format, ...);
80
81static int update_running_proc(int, char *, char *);
82static int set_ids(struct ps_prochandle *, struct project *,
83    struct passwd *);
84static struct passwd *match_user(uid_t, char *, int);
85static void setproject_err(char *, char *, int, struct project *);
86
87static void
88usage(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
95int
96main(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
322static int
323update_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 */
380pgrab_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, &current_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
510static int
511set_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 */
631void
632preserve_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 */
654static struct passwd *
655match_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
737void
738setproject_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}
804