1/*-
2 * Copyright (c) 2015 Antti Kantee.  All Rights Reserved.
3 * Copyright (c) 2014 Martin Lucina.  All Rights Reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
15 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27/*
28 * NOTE: this implementation is currently a sketch of what things
29 * should looks like.
30 */
31
32#include <sys/param.h>
33#include <sys/disklabel.h>
34#include <sys/ioctl.h>
35#include <sys/stat.h>
36
37#include <ufs/ufs/ufsmount.h>
38#include <isofs/cd9660/cd9660_mount.h>
39
40#include <dev/vndvar.h>
41
42#include <assert.h>
43#include <err.h>
44#include <errno.h>
45#include <fcntl.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50
51#include <rump/rump.h>
52#include <rump/netconfig.h>
53
54#include <rumprun-base/config.h>
55#include <rumprun-base/parseargs.h>
56
57#include <bmk-core/jsmn.h>
58
59/* helper macros */
60#define T_SIZE(t) ((t)->end - (t)->start)
61#define T_STR(t,d) ((t)->start + d)
62#define T_PRINTFSTAR(t,d) T_SIZE(t), T_STR(t,d)
63#define T_STREQ(t, d, str) (strncmp(T_STR(t,d), str, T_SIZE(t)) == 0)
64
65#define T_STRCPY(dest, destsize, t, d)					\
66  do {									\
67	unsigned long strsize = MIN(destsize-1,T_SIZE(t));		\
68	strncpy(dest, T_STR(t, d), strsize);				\
69	dest[strsize] = '\0';						\
70  } while (/*CONSTCOND*/0)
71
72#define T_CHECKTYPE(t, data, exp, fun)					\
73  do {									\
74	if (t->type != exp) {						\
75		errx(1, "unexpected type for token \"%.*s\" "		\
76		    "in \"%s\"", T_PRINTFSTAR(t,data), fun);		\
77	}								\
78  } while (/*CONSTCOND*/0)
79
80#define T_CHECKSIZE(t, data, exp, fun)					\
81  do {									\
82	if (t->size != exp) {						\
83		errx(1, "unexpected size for token \"%.*s\" "		\
84		    "in \"%s\"", T_PRINTFSTAR(t,data), fun);		\
85	}								\
86  } while (/*CONSTCOND*/0)
87
88static char *
89token2cstr(jsmntok_t *t, char *data)
90{
91
92	*(T_STR(t, data) + T_SIZE(t)) = '\0';
93	return T_STR(t, data);
94}
95
96struct rumprun_execs rumprun_execs = TAILQ_HEAD_INITIALIZER(rumprun_execs);
97
98static void
99makeargv(char *argvstr)
100{
101	struct rumprun_exec *rre;
102	char **argv;
103	int nargs;
104
105	rumprun_parseargs(argvstr, &nargs, 0);
106	rre = malloc(sizeof(*rre) + (nargs+1) * sizeof(*argv));
107	if (rre == NULL)
108		err(1, "could not allocate rre");
109
110	rumprun_parseargs(argvstr, &nargs, rre->rre_argv);
111	rre->rre_argv[nargs] = NULL;
112	rre->rre_flags = RUMPRUN_EXEC_CMDLINE;
113	rre->rre_argc = nargs;
114
115	TAILQ_INSERT_TAIL(&rumprun_execs, rre, rre_entries);
116}
117
118static int
119handle_cmdline(jsmntok_t *t, int left, char *data)
120{
121
122	T_CHECKTYPE(t, data, JSMN_STRING, __func__);
123
124	makeargv(token2cstr(t, data));
125
126	return 1;
127}
128
129/*
130 * "rc": [
131 *	{ "bin" : "binname",
132 *	  "argv" : [ "arg1", "arg2", ... ], (optional)
133 *	  "runmode" : "& OR |" (optional)
134 *	},
135 *      ....
136 * ]
137 */
138static int
139addbin(jsmntok_t *t, char *data)
140{
141	jsmntok_t *t_bin, *t_argv, *t_runmode;
142	struct rumprun_exec *rre;
143	jsmntok_t *key, *value;
144	char *binname;
145	int binsize = 1;
146	int objleft = t->size;
147	int rreflags, i;
148
149	T_CHECKTYPE(t, data, JSMN_OBJECT, __func__);
150	t++;
151
152	/* process and validate data */
153	t_bin = t_argv = t_runmode = NULL;
154	while (objleft--) {
155		int mysize;
156
157		key = t;
158		value = t+1;
159
160		T_CHECKTYPE(key, data, JSMN_STRING, __func__);
161
162		if (T_STREQ(key, data, "bin")) {
163			t_bin = value;
164
165			T_CHECKSIZE(key, data, 1, __func__);
166			T_CHECKTYPE(value, data, JSMN_STRING, __func__);
167			T_CHECKSIZE(value, data, 0, __func__);
168
169			mysize = 1 + 1;
170		} else if (T_STREQ(key, data, "argv")) {
171			T_CHECKTYPE(value, data, JSMN_ARRAY, __func__);
172
173			t_argv = value;
174
175			/* key + array + array contents */
176			mysize = 1 + 1 + value->size;
177		} else if (T_STREQ(key, data, "runmode")) {
178			t_runmode = value;
179
180			T_CHECKSIZE(key, data, 1, __func__);
181			T_CHECKTYPE(value, data, JSMN_STRING, __func__);
182			T_CHECKSIZE(value, data, 0, __func__);
183
184			mysize = 1 + 1;
185		} else {
186			errx(1, "unexpected key \"%.*s\" in \"%s\"",
187			    T_PRINTFSTAR(key, data), __func__);
188		}
189
190		t += mysize;
191		binsize += mysize;
192	}
193
194	if (!t_bin)
195		errx(1, "missing \"bin\" for rc entry");
196	binname = token2cstr(t_bin, data);
197
198	if (t_runmode) {
199		bool sizeok = T_SIZE(t_runmode) == 1;
200
201		if (sizeok && *T_STR(t_runmode,data) == '&') {
202			rreflags = RUMPRUN_EXEC_BACKGROUND;
203		} else if (sizeok && *T_STR(t_runmode,data) == '|') {
204			rreflags = RUMPRUN_EXEC_PIPE;
205		} else {
206			errx(1, "invalid runmode \"%.*s\" for bin \"%.*s\"",
207			    T_PRINTFSTAR(t_runmode, data),
208			    T_PRINTFSTAR(t_bin, data));
209		}
210	} else {
211		rreflags = 0;
212	}
213
214	/* ok, we got everything.  save into rumprun_exec structure */
215	rre = malloc(sizeof(*rre) + (2+t_argv->size) * sizeof(char *));
216	if (rre == NULL)
217		err(1, "allocate rumprun_exec");
218	rre->rre_flags = rreflags;
219	rre->rre_argc = 1+t_argv->size;
220	rre->rre_argv[0] = binname;
221	for (i = 1, t = t_argv+1; i <= t_argv->size; i++, t++) {
222		T_CHECKTYPE(t, data, JSMN_STRING, __func__);
223		rre->rre_argv[i] = token2cstr(t, data);
224	}
225	rre->rre_argv[rre->rre_argc] = NULL;
226
227	TAILQ_INSERT_TAIL(&rumprun_execs, rre, rre_entries);
228
229	return binsize;
230}
231
232static int
233handle_rc(jsmntok_t *t, int left, char *data)
234{
235	int onesize, totsize, rem;
236
237	T_CHECKTYPE(t, data, JSMN_ARRAY, __func__);
238
239	rem = t->size;
240	for (t++, totsize = 1; rem; rem--) {
241		onesize = addbin(t, data);
242		totsize += onesize;
243		t += onesize;
244	}
245
246	return totsize;
247}
248
249static int
250handle_env(jsmntok_t *t, int left, char *data)
251{
252
253	T_CHECKTYPE(t, data, JSMN_STRING, __func__);
254
255	if (putenv(token2cstr(t, data)) == -1)
256		err(1, "putenv");
257
258	return 1;
259}
260
261static int
262handle_hostname(jsmntok_t *t, int left, char *data)
263{
264
265	T_CHECKTYPE(t, data, JSMN_STRING, __func__);
266
267	if (sethostname(token2cstr(t, data), T_SIZE(t)) == -1)
268		err(1, "sethostname");
269
270	return 1;
271}
272
273static void
274config_ipv4(const char *ifname, const char *method,
275	const char *addr, const char *mask, const char *gw)
276{
277	int rv;
278
279	if (strcmp(method, "dhcp") == 0) {
280		if ((rv = rump_pub_netconfig_dhcp_ipv4_oneshot(ifname)) != 0)
281			errx(1, "configuring dhcp for %s failed: %d",
282			    ifname, rv);
283	} else {
284		if (strcmp(method, "static") != 0) {
285			errx(1, "method \"static\" or \"dhcp\" expected, "
286			    "got \"%s\"", method);
287		}
288
289		if (!addr || !mask) {
290			errx(1, "static net cfg missing addr or mask");
291		}
292
293		if ((rv = rump_pub_netconfig_ipv4_ifaddr_cidr(ifname,
294		    addr, atoi(mask))) != 0) {
295			errx(1, "ifconfig \"%s\" for \"%s/%s\" failed",
296			    ifname, addr, mask);
297		}
298		if (gw && (rv = rump_pub_netconfig_ipv4_gw(gw)) != 0) {
299			errx(1, "gw \"%s\" addition failed", gw);
300		}
301	}
302}
303
304static void
305config_ipv6(const char *ifname, const char *method,
306	const char *addr, const char *mask, const char *gw)
307{
308	int rv;
309
310	if (strcmp(method, "auto") == 0) {
311		if ((rv = rump_pub_netconfig_auto_ipv6(ifname)) != 0) {
312			errx(1, "ipv6 autoconfig failed");
313		}
314	} else {
315		if (strcmp(method, "static") != 0) {
316			errx(1, "method \"static\" or \"dhcp\" expected, "
317			    "got \"%s\"", method);
318		}
319
320		if (!addr || !mask) {
321			errx(1, "static net cfg missing addr or mask");
322		}
323
324		if ((rv = rump_pub_netconfig_ipv6_ifaddr(ifname,
325		    addr, atoi(mask))) != 0) {
326			errx(1, "ifconfig \"%s\" for \"%s/%s\" failed",
327			    ifname, addr, mask);
328		}
329		if (gw && (rv = rump_pub_netconfig_ipv6_gw(gw)) != 0) {
330			errx(1, "gw \"%s\" addition failed", gw);
331		}
332	}
333}
334
335static int
336handle_net(jsmntok_t *t, int left, char *data)
337{
338	const char *ifname, *cloner, *type, *method;
339	const char *addr, *mask, *gw;
340	jsmntok_t *key, *value;
341	int i, objsize;
342	int rv;
343	static int configured;
344
345	T_CHECKTYPE(t, data, JSMN_OBJECT, __func__);
346
347	/* we expect straight key-value pairs (at least for now) */
348	objsize = t->size;
349	if (left < 2*objsize + 1) {
350		return -1;
351	}
352	t++;
353
354	if (configured) {
355		errx(1, "currently only 1 \"net\" configuration is supported");
356	}
357
358	ifname = cloner = type = method = NULL;
359	addr = mask = gw = NULL;
360
361	for (i = 0; i < objsize; i++, t+=2) {
362		const char *valuestr;
363		key = t;
364		value = t+1;
365
366		T_CHECKTYPE(key, data, JSMN_STRING, __func__);
367		T_CHECKSIZE(key, data, 1, __func__);
368
369		T_CHECKTYPE(value, data, JSMN_STRING, __func__);
370		T_CHECKSIZE(value, data, 0, __func__);
371
372		/*
373		 * XXX: this mimics the structure from Xen.  We probably
374		 * want a richer structure, but let's be happy to not
375		 * diverge for now.
376		 */
377		valuestr = token2cstr(value, data);
378		if (T_STREQ(key, data, "if")) {
379			ifname = valuestr;
380		} else if (T_STREQ(key, data, "cloner")) {
381			cloner = valuestr;
382		} else if (T_STREQ(key, data, "type")) {
383			type = valuestr;
384		} else if (T_STREQ(key, data, "method")) {
385			method = valuestr;
386		} else if (T_STREQ(key, data, "addr")) {
387			addr = valuestr;
388		} else if (T_STREQ(key, data, "mask")) {
389			/* XXX: we could also pass mask as a number ... */
390			mask = valuestr;
391		} else if (T_STREQ(key, data, "gw")) {
392			gw = valuestr;
393		} else {
394			errx(1, "unexpected key \"%.*s\" in \"%s\"",
395			    T_PRINTFSTAR(key, data), __func__);
396		}
397	}
398
399	if (!ifname || !type || !method) {
400		errx(1, "net cfg missing vital data, not configuring");
401	}
402
403	if (cloner) {
404		if ((rv = rump_pub_netconfig_ifcreate(ifname)) != 0) {
405			errx(1, "rumprun_config: ifcreate %s failed: %d",
406			    ifname, rv);
407		}
408	}
409
410	if (strcmp(type, "inet") == 0) {
411		config_ipv4(ifname, method, addr, mask, gw);
412	} else if (strcmp(type, "inet6") == 0) {
413		config_ipv6(ifname, method, addr, mask, gw);
414	} else {
415		errx(1, "network type \"%s\" not supported", type);
416	}
417
418	return 2*objsize + 1;
419}
420
421static void
422makevnddev(int israw, int unit, int part, char *storage, size_t storagesize)
423{
424
425	snprintf(storage, storagesize, "/dev/%svnd%d%c",
426	    israw ? "r" : "", unit, 'a' + part);
427}
428
429static devmajor_t
430getvndmajor(int israw)
431{
432	struct stat sb;
433	char path[32];
434
435	makevnddev(israw, 0, RAW_PART, path, sizeof(path));
436	if (stat(path, &sb) == -1)
437		err(1, "failed to stat %s", path);
438	return major(sb.st_rdev);
439}
440
441static char *
442configvnd(const char *path)
443{
444	static int nextvnd;
445	struct vnd_ioctl vndio;
446	char bbuf[32], rbuf[32];
447	int fd;
448
449	makevnddev(0, nextvnd, RAW_PART, bbuf, sizeof(bbuf));
450	makevnddev(1, nextvnd, RAW_PART, rbuf, sizeof(rbuf));
451
452	memset(&vndio, 0, sizeof(vndio));
453	vndio.vnd_file = __UNCONST(path);
454	vndio.vnd_flags = VNDIOF_READONLY;
455
456	fd = open(rbuf, O_RDWR);
457	if (fd == -1) {
458		/*
459		 * node doesn't exist?  try creating it.  use majors from
460		 * vnd0, which we (obviously) assume/hope exists
461		 */
462		if (errno == ENOENT) {
463			const devmajor_t bmaj = getvndmajor(0);
464			const devmajor_t rmaj = getvndmajor(1);
465
466			if (mknod(bbuf, 0666 | S_IFBLK,
467			    MAKEDISKDEV(bmaj, nextvnd, RAW_PART)) == -1)
468				err(1, "mknod %s", bbuf);
469			if (mknod(rbuf, 0666 | S_IFBLK,
470			    MAKEDISKDEV(rmaj, nextvnd, RAW_PART)) == -1)
471				err(1, "mknod %s", rbuf);
472
473			fd = open(rbuf, O_RDWR);
474		}
475		if (fd == -1)
476			err(1, "cannot open %s", rbuf);
477	}
478
479	if (ioctl(fd, VNDIOCSET, &vndio) == -1)
480		err(1, "vndset failed");
481	close(fd);
482
483	nextvnd++;
484	return strdup(bbuf);
485}
486
487static char *
488configetfs(const char *path, int hard)
489{
490	char buf[32];
491	char epath[32];
492	char *p;
493	int rv;
494
495	snprintf(epath, sizeof(epath), "XENBLK_%s", path);
496	snprintf(buf, sizeof(buf), "/dev/%s", path);
497	rv = rump_pub_etfs_register(buf, epath, RUMP_ETFS_BLK);
498	if (rv != 0) {
499		if (!hard)
500			return NULL;
501		errx(1, "etfs register for \"%s\" failed: %d", path, rv);
502	}
503
504	if ((p = strdup(buf)) == NULL)
505		err(1, "failed to allocate pathbuf");
506	return p;
507}
508
509static bool
510mount_blk(const char *dev, const char *mp)
511{
512	struct ufs_args mntargs_ufs = { .fspec = __UNCONST(dev) };
513	struct iso_args mntargs_iso = { .fspec = dev };
514
515	if (mount(MOUNT_FFS, mp, 0, &mntargs_ufs, sizeof(mntargs_ufs)) == 0)
516		return true;
517	if (mount(MOUNT_EXT2FS, mp, 0, &mntargs_ufs, sizeof(mntargs_ufs)) == 0)
518		return true;
519	if (mount(MOUNT_CD9660,
520	    mp, MNT_RDONLY, &mntargs_iso, sizeof(mntargs_iso)) == 0)
521		return true;
522
523	return false;
524}
525
526static bool
527mount_kernfs(const char *dev, const char *mp)
528{
529
530	if (mount(MOUNT_KERNFS, mp, 0, NULL, 0) == 0)
531		return true;
532
533	return false;
534}
535
536struct {
537	const char *mt_fstype;
538	bool (*mt_mount)(const char *, const char *);
539} mounters[] = {
540	{ "blk",	mount_blk },
541	{ "kernfs",	mount_kernfs },
542};
543
544static int
545handle_blk(jsmntok_t *t, int left, char *data)
546{
547	const char *source, *origpath, *fstype;
548	char *mp, *path;
549	jsmntok_t *key, *value;
550	int i, objsize;
551
552	T_CHECKTYPE(t, data, JSMN_OBJECT, __func__);
553
554	/* we expect straight key-value pairs */
555	objsize = t->size;
556	if (left < 2*objsize + 1) {
557		return -1;
558	}
559	t++;
560
561	fstype = source = origpath = mp = path = NULL;
562
563	for (i = 0; i < objsize; i++, t+=2) {
564		char *valuestr;
565		key = t;
566		value = t+1;
567
568		T_CHECKTYPE(key, data, JSMN_STRING, __func__);
569		T_CHECKSIZE(key, data, 1, __func__);
570
571		T_CHECKTYPE(value, data, JSMN_STRING, __func__);
572		T_CHECKSIZE(value, data, 0, __func__);
573
574		valuestr = token2cstr(value, data);
575		if (T_STREQ(key, data, "source")) {
576			source = valuestr;
577		} else if (T_STREQ(key, data, "path")) {
578			origpath = path = valuestr;
579		} else if (T_STREQ(key, data, "fstype")) {
580			fstype = valuestr;
581		} else if (T_STREQ(key, data, "mountpoint")) {
582			mp = valuestr;
583		} else {
584			errx(1, "unexpected key \"%.*s\" in \"%s\"",
585			    T_PRINTFSTAR(key, data), __func__);
586		}
587	}
588
589	if (!source || !path) {
590		errx(1, "blk cfg missing vital data");
591	}
592
593	if (strcmp(source, "dev") == 0) {
594		/* nothing to do here */
595	} else if (strcmp(source, "vnd") == 0) {
596		path = configvnd(path);
597	} else if (strcmp(source, "etfs") == 0) {
598		path = configetfs(path, 1);
599	} else {
600		errx(1, "unsupported blk source \"%s\"", source);
601	}
602
603	/* we only need to do something only if a mountpoint is specified */
604	if (mp) {
605		char *chunk;
606		unsigned mi;
607
608		if (!fstype) {
609			errx(1, "no fstype for mountpoint \"%s\"\n", mp);
610		}
611
612		for (chunk = mp;;) {
613			bool end;
614
615			/* find & terminate the next chunk */
616			chunk += strspn(chunk, "/");
617			chunk += strcspn(chunk, "/");
618			end = (*chunk == '\0');
619			*chunk = '\0';
620
621			if (mkdir(mp, 0755) == -1) {
622				if (errno != EEXIST)
623					err(1, "failed to create mp dir \"%s\"",
624					    chunk);
625			}
626
627			/* restore path */
628			if (!end)
629				*chunk = '/';
630			else
631				break;
632		}
633
634		for (mi = 0; mi < __arraycount(mounters); mi++) {
635			if (strcmp(fstype, mounters[mi].mt_fstype) == 0) {
636				if (!mounters[mi].mt_mount(path, mp))
637					errx(1, "failed to mount fs type "
638					    "\"%s\" from \"%s\" to \"%s\"",
639					    fstype, path, mp);
640				break;
641			}
642		}
643		if (mi == __arraycount(mounters))
644			errx(1, "unknown fstype \"%s\"", fstype);
645	}
646
647	if (path != origpath)
648		free(path);
649
650	return 2*objsize + 1;
651}
652
653struct {
654	const char *name;
655	int (*handler)(jsmntok_t *, int, char *);
656} parsers[] = {
657	{ "cmdline", handle_cmdline },
658	{ "rc_TESTING", handle_rc },
659	{ "env", handle_env },
660	{ "hostname", handle_hostname },
661	{ "blk", handle_blk },
662	{ "net", handle_net },
663};
664
665/* don't believe we can have a >64k config */
666#define CFGMAXSIZE (64*1024)
667static char *
668getcmdlinefromroot(const char *cfgname)
669{
670	const char *tryroot[] = {
671		"/dev/ld0a",
672		"/dev/sd0a",
673	};
674	struct stat sb;
675	unsigned int i;
676	int fd;
677	char *p;
678
679	if (mkdir("/rootfs", 0777) == -1)
680		err(1, "mkdir /rootfs failed");
681
682	/*
683	 * XXX: should not be hardcoded to cd9660.  but it is for now.
684	 * Maybe use mountroot() here somehow?
685	 */
686	for (i = 0; i < __arraycount(tryroot); i++) {
687		if (mount_blk(tryroot[i], "/rootfs"))
688			break;
689	}
690
691	/* didn't find it that way.  one more try: etfs for sda1 (EC2) */
692	if (i == __arraycount(tryroot)) {
693		char *devpath;
694
695		devpath = configetfs("sda1", 0);
696		if (!devpath)
697			errx(1, "failed to mount rootfs from image");
698
699		if (!mount_blk(devpath, "/rootfs"))
700			errx(1, "failed to mount /rootfs");
701	}
702
703	/*
704	 * Ok, we've successfully mounted /rootfs.  Now get the config.
705	 */
706
707	while (*cfgname == '/')
708		cfgname++;
709	if (chdir("/rootfs") == -1)
710		err(1, "chdir rootfs");
711
712	if ((fd = open(cfgname, O_RDONLY)) == -1)
713		err(1, "open %s", cfgname);
714	if (stat(cfgname, &sb) == -1)
715		err(1, "stat %s", cfgname);
716
717	if (sb.st_size > CFGMAXSIZE)
718		errx(1, "unbelievable cfg file size, increase CFGMAXSIZE");
719	if ((p = malloc(sb.st_size+1)) == NULL)
720		err(1, "cfgname storage");
721
722	if (read(fd, p, sb.st_size) != sb.st_size)
723		err(1, "read cfgfile");
724	close(fd);
725
726	p[sb.st_size] = '\0';
727	return p;
728}
729
730
731#define ROOTCFG "_RUMPRUN_ROOTFSCFG="
732static const size_t rootcfglen = sizeof(ROOTCFG)-1;
733static char *
734rumprun_config_path(char *cmdline)
735{
736	char *cfg = strstr(cmdline, ROOTCFG);
737
738	if (cfg != NULL)
739		cfg += rootcfglen;
740
741	return cfg;
742}
743#undef ROOTCFG
744
745void
746rumprun_config(char *cmdline)
747{
748	char *cfg;
749	struct rumprun_exec *rre;
750	jsmn_parser p;
751	jsmntok_t *tokens = NULL;
752	jsmntok_t *t;
753	size_t cmdline_len;
754	unsigned int i;
755	int ntok;
756
757	/* is the config file on rootfs?  if so, mount & dig it out */
758	cfg = rumprun_config_path(cmdline);
759	if (cfg != NULL) {
760		cmdline = getcmdlinefromroot(cfg);
761		if (cmdline == NULL)
762			errx(1, "could not get cfg from rootfs");
763	}
764
765	while (*cmdline != '{') {
766		if (*cmdline == '\0') {
767			warnx("could not find start of json.  no config?");
768			makeargv(strdup("rumprun"));
769			return;
770		}
771		cmdline++;
772	}
773
774	cmdline_len = strlen(cmdline);
775	jsmn_init(&p);
776	ntok = jsmn_parse(&p, cmdline, cmdline_len, NULL, 0);
777
778	if (ntok <= 0) {
779		errx(1, "json parse failed 1");
780	}
781
782	tokens = malloc(ntok * sizeof(*t));
783	if (!tokens) {
784		errx(1, "failed to allocate jsmn tokens");
785	}
786
787	jsmn_init(&p);
788	if ((ntok = jsmn_parse(&p, cmdline, cmdline_len, tokens, ntok)) < 1) {
789		errx(1, "json parse failed 2");
790	}
791
792	T_CHECKTYPE(tokens, cmdline, JSMN_OBJECT, __func__);
793
794	for (t = &tokens[0]; t < &tokens[ntok]; ) {
795		/* allow multiple levels of object nesting */
796		if (t->type == JSMN_OBJECT) {
797			t++;
798			continue;
799		}
800
801		T_CHECKTYPE(t, cmdline, JSMN_STRING, __func__);
802		for (i = 0; i < __arraycount(parsers); i++) {
803			if (T_STREQ(t, cmdline, parsers[i].name)) {
804				int left;
805
806				t++;
807				left = &tokens[ntok] - t;
808				t += parsers[i].handler(t, left, cmdline);
809				break;
810			}
811		}
812		if (i == __arraycount(parsers))
813			errx(1, "no match for key \"%.*s\"",
814			    T_PRINTFSTAR(t, cmdline));
815	}
816
817	/*
818	 * Before we start running things, perform some sanity checks
819	 */
820	rre = TAILQ_LAST(&rumprun_execs, rumprun_execs);
821	if (rre == NULL) {
822		errx(1, "rumprun_config: no bins");
823	}
824	if (rre->rre_flags & RUMPRUN_EXEC_PIPE) {
825		errx(1, "rumprun_config: last bin may not output to pipe");
826	}
827
828	free(tokens);
829}
830