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