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 */
44static char *
45quote_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 */
83static char *
84val_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 */
106static int
107get_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 */
154static int
155get_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
320out:
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);
331scferr:
332	*retstr = safe_strdup(scf_strerror(scf_error()));
333	ret = -1;
334	goto out;
335properr:
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;
342err:
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 */
356static int
357expand_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
555int
556expand_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