xref: /illumos-gate/usr/src/cmd/svc/startd/expand.c (revision 24da5b34f49324ed742a340010ed5bd3d4e06625)
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 2004 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 <assert.h>
30 #include <libscf.h>
31 #include <libscf_priv.h>
32 #include <libuutil.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <strings.h>
36 #include <errno.h>
37 
38 #include "startd.h"
39 
40 /*
41  * Return an allocated copy of str, with the Bourne shell's metacharacters
42  * escaped by '\'.  Returns NULL on (allocation) failure.
43  */
44 static char *
45 quote_for_shell(const char *str)
46 {
47 	const char *sp;
48 	char *dst, *dp;
49 	size_t dst_len;
50 
51 	const char * const metachars = ";&()|^<>\n \t\\\"\'`";
52 
53 	dst_len = 0;
54 	for (sp = str; *sp != '\0'; ++sp) {
55 		++dst_len;
56 
57 		if (strchr(metachars, *sp) != NULL)
58 			++dst_len;
59 	}
60 
61 	if (sp - str == dst_len)
62 		return (safe_strdup(str));
63 
64 	dst = malloc(dst_len + 1);
65 	if (dst == NULL)
66 		return (NULL);
67 
68 	for (dp = dst, sp = str; *sp != '\0'; ++dp, ++sp) {
69 		if (strchr(metachars, *sp) != NULL)
70 			*dp++ = '\\';
71 
72 		*dp = *sp;
73 	}
74 	*dp = '\0';
75 
76 	return (dst);
77 }
78 
79 /*
80  * Return an allocated string representation of the value v.
81  * Return NULL on error.
82  */
83 static char *
84 val_to_str(scf_value_t *v)
85 {
86 	char *buf;
87 	ssize_t buflen, ret;
88 
89 	buflen = scf_value_get_as_string(v, NULL, 0);
90 	assert(buflen >= 0);
91 
92 	buf = malloc(buflen + 1);
93 	if (buf == NULL)
94 		return (NULL);
95 
96 	ret = scf_value_get_as_string(v, buf, buflen + 1);
97 	assert(ret == buflen);
98 
99 	return (buf);
100 }
101 
102 /*
103  * Look up a property in the given snapshot, or the editing one
104  * if not found. Returns scf_error() on failure, or 0 otherwise.
105  */
106 static int
107 get_prop(const scf_instance_t *inst, scf_snapshot_t *snap,
108     const char *pgn, const char *pn, scf_propertygroup_t *pg,
109     scf_property_t *prop)
110 {
111 	int ret;
112 
113 	ret = scf_instance_get_pg_composed(inst, snap, pgn, pg);
114 	if (ret != 0) {
115 		snap = NULL;
116 		if (scf_error() == SCF_ERROR_NOT_FOUND)
117 			ret = scf_instance_get_pg_composed(inst, snap, pgn, pg);
118 		if (ret != 0)
119 			return (scf_error());
120 	}
121 
122 	if (scf_pg_get_property(pg, pn, prop) == 0)
123 		return (0);
124 
125 	if (snap == NULL)
126 		return (scf_error());
127 
128 	ret = scf_instance_get_pg_composed(inst, NULL, pgn, pg);
129 	if (ret != 0)
130 		return (scf_error());
131 
132 	if (scf_pg_get_property(pg, pn, prop) == 0)
133 		return (0);
134 
135 	return (scf_error());
136 }
137 
138 /*
139  * Get an allocated string representation of the values of the property
140  * specified by inst & prop_spec and store it in *retstr.  prop_spec may
141  * be a full property FMRI, or a "property-group/property" pair relative
142  * to inst, or the name of a property in inst's "application" property
143  * group.  In the latter two cases, the property is looked up in inst's
144  * snap snapshot.  In the first case, the target instance's running
145  * snapshot will be used.  In any case, if the property or its group
146  * can't be found, the "editing" snapshot will be checked.  Multiple
147  * values will be separated by sep.
148  *
149  * On error, non-zero is returned, and *retstr is set to an error
150  * string.
151  *
152  * *retstr should always be freed by the caller.
153  */
154 static int
155 get_prop_val_str(const scf_instance_t *inst, scf_snapshot_t *snap,
156     const char *prop_spec, char sep, char **retstr)
157 {
158 	scf_handle_t *h = scf_instance_handle(inst);
159 	scf_scope_t *scope = NULL;
160 	scf_service_t *svc = NULL;
161 	scf_instance_t *tmpinst = NULL;
162 	scf_snapshot_t *tmpsnap = NULL;
163 	scf_propertygroup_t *pg = NULL;
164 	scf_iter_t *iter = NULL;
165 	scf_property_t *prop = NULL;
166 	scf_value_t *val = NULL;
167 	char *spec;
168 	char *str, *qstr;
169 	size_t strl;
170 	int ret;
171 
172 	spec = safe_strdup(prop_spec);
173 
174 	if (strstr(spec, ":properties") != NULL) {
175 		const char *scn, *sn, *in, *pgn, *pn;
176 
177 		if (scf_parse_svc_fmri(spec, &scn, &sn, &in, &pgn,
178 		    &pn) != 0)
179 			goto scferr;
180 
181 		if (sn == NULL || pgn == NULL || pn == NULL) {
182 			free(spec);
183 			*retstr = safe_strdup("parse error");
184 			return (-1);
185 		}
186 
187 		if ((scope = scf_scope_create(h)) == NULL ||
188 		    (svc = scf_service_create(h)) == NULL ||
189 		    (pg = scf_pg_create(h)) == NULL ||
190 		    (prop = scf_property_create(h)) == NULL)
191 			goto scferr;
192 
193 		if (scf_handle_get_scope(h, scn == NULL ? SCF_SCOPE_LOCAL : scn,
194 		    scope) != 0)
195 			goto properr;
196 
197 		if (scf_scope_get_service(scope, sn, svc) != 0)
198 			goto properr;
199 
200 		if (in == NULL) {
201 			if (scf_service_get_pg(svc, pgn, pg) != 0)
202 				goto properr;
203 			if (scf_pg_get_property(pg, pn, prop) != 0)
204 				goto properr;
205 		} else {
206 			if ((tmpinst = scf_instance_create(h)) == NULL)
207 				goto scferr;
208 			if (scf_service_get_instance(svc, in, tmpinst) != 0)
209 				goto properr;
210 
211 			tmpsnap = libscf_get_running_snapshot(tmpinst);
212 			if (tmpsnap == NULL)
213 				goto scferr;
214 
215 			if (get_prop(tmpinst, tmpsnap, pgn, pn, pg, prop) != 0)
216 				goto properr;
217 		}
218 	} else {
219 		char *slash, *pgn, *pn;
220 
221 		/* Try prop or pg/prop in inst. */
222 
223 		prop = scf_property_create(h);
224 		if (prop == NULL)
225 			goto scferr;
226 
227 		pg = scf_pg_create(h);
228 		if (pg == NULL)
229 			goto scferr;
230 
231 		slash = strchr(spec, '/');
232 		if (slash == NULL) {
233 			pgn = "application";
234 			pn = spec;
235 		} else {
236 			*slash = '\0';
237 			pgn = spec;
238 			pn = slash + 1;
239 		}
240 
241 		if (get_prop(inst, snap, pgn, pn, pg, prop) != 0)
242 			goto properr;
243 	}
244 
245 	iter = scf_iter_create(h);
246 	if (iter == NULL)
247 		goto scferr;
248 
249 
250 	if (scf_iter_property_values(iter, prop) == -1)
251 		goto scferr;
252 
253 	val = scf_value_create(h);
254 	if (val == NULL)
255 		goto scferr;
256 
257 	ret = scf_iter_next_value(iter, val);
258 	if (ret == 0) {
259 		*retstr = safe_strdup("");
260 		goto out;
261 	} else if (ret == -1) {
262 		goto scferr;
263 	}
264 
265 	str = val_to_str(val);
266 	if (str == NULL)
267 		goto err;
268 
269 	qstr = quote_for_shell(str);
270 	free(str);
271 	str = qstr;
272 	if (qstr == NULL)
273 		goto err;
274 
275 	strl = strlen(str);
276 
277 	while ((ret = scf_iter_next_value(iter, val)) == 1) {
278 		char *nv, *qnv;
279 		size_t nl;
280 		void *p;
281 
282 		/* Append sep & val_to_str(val) to str. */
283 
284 		nv = val_to_str(val);
285 		if (nv == NULL) {
286 			free(str);
287 			goto err;
288 		}
289 		qnv = quote_for_shell(nv);
290 		free(nv);
291 		if (qnv == NULL) {
292 			free(str);
293 			goto err;
294 		}
295 		nv = qnv;
296 
297 		nl = strl + 1 + strlen(nv);
298 		p = realloc(str, nl + 1);
299 		if (p == NULL) {
300 			free(str);
301 			free(nv);
302 			goto err;
303 		}
304 		str = p;
305 
306 		str[strl] = sep;
307 		(void) strcpy(&str[strl + 1], nv);
308 
309 		free(nv);
310 
311 		strl = nl;
312 	}
313 	if (ret == -1) {
314 		free(str);
315 		goto scferr;
316 	}
317 
318 	*retstr = str;
319 
320 out:
321 	scf_value_destroy(val);
322 	scf_iter_destroy(iter);
323 	scf_property_destroy(prop);
324 	scf_pg_destroy(pg);
325 	scf_instance_destroy(tmpinst);
326 	scf_snapshot_destroy(tmpsnap);
327 	scf_service_destroy(svc);
328 	scf_scope_destroy(scope);
329 	free(spec);
330 	return (ret);
331 scferr:
332 	*retstr = safe_strdup(scf_strerror(scf_error()));
333 	ret = -1;
334 	goto out;
335 properr:
336 	ret = -1;
337 	if (scf_error() != SCF_ERROR_NOT_FOUND)
338 		goto scferr;
339 	*retstr = uu_msprintf("property \"%s\" not found", prop_spec);
340 	if (*retstr != NULL)
341 		goto out;
342 err:
343 	*retstr = safe_strdup(strerror(errno));
344 	ret = -1;
345 	goto out;
346 }
347 
348 /*
349  * Interpret the token at the beginning of str (which should be just
350  * after the escape character), and set *retstr to point at it.  Returns
351  * the number of characters swallowed.  On error, this returns -1, and
352  * *retstr is set to an error string.
353  *
354  * *retstr should always be freed by the caller.
355  */
356 static int
357 expand_token(const char *str, scf_instance_t *inst, scf_snapshot_t *snap,
358     int method_type, char **retstr)
359 {
360 	scf_handle_t *h = scf_instance_handle(inst);
361 
362 	switch (str[0]) {
363 	case 's': {		/* service */
364 		scf_service_t *svc;
365 		char *sname;
366 		ssize_t sname_len, szret;
367 		int ret;
368 
369 		svc = scf_service_create(h);
370 		if (svc == NULL) {
371 			*retstr = safe_strdup(strerror(scf_error()));
372 			return (-1);
373 		}
374 
375 		ret = scf_instance_get_parent(inst, svc);
376 		if (ret != 0) {
377 			int err = scf_error();
378 			scf_service_destroy(svc);
379 			*retstr = safe_strdup(scf_strerror(err));
380 			return (-1);
381 		}
382 
383 		sname_len = scf_service_get_name(svc, NULL, 0);
384 		if (sname_len < 0) {
385 			int err = scf_error();
386 			scf_service_destroy(svc);
387 			*retstr = safe_strdup(scf_strerror(err));
388 			return (-1);
389 		}
390 
391 		sname = malloc(sname_len + 1);
392 		if (sname == NULL) {
393 			int err = scf_error();
394 			scf_service_destroy(svc);
395 			*retstr = safe_strdup(scf_strerror(err));
396 			return (-1);
397 		}
398 
399 		szret = scf_service_get_name(svc, sname, sname_len + 1);
400 
401 		if (szret < 0) {
402 			int err = scf_error();
403 			free(sname);
404 			scf_service_destroy(svc);
405 			*retstr = safe_strdup(scf_strerror(err));
406 			return (-1);
407 		}
408 
409 		scf_service_destroy(svc);
410 		*retstr = sname;
411 		return (1);
412 	}
413 
414 	case 'i': {	/* instance */
415 		char *iname;
416 		ssize_t iname_len, szret;
417 
418 		iname_len = scf_instance_get_name(inst, NULL, 0);
419 		if (iname_len < 0) {
420 			*retstr = safe_strdup(scf_strerror(scf_error()));
421 			return (-1);
422 		}
423 
424 		iname = malloc(iname_len + 1);
425 		if (iname == NULL) {
426 			*retstr = safe_strdup(strerror(errno));
427 			return (-1);
428 		}
429 
430 		szret = scf_instance_get_name(inst, iname, iname_len + 1);
431 		if (szret < 0) {
432 			free(iname);
433 			*retstr = safe_strdup(scf_strerror(scf_error()));
434 			return (-1);
435 		}
436 
437 		*retstr = iname;
438 		return (1);
439 	}
440 
441 	case 'f': {	/* fmri */
442 		char *fmri;
443 		ssize_t fmri_len;
444 		int ret;
445 
446 		fmri_len = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH);
447 		if (fmri_len == -1) {
448 			*retstr = safe_strdup(scf_strerror(scf_error()));
449 			return (-1);
450 		}
451 
452 		fmri = malloc(fmri_len + 1);
453 		if (fmri == NULL) {
454 			*retstr = safe_strdup(strerror(errno));
455 			return (-1);
456 		}
457 
458 		ret = scf_instance_to_fmri(inst, fmri, fmri_len + 1);
459 		if (ret == -1) {
460 			free(fmri);
461 			*retstr = safe_strdup(scf_strerror(scf_error()));
462 			return (-1);
463 		}
464 
465 		*retstr = fmri;
466 		return (1);
467 	}
468 
469 	case 'm': {	/* method */
470 		char *str = NULL;
471 		switch (method_type) {
472 		case METHOD_START:
473 			str = "start";
474 			break;
475 		case METHOD_STOP:
476 			str = "stop";
477 			break;
478 		case METHOD_REFRESH:
479 			str = "refresh";
480 			break;
481 		default:
482 			assert(0);
483 			return (-1);
484 		}
485 		*retstr = safe_strdup(str);
486 		return (1);
487 	}
488 
489 	case 'r':	/* restarter */
490 		*retstr = safe_strdup("svc.startd");
491 		return (1);
492 
493 	case '{': {
494 		/* prop_spec[,:]?  See get_prop_val_str() for prop_spec. */
495 
496 		char *close;
497 		size_t len;
498 		char *buf;
499 		char sep;
500 		int ret;
501 		int skip;
502 
503 		close = strchr(str + 1, '}');
504 		if (close == NULL) {
505 			*retstr = safe_strdup("parse error");
506 			return (-1);
507 		}
508 
509 		len = close - (str + 1);	/* between the {}'s */
510 		skip = len + 2;			/* including the {}'s */
511 
512 		/*
513 		 * If the last character is , or :, use it as the separator.
514 		 * Otherwise default to space.
515 		 */
516 		sep = *(close - 1);
517 		if (sep == ',' || sep == ':')
518 			--len;
519 		else
520 			sep = ' ';
521 
522 		buf = malloc(len + 1);
523 		if (buf == NULL) {
524 			*retstr = safe_strdup(strerror(errno));
525 			return (-1);
526 		}
527 
528 		(void) strlcpy(buf, str + 1, len + 1);
529 
530 		ret = get_prop_val_str(inst, snap, buf, sep, retstr);
531 
532 		if (ret != 0) {
533 			free(buf);
534 			return (-1);
535 		}
536 
537 		free(buf);
538 		return (skip);
539 	}
540 
541 	default:
542 		*retstr = safe_strdup("unknown method token");
543 		return (-1);
544 	}
545 }
546 
547 /*
548  * Expand method tokens in the given string, and place the result in
549  * *retstr.  Tokens begin with the ESCAPE character.  Returns 0 on
550  * success.  On failure, returns -1 and an error string is placed in
551  * *retstr.  Caller should free *retstr.
552  */
553 #define	ESCAPE	'%'
554 
555 int
556 expand_method_tokens(const char *str, scf_instance_t *inst,
557     scf_snapshot_t *snap, int method_type, char **retstr)
558 {
559 	char *expanded;
560 	size_t exp_sz;
561 	const char *sp;
562 	int ei;
563 
564 	if (scf_instance_handle(inst) == NULL) {
565 		*retstr = safe_strdup(scf_strerror(scf_error()));
566 		return (-1);
567 	}
568 
569 	exp_sz = strlen(str) + 1;
570 	expanded = malloc(exp_sz);
571 	if (expanded == NULL) {
572 		*retstr = safe_strdup(strerror(errno));
573 		return (-1);
574 	}
575 
576 	/*
577 	 * Copy str into expanded, expanding %-tokens & realloc()ing as we go.
578 	 */
579 
580 	sp = str;
581 	ei = 0;
582 
583 	for (;;) {
584 		char *esc;
585 		size_t len;
586 
587 		esc = strchr(sp, ESCAPE);
588 		if (esc == NULL) {
589 			(void) strcpy(expanded + ei, sp);
590 			*retstr = expanded;
591 			return (0);
592 		}
593 
594 		/* Copy up to the escape character. */
595 		len = esc - sp;
596 
597 		(void) strncpy(expanded + ei, sp, len);
598 
599 		sp += len;
600 		ei += len;
601 
602 		if (sp[1] == '\0') {
603 			expanded[ei] = '\0';
604 			*retstr = expanded;
605 			return (0);
606 		}
607 
608 		if (sp[1] == ESCAPE) {
609 			expanded[ei] = ESCAPE;
610 
611 			sp += 2;
612 			ei++;
613 		} else {
614 			char *tokval;
615 			int skip;
616 			char *p;
617 
618 			skip = expand_token(sp + 1, inst, snap,
619 			    method_type, &tokval);
620 			if (skip == -1) {
621 				free(expanded);
622 				*retstr = tokval;
623 				return (-1);
624 			}
625 
626 			len = strlen(tokval);
627 			exp_sz += len;
628 			p = realloc(expanded, exp_sz);
629 			if (p == NULL) {
630 				*retstr = safe_strdup(strerror(errno));
631 				free(expanded);
632 				free(tokval);
633 				return (-1);
634 			}
635 			expanded = p;
636 
637 			(void) strcpy(expanded + ei, tokval);
638 			sp += 1 + skip;
639 			ei += len;
640 
641 			free(tokval);
642 		}
643 	}
644 
645 	/* NOTREACHED */
646 }
647